import unittest from semantic_index.redmine import RedmineApiSource class RecordingRedmineSource(RedmineApiSource): def __init__(self): super().__init__(redmine_url="http://redmine.local", api_key="secret", project_identifier="customer-service") self.urls = [] def _get_json(self, url): self.urls.append(url) if url.startswith("http://redmine.local/issues.json"): return {"issues": [{"id": 39779}]} return {"issue": {"id": 39779, "subject": "Goods return"}} class PagedRedmineSource(RedmineApiSource): def __init__(self): super().__init__(redmine_url="http://redmine.local", api_key="secret", project_identifier="customer-service") self.urls = [] def _get_json(self, url): self.urls.append(url) if url.startswith("http://redmine.local/issues.json"): query = url.split("?", 1)[1] params = dict(part.split("=", 1) for part in query.split("&")) offset = int(params.get("offset", "0")) limit = int(params.get("limit", "0")) return {"issues": [{"id": issue_id} for issue_id in range(offset + 1, offset + limit + 1)]} issue_id = int(url.split("/issues/", 1)[1].split(".", 1)[0]) return {"issue": {"id": issue_id, "subject": f"Issue {issue_id}"}} class DuplicatePagedRedmineSource(RedmineApiSource): def __init__(self): super().__init__(redmine_url="http://redmine.local", api_key="secret", project_identifier="customer-service") def _get_json(self, url): if url.startswith("http://redmine.local/issues.json"): query = url.split("?", 1)[1] params = dict(part.split("=", 1) for part in query.split("&")) offset = int(params.get("offset", "0")) if offset == 0: return {"issues": [{"id": 1}, {"id": 2}]} if offset == 2: return {"issues": [{"id": 2}, {"id": 3}]} return {"issues": []} issue_id = int(url.split("/issues/", 1)[1].split(".", 1)[0]) return {"issue": {"id": issue_id, "subject": f"Issue {issue_id}"}} class RedmineApiSourceTest(unittest.TestCase): def test_recent_issue_summaries_do_not_fetch_issue_details(self): source = RecordingRedmineSource() summaries = list(source.recent_issue_summaries(limit=1)) self.assertEqual(39779, summaries[0]["id"]) self.assertEqual(1, len(source.urls)) self.assertTrue(source.urls[0].startswith("http://redmine.local/issues.json")) def test_issue_detail_fetches_journals_and_helpdesk(self): source = RecordingRedmineSource() detail = source.issue_detail(39779) self.assertEqual(39779, detail["id"]) self.assertIn("include=journals%2Chelpdesk", source.urls[0]) def test_recent_helpdesk_issues_requests_helpdesk_include_with_journals(self): source = RecordingRedmineSource() issues = list(source.recent_helpdesk_issues(limit=1)) self.assertEqual(39779, issues[0]["id"]) self.assertIn("include=journals%2Chelpdesk", source.urls[1]) self.assertIn("subproject_id=%21%2A", source.urls[0]) def test_recent_helpdesk_issues_paginates_past_redmine_page_limit(self): source = PagedRedmineSource() issues = list(source.recent_helpdesk_issues(limit=250)) self.assertEqual(250, len(issues)) list_urls = [url for url in source.urls if url.startswith("http://redmine.local/issues.json")] self.assertEqual(3, len(list_urls)) self.assertIn("limit=100", list_urls[0]) self.assertIn("offset=0", list_urls[0]) self.assertIn("offset=100", list_urls[1]) self.assertIn("offset=200", list_urls[2]) def test_recent_helpdesk_issues_skips_duplicate_issue_ids_across_pages(self): source = DuplicatePagedRedmineSource() issues = list(source.recent_helpdesk_issues(limit=3)) self.assertEqual([1, 2, 3], [issue["id"] for issue in issues]) if __name__ == "__main__": unittest.main()