139 lines
6.4 KiB
Python
139 lines
6.4 KiB
Python
import unittest
|
|
|
|
from semantic_index.models import IndexDocument
|
|
from semantic_index.redmine import RedmineMapper
|
|
|
|
|
|
class RedmineMapperTest(unittest.TestCase):
|
|
def test_issue_chunks_have_stable_ids_and_metadata(self):
|
|
issue = {
|
|
"id": 42,
|
|
"subject": "Widget order ORD-12345 cannot ship",
|
|
"description": "Customer reports that widget order ORD-12345 is blocked.",
|
|
"project": {"id": 7, "identifier": "fud-helpdesk"},
|
|
"contact": {"id": 9, "email": "ada@example.com", "name": "Ada Lovelace"},
|
|
"created_on": "2026-04-01T10:00:00Z",
|
|
"updated_on": "2026-04-02T10:00:00Z",
|
|
"url": "http://redmine.local/issues/42",
|
|
}
|
|
|
|
first = RedmineMapper(redmine_url="http://redmine.local").issue_to_documents(issue)
|
|
second = RedmineMapper(redmine_url="http://redmine.local").issue_to_documents(issue)
|
|
|
|
self.assertEqual([doc.id for doc in first], [doc.id for doc in second])
|
|
self.assertEqual("redmine:issue:42:chunk:0", first[0].id)
|
|
self.assertEqual("issue", first[0].payload["doc_type"])
|
|
self.assertEqual(42, first[0].payload["issue_id"])
|
|
self.assertEqual("fud-helpdesk", first[0].payload["project_identifier"])
|
|
self.assertIsNone(first[0].payload["project_name"])
|
|
self.assertFalse(first[0].payload["has_helpdesk_ticket"])
|
|
self.assertEqual("ada@example.com", first[0].payload["contact_email"])
|
|
self.assertEqual("Ada Lovelace", first[0].payload["contact_name"])
|
|
self.assertEqual("http://redmine.local/issues/42", first[0].payload["redmine_url"])
|
|
self.assertIn("source_hash", first[0].payload)
|
|
|
|
def test_helpdesk_ticket_contact_is_mapped_to_all_issue_chunks(self):
|
|
issue = {
|
|
"id": 39779,
|
|
"subject": "Goods return",
|
|
"description": "Please arrange to return these goods.",
|
|
"project": {"id": 1, "identifier": "customer-service"},
|
|
"helpdesk_ticket": {
|
|
"id": 35159,
|
|
"contact_id": 1890,
|
|
"from_address": "callum@safetagtracking.com",
|
|
"contact": {
|
|
"id": 1890,
|
|
"name": "Callum Mackeonis",
|
|
"company": "SafeTag Tracking",
|
|
"email": "callum@safetagtracking.com",
|
|
},
|
|
},
|
|
"journals": [
|
|
{"id": 71570, "notes": "Hello, yes we can arrange this today.", "created_on": "2026-04-14T14:29:49Z"}
|
|
],
|
|
}
|
|
|
|
docs = RedmineMapper(redmine_url="http://redmine.local").issue_to_documents(issue)
|
|
issue_doc = next(doc for doc in docs if doc.payload["doc_type"] == "issue")
|
|
journal_doc = next(doc for doc in docs if doc.payload["doc_type"] == "journal")
|
|
contact_doc = next(doc for doc in docs if doc.payload["doc_type"] == "contact")
|
|
|
|
for doc in (issue_doc, journal_doc, contact_doc):
|
|
self.assertEqual(35159, doc.payload["helpdesk_ticket_id"])
|
|
self.assertTrue(doc.payload["has_helpdesk_ticket"])
|
|
self.assertEqual(1890, doc.payload["contact_id"])
|
|
self.assertEqual("Callum Mackeonis", doc.payload["contact_name"])
|
|
self.assertEqual("SafeTag Tracking", doc.payload["contact_company"])
|
|
self.assertEqual("callum@safetagtracking.com", doc.payload["contact_email"])
|
|
self.assertIn("Callum Mackeonis", issue_doc.text)
|
|
self.assertIn("callum@safetagtracking.com", contact_doc.text)
|
|
|
|
def test_configured_project_identifier_is_used_when_issue_payload_omits_identifier(self):
|
|
issue = {
|
|
"id": 42,
|
|
"subject": "Widget order",
|
|
"description": "Body",
|
|
"project": {"id": 1, "name": "Customer Service"},
|
|
}
|
|
|
|
docs = RedmineMapper(
|
|
redmine_url="http://redmine.local",
|
|
project_identifier="customer-service",
|
|
).issue_to_documents(issue)
|
|
|
|
self.assertEqual("customer-service", docs[0].payload["project_identifier"])
|
|
self.assertEqual("Customer Service", docs[0].payload["project_name"])
|
|
|
|
def test_internal_non_helpdesk_issue_keeps_project_metadata_without_contact(self):
|
|
issue = {
|
|
"id": 55,
|
|
"subject": "Internal hiring task",
|
|
"description": "Follow up with candidate.",
|
|
"project": {"id": 68, "identifier": "hiring", "name": "Hiring"},
|
|
}
|
|
|
|
docs = RedmineMapper(redmine_url="http://redmine.local").issue_to_documents(issue)
|
|
|
|
self.assertEqual(1, len(docs))
|
|
self.assertEqual("hiring", docs[0].payload["project_identifier"])
|
|
self.assertEqual("Hiring", docs[0].payload["project_name"])
|
|
self.assertFalse(docs[0].payload["has_helpdesk_ticket"])
|
|
self.assertIsNone(docs[0].payload["contact_id"])
|
|
|
|
def test_issue_journals_messages_and_contact_are_mapped(self):
|
|
issue = {
|
|
"id": 42,
|
|
"subject": "Widget order",
|
|
"description": "Ticket envelope",
|
|
"project": {"id": 7, "identifier": "fud-helpdesk"},
|
|
"contact": {"id": 9, "email": "ada@example.com", "name": "Ada Lovelace"},
|
|
"journals": [
|
|
{"id": 5, "notes": "Private escalation note", "private_notes": True, "created_on": "2026-04-03T10:00:00Z"}
|
|
],
|
|
"messages": [
|
|
{"id": 6, "body": "Customer reply body", "direction": "incoming", "created_on": "2026-04-03T11:00:00Z"}
|
|
],
|
|
}
|
|
|
|
docs = RedmineMapper(redmine_url="http://redmine.local").issue_to_documents(issue)
|
|
ids = {doc.id for doc in docs}
|
|
types = {doc.payload["doc_type"] for doc in docs}
|
|
|
|
self.assertIn("redmine:issue:42:journal:5:chunk:0", ids)
|
|
self.assertIn("redmine:issue:42:message:6:chunk:0", ids)
|
|
self.assertIn("redmine:contact:9:issue:42:chunk:0", ids)
|
|
self.assertEqual({"issue", "journal", "message", "contact"}, types)
|
|
journal = next(doc for doc in docs if doc.payload["doc_type"] == "journal")
|
|
message = next(doc for doc in docs if doc.payload["doc_type"] == "message")
|
|
self.assertEqual("private", journal.payload["visibility"])
|
|
self.assertEqual("incoming", message.payload["direction"])
|
|
|
|
def test_empty_documents_are_rejected(self):
|
|
with self.assertRaises(ValueError):
|
|
IndexDocument(id="x", text=" ", payload={})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|