#!/usr/bin/env python3 from __future__ import annotations import unittest from datetime import datetime, timedelta, timezone from redmine_ticket_query import QueryError, compile_query, filter_by_query, issue_matches from redmine_ticket_view import render_ticket_markdown class PropertyQueryTest(unittest.TestCase): def setUp(self): self.issue = { "id": 1, "subject": "Please send verification code", "updated_on": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), "status": {"name": "New"}, "assigned_to": None, "helpdesk_ticket": { "from_address": "same@example.com", "contact": { "id": 9, "email": "same@example.com", "company": "same@example.com", "name": "Same", }, }, } def test_includes(self): self.assertTrue(issue_matches(self.issue, 'subject.includes("verification code")')) self.assertFalse(issue_matches(self.issue, 'subject.includes("magic link")')) def test_path_eq_path(self): expr = "helpdesk_ticket.contact.email == helpdesk_ticket.contact.company" self.assertTrue(issue_matches(self.issue, expr)) def test_is_null(self): self.assertTrue(issue_matches(self.issue, "assigned_to IS NULL")) self.issue["assigned_to"] = {"name": "Jason T."} self.assertFalse(issue_matches(self.issue, "assigned_to IS NULL")) self.assertTrue(issue_matches(self.issue, 'assigned_to != "Other"')) self.assertFalse(issue_matches(self.issue, 'assigned_to != "Jason T."')) def test_and_or_parens(self): expr = ( '(subject.includes("verification code") OR subject.includes("magic link")) ' 'AND (assigned_to IS NULL)' ) self.assertTrue(issue_matches(self.issue, expr)) def test_ampersand_and(self): self.assertTrue( issue_matches(self.issue, 'subject.includes("verification") && status.name == "New"') ) def test_since(self): self.assertTrue(issue_matches(self.issue, 'updated_on.since("1 hour")')) old = { **self.issue, "updated_on": (datetime.now(timezone.utc) - timedelta(days=10)) .isoformat() .replace("+00:00", "Z"), } self.assertFalse(issue_matches(old, 'updated_on.since("1 day")')) def test_filter_multiple_and(self): issues = [self.issue, {**self.issue, "id": 2, "subject": "hello"}] out = filter_by_query( issues, expressions=['subject.includes("verification")', 'status.name == "New"'], ) self.assertEqual([1], [i["id"] for i in out]) def test_bad_expr(self): with self.assertRaises(QueryError): compile_query("subject ~~ boom") class MarkdownRenderTest(unittest.TestCase): def test_render_contains_core_fields(self): md = render_ticket_markdown( { "id": 42, "subject": "Test", "status": {"name": "New"}, "project": {"name": "customer-service"}, "description": "Body here", "journals": [], "updated_on": "2026-07-14T00:00:00Z", "helpdesk_ticket": {"from_address": "a@b.c", "contact": {"id": 1}}, } ) self.assertIn("# #42", md) self.assertIn("Body here", md) self.assertIn("a@b.c", md) if __name__ == "__main__": unittest.main()