18 lines
18 KiB
XML
18 lines
18 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<project version="4">
|
|
<component name="CopilotDiffPersistence">
|
|
<option name="pendingDiffs">
|
|
<map>
|
|
<entry key="/test_weekly_report (snapshot).py">
|
|
<value>
|
|
<PendingDiffInfo>
|
|
<option name="filePath" value="/test_weekly_report (snapshot).py" />
|
|
<option name="originalContent" value="import os import sys import tempfile import unittest from datetime import date from io import BytesIO from pathlib import Path from openpyxl import load_workbook PROJECT_DIR = Path(__file__).resolve().parents[1] sys.path.insert(0, str(PROJECT_DIR / "stempelbot")) os.environ.setdefault("USERNAME", "test-user") os.environ.setdefault("PASSWORD", "test-password") os.environ.setdefault("SMART_TIME_PASSWORD", "smarttime-password") os.environ.setdefault("AUTOSTAMP_FILE", "autostamps.json") os.environ.setdefault("STAMPHISTORY_FILE", "timestamp_history.json") os.environ.setdefault("LOG_LEVEL", "CRITICAL") from excel_export import export_weekly_workbook, load_template_options # noqa: E402 from jira_client import JiraActivity, JiraClient, JiraError # noqa: E402 from weekly_report import calculate_week, iso_week_bounds # noqa: E402 TEMPLATE = PROJECT_DIR / "TimeTracking Activation_Firstname_Lastname_CW_Version 15.xlsx" class FakeResponse: def __init__(self, payload, status_code=200): self.payload = payload self.status_code = status_code def raise_for_status(self): if self.status_code >= 400: import requests response = requests.Response() response.status_code = self.status_code raise requests.HTTPError(response=response) def json(self): return self.payload @property def text(self): return str(self.payload) @property def reason(self): return "Unauthorized" if self.status_code == 401 else "OK" class FakeSession: def __init__(self, responses): self.responses = iter(responses) self.headers = {} self.auth = None self.calls = [] def get(self, url, **kwargs): self.calls.append((url, kwargs)) response = next(self.responses) return response if isinstance(response, FakeResponse) else FakeResponse(response) class WeeklyReportTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.options = load_template_options(TEMPLATE) def test_iso_week_can_cross_year_boundary(self): self.assertEqual( iso_week_bounds(2026, 1), (date(2025, 12, 29), date(2026, 1, 4)), ) def test_daily_net_time_applies_legal_break_and_accounts_for_minutes(self): day = date(2026, 8, 24) rows = calculate_week( 2026, 35, {day: ["08:00", "12:00", "12:15", "16:15"]}, [JiraActivity(day, "ABC-1", "Implement export", "Other", "change", "code updated", "url")], self.options, ) self.assertEqual(len(rows), 1) self.assertEqual(rows[0].minutes, 465) self.assertEqual(rows[0].issue_keys, "ABC-1") self.assertEqual(rows[0].project, "Other") def test_incomplete_historical_stamp_is_not_guessed(self): day = date(2026, 8, 25) rows = calculate_week(2026, 35, {day: ["08:00"]}, [], self.options) self.assertEqual(rows[0].minutes, 0) self.assertIn("Incomplete", rows[0].warning) def test_jira_pagination_and_authored_activity_filtering(self): issue = { "key": "ABC-7", "fields": { "summary": "Build report", "project": {"name": "Other"}, "assignee": {"accountId": "me"}, "creator": {"accountId": "someone"}, "updated": "2026-08-24T15:00:00+00:00", "comment": { "comments": [ {"author": {"accountId": "me"}, "created": "2026-08-24T12:00:00+00:00"}, {"author": {"accountId": "other"}, "created": "2026-08-24T13:00:00+00:00"}, ] }, "worklog": {"worklogs": []}, }, "changelog": {"histories": []}, } session = FakeSession([ {"accountId": "me", "displayName": "Tester"}, {"issues": [issue], "nextPageToken": "next"}, {"issues": [], "isLast": True}, ]) client = JiraClient("https://jira.example", "mail", "token", session=session) activities = client.get_week_activity(date(2026, 8, 24), date(2026, 8, 30)) self.assertEqual([(item.issue_key, item.activity_type) for item in activities], [("ABC-7", "comment")]) self.assertEqual(session.calls[2][1]["params"]["nextPageToken"], "next") def test_jira_http_failure_logs_response_without_token(self): session = FakeSession([ FakeResponse( {"errorMessages": ["Client must be authenticated"]}, status_code=401 ) ]) client = JiraClient( "https://jira.example", "mail@example.com", "super-secret-token", session=session ) with self.assertLogs("jira_client", level="ERROR") as captured: with self.assertRaisesRegex(JiraError, "HTTP 401"): client._get("/rest/api/3/myself") output = "\n".join(captured.output) self.assertIn("Client must be authenticated", output) self.assertIn("401 Unauthorized", output) self.assertIn("GET https://jira.example/rest/api/3/myself", output) self.assertNotIn("super-secret-token", output) def test_export_preserves_template_and_exact_total(self): first = date(2026, 8, 24) rows = calculate_week( 2026, 35, { first: ["08:00", "12:00"], date(2026, 8, 25): ["09:00", "10:01"], }, [], self.options, ) data = export_weekly_workbook(TEMPLATE, "Test Person", 2026, 35, rows) workbook = load_workbook(BytesIO(data), data_only=False) sheet = workbook["weekly time tracking"] self.assertEqual(workbook.sheetnames, ["weekly time tracking", "Dropdowns"]) self.assertEqual(sheet["B3"].value, "Test Person") self.assertEqual(sheet["B6"].value, "KW 35") self.assertEqual(sheet["C6"].value, "24.08. bis 30.08.") self.assertAlmostEqual(sheet["H6"].value + sheet["H7"].value, 301 / 60) self.assertEqual(sheet["H6"].number_format, "0.00") self.assertEqual(len(sheet.data_validations.dataValidation), 5) self.assertTrue(workbook.calculation.fullCalcOnLoad) self.assertGreater(len(sheet.merged_cells.ranges), 0) workbook.close() if __name__ == "__main__": unittest.main() " />
|
|
<option name="updatedContent" value="import os import sys import tempfile import unittest from datetime import date from io import BytesIO from pathlib import Path from types import SimpleNamespace from openpyxl import load_workbook PROJECT_DIR = Path(__file__).resolve().parents[1] sys.path.insert(0, str(PROJECT_DIR / "stempelbot")) os.environ.setdefault("USERNAME", "test-user") os.environ.setdefault("PASSWORD", "test-password") os.environ.setdefault("SMART_TIME_PASSWORD", "smarttime-password") os.environ.setdefault("AUTOSTAMP_FILE", "autostamps.json") os.environ.setdefault("STAMPHISTORY_FILE", "timestamp_history.json") os.environ.setdefault("LOG_LEVEL", "CRITICAL") from excel_export import export_weekly_workbook, load_template_options # noqa: E402 from jira_client import JiraActivity, JiraClient, JiraError # noqa: E402 from weekly_report import calculate_week, iso_week_bounds # noqa: E402 TEMPLATE = PROJECT_DIR / "TimeTracking Activation_Firstname_Lastname_CW_Version 15.xlsx" class FakeResponse: def __init__(self, payload, status_code=200): self.payload = payload self.status_code = status_code self.headers = {} self.request = None def raise_for_status(self): if self.status_code >= 400: import requests raise requests.HTTPError(response=self) def json(self): return self.payload @property def text(self): return str(self.payload) @property def reason(self): return "Unauthorized" if self.status_code == 401 else "OK" class FakeSession: def __init__(self, responses): self.responses = iter(responses) self.headers = {} self.auth = None self.calls = [] def get(self, url, **kwargs): self.calls.append((url, kwargs)) response = next(self.responses) return response if isinstance(response, FakeResponse) else FakeResponse(response) class WeeklyReportTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.options = load_template_options(TEMPLATE) def test_iso_week_can_cross_year_boundary(self): self.assertEqual( iso_week_bounds(2026, 1), (date(2025, 12, 29), date(2026, 1, 4)), ) def test_daily_net_time_applies_legal_break_and_accounts_for_minutes(self): day = date(2026, 8, 24) rows = calculate_week( 2026, 35, {day: ["08:00", "12:00", "12:15", "16:15"]}, [JiraActivity(day, "ABC-1", "Implement export", "Other", "change", "code updated", "url")], self.options, ) self.assertEqual(len(rows), 1) self.assertEqual(rows[0].minutes, 465) self.assertEqual(rows[0].issue_keys, "ABC-1") self.assertEqual(rows[0].project, "Other") def test_incomplete_historical_stamp_is_not_guessed(self): day = date(2026, 8, 25) rows = calculate_week(2026, 35, {day: ["08:00"]}, [], self.options) self.assertEqual(rows[0].minutes, 0) self.assertIn("Incomplete", rows[0].warning) def test_jira_pagination_and_authored_activity_filtering(self): issue = { "key": "ABC-7", "fields": { "summary": "Build report", "project": {"name": "Other"}, "assignee": {"accountId": "me"}, "creator": {"accountId": "someone"}, "updated": "2026-08-24T15:00:00+00:00", "comment": { "comments": [ {"author": {"accountId": "me"}, "created": "2026-08-24T12:00:00+00:00"}, {"author": {"accountId": "other"}, "created": "2026-08-24T13:00:00+00:00"}, ] }, "worklog": {"worklogs": []}, }, "changelog": {"histories": []}, } session = FakeSession([ {"accountId": "me", "displayName": "Tester"}, {"issues": [issue], "nextPageToken": "next"}, {"issues": [], "isLast": True}, ]) client = JiraClient("https://jira.example", "mail", "token", session=session) activities = client.get_week_activity(date(2026, 8, 24), date(2026, 8, 30)) self.assertEqual([(item.issue_key, item.activity_type) for item in activities], [("ABC-7", "comment")]) self.assertEqual(session.calls[2][1]["params"]["nextPageToken"], "next") def test_jira_http_failure_logs_response_without_token(self): response = FakeResponse( {"errorMessages": ["Client must be authenticated"]}, status_code=401 ) response.request = SimpleNamespace( method="GET", url="https://jira.example/rest/api/3/myself", headers={"Accept": "application/json", "Authorization": "Basic super-secret-token"}, ) response.headers = { "WWW-Authenticate": 'Bearer realm="Atlassian"', "Set-Cookie": "secret-session-cookie", } session = FakeSession([ response ]) client = JiraClient( "https://jira.example", "mail@example.com", "super-secret-token", session=session ) with self.assertLogs("jira_client", level="ERROR") as captured: with self.assertRaisesRegex(JiraError, "HTTP 401"): client._get("/rest/api/3/myself") output = "\n".join(captured.output) self.assertIn("Client must be authenticated", output) self.assertIn("401 Unauthorized", output) self.assertIn("GET https://jira.example/rest/api/3/myself", output) self.assertIn("WWW-Authenticate", output) self.assertIn("<redacted>", output) self.assertNotIn("super-secret-token", output) self.assertNotIn("secret-session-cookie", output) def test_export_preserves_template_and_exact_total(self): first = date(2026, 8, 24) rows = calculate_week( 2026, 35, { first: ["08:00", "12:00"], date(2026, 8, 25): ["09:00", "10:01"], }, [], self.options, ) data = export_weekly_workbook(TEMPLATE, "Test Person", 2026, 35, rows) workbook = load_workbook(BytesIO(data), data_only=False) sheet = workbook["weekly time tracking"] self.assertEqual(workbook.sheetnames, ["weekly time tracking", "Dropdowns"]) self.assertEqual(sheet["B3"].value, "Test Person") self.assertEqual(sheet["B6"].value, "KW 35") self.assertEqual(sheet["C6"].value, "24.08. bis 30.08.") self.assertAlmostEqual(sheet["H6"].value + sheet["H7"].value, 301 / 60) self.assertEqual(sheet["H6"].number_format, "0.00") self.assertEqual(len(sheet.data_validations.dataValidation), 5) self.assertTrue(workbook.calculation.fullCalcOnLoad) self.assertGreater(len(sheet.merged_cells.ranges), 0) workbook.close() if __name__ == "__main__": unittest.main() " />
|
|
</PendingDiffInfo>
|
|
</value>
|
|
</entry>
|
|
</map>
|
|
</option>
|
|
</component>
|
|
</project> |