| 1 | import json |
| 2 | import tempfile |
| 3 | import unittest |
| 4 | from datetime import date |
| 5 | from pathlib import Path |
| 6 | |
| 7 | import yaml |
| 8 | |
| 9 | import scripts.check_copilot_pricing_review as pricing_review |
| 10 | |
| 11 | |
| 12 | class CopilotPricingReviewTests(unittest.TestCase): |
| 13 | def test_review_not_due_before_two_month_interval(self) -> None: |
| 14 | status = pricing_review.pricing_status(date(2026, 8, 5)) |
| 15 | self.assertFalse(status["needs_review"]) |
| 16 | self.assertFalse(status["review_due"]) |
| 17 | self.assertEqual(status["due_date"], "2026-08-06") |
| 18 | |
| 19 | def test_review_due_at_two_month_interval(self) -> None: |
| 20 | status = pricing_review.pricing_status(date(2026, 8, 6)) |
| 21 | self.assertTrue(status["needs_review"]) |
| 22 | self.assertTrue(status["review_due"]) |
| 23 | |
| 24 | def test_source_url_mismatch_requires_review(self) -> None: |
| 25 | status = pricing_review.pricing_status( |
| 26 | date(2026, 7, 1), source_url="https://example.invalid/pricing" |
| 27 | ) |
| 28 | self.assertTrue(status["needs_review"]) |
| 29 | self.assertFalse(status["source_url_matches"]) |
| 30 | |
| 31 | def test_source_headers_are_parsed_for_report_metadata(self) -> None: |
| 32 | tests_root = Path(__file__).resolve().parent |
| 33 | with tempfile.TemporaryDirectory(dir=tests_root) as tmpdir: |
| 34 | headers = Path(tmpdir) / "headers.txt" |
| 35 | headers.write_text( |
| 36 | 'HTTP/2 200\netag: "abc123"\nlast-modified: Sat, 06 Jun 2026 00:00:00 GMT\n', |
| 37 | encoding="utf-8", |
| 38 | ) |
| 39 | status = pricing_review.pricing_status( |
| 40 | date(2026, 7, 1), source_headers=pricing_review.parse_source_headers(headers) |
| 41 | ) |
| 42 | report = pricing_review.render_report(status) |
| 43 | self.assertEqual(status["source_headers"]["etag"], '"abc123"') |
| 44 | self.assertIn("last-modified", report) |
| 45 | |
| 46 | def test_main_writes_report_json_and_github_outputs(self) -> None: |
| 47 | tests_root = Path(__file__).resolve().parent |
| 48 | with tempfile.TemporaryDirectory(dir=tests_root) as tmpdir: |
| 49 | base = Path(tmpdir) |
| 50 | report_path = base / "report.md" |
| 51 | json_path = base / "status.json" |
| 52 | github_output = base / "github-output.txt" |
| 53 | |
| 54 | rc = pricing_review.main( |
| 55 | [ |
| 56 | "--current-date", |
| 57 | "2026-08-06", |
| 58 | "--output", |
| 59 | str(report_path), |
| 60 | "--json-output", |
| 61 | str(json_path), |
| 62 | "--github-output", |
| 63 | str(github_output), |
| 64 | ] |
| 65 | ) |
| 66 | |
| 67 | self.assertEqual(rc, 0) |
| 68 | self.assertIn( |
| 69 | "does not change pricing automatically", report_path.read_text(encoding="utf-8") |
| 70 | ) |
| 71 | status = json.loads(json_path.read_text(encoding="utf-8")) |
| 72 | self.assertTrue(status["needs_review"]) |
| 73 | self.assertIn("needs_review=true", github_output.read_text(encoding="utf-8")) |
| 74 | |
| 75 | |
| 76 | class CopilotPricingReviewWorkflowTests(unittest.TestCase): |
| 77 | def test_workflow_is_scheduled_and_opens_issue_without_changing_pricing(self) -> None: |
| 78 | workflow_path = Path(".github/workflows/copilot-pricing-review.yml") |
| 79 | workflow = yaml.safe_load(workflow_path.read_text(encoding="utf-8")) |
| 80 | |
| 81 | self.assertEqual(workflow["name"], "Copilot Pricing Review") |
| 82 | trigger = workflow.get("on", workflow.get(True)) |
| 83 | self.assertIsNotNone(trigger) |
| 84 | self.assertEqual(trigger["schedule"][0]["cron"], "23 9 6 2,4,6,8,10,12 *") |
| 85 | self.assertIn("workflow_dispatch", trigger) |
| 86 | self.assertEqual(workflow["permissions"], {"contents": "read"}) |
| 87 | |
| 88 | job = workflow["jobs"]["review-pricing"] |
| 89 | # issues:write is scoped to the job (least-privilege) rather than the workflow. |
| 90 | self.assertEqual(job["permissions"], {"contents": "read", "issues": "write"}) |
| 91 | pricing_step = next((step for step in job["steps"] if step.get("id") == "pricing"), None) |
| 92 | self.assertIsNotNone(pricing_step) |
| 93 | self.assertIn("scripts/check_copilot_pricing_review.py", pricing_step["run"]) |
| 94 | self.assertIn("--source-headers", pricing_step["run"]) |
| 95 | self.assertIn("--github-output", pricing_step["run"]) |
| 96 | metadata_step = next( |
| 97 | ( |
| 98 | step |
| 99 | for step in job["steps"] |
| 100 | if step.get("name") == "Capture Copilot pricing source metadata" |
| 101 | ), |
| 102 | None, |
| 103 | ) |
| 104 | self.assertIsNotNone(metadata_step) |
| 105 | self.assertIn("curl -fsSLI", metadata_step["run"]) |
| 106 | |
| 107 | issue_step = next( |
| 108 | ( |
| 109 | step |
| 110 | for step in job["steps"] |
| 111 | if step.get("name") == "Create or update pricing review issue" |
| 112 | ), |
| 113 | None, |
| 114 | ) |
| 115 | self.assertIsNotNone(issue_step) |
| 116 | self.assertEqual(issue_step["if"], "steps.pricing.outputs.needs_review == 'true'") |
| 117 | self.assertIn("gh issue create", issue_step["run"]) |
| 118 | self.assertIn("gh issue comment", issue_step["run"]) |
| 119 | self.assertNotIn("git commit", issue_step["run"]) |
| 120 | self.assertNotIn("git push", issue_step["run"]) |
| 121 | |
| 122 | |
| 123 | if __name__ == "__main__": |
| 124 | unittest.main() |