|
| 1 | +"""Unit tests for scripts/upstream_compat.py (SDK-22).""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import unittest |
| 6 | + |
| 7 | +import upstream_compat as uc |
| 8 | + |
| 9 | +FASTMCP = uc.Entry("fastmcp", "fastmcp", "project.dependencies") |
| 10 | +MCP = uc.Entry("mcp", "mcp", "project.dependencies") |
| 11 | +AGENTS = uc.Entry( |
| 12 | + "temporal", "openai-agents", "project.optional-dependencies.openai-agents" |
| 13 | +) |
| 14 | +TEMPORALIO = uc.Entry("temporal", "temporalio", "project.dependencies") |
| 15 | + |
| 16 | +PYPROJECT = """[project] |
| 17 | +name = "keycardai-temporal" |
| 18 | +dependencies = [ |
| 19 | + "keycardai-oauth>=0.33.0", |
| 20 | + "temporalio>=1.32.0", |
| 21 | +] |
| 22 | +
|
| 23 | +[project.optional-dependencies] |
| 24 | +openai-agents = [ |
| 25 | + "openai-agents>=0.20.0", |
| 26 | + "temporalio[openai-agents]>=1.32.0", |
| 27 | +] |
| 28 | +test = [ |
| 29 | + "pytest>=8.0", |
| 30 | +] |
| 31 | +
|
| 32 | +[project.urls] |
| 33 | +Homepage = "https://keycard.ai" |
| 34 | +""" |
| 35 | + |
| 36 | + |
| 37 | +class SpecifierTests(unittest.TestCase): |
| 38 | + def test_open_floor_covers_every_later_release(self) -> None: |
| 39 | + self.assertTrue(uc.in_range(">=3.1.0", "4.1.0")) |
| 40 | + self.assertFalse(uc.has_cap(">=3.1.0")) |
| 41 | + self.assertEqual(uc.widened_range(">=3.1.0", "4.1.0"), ">=3.1.0") |
| 42 | + self.assertEqual(uc.decide(True, uc.in_range(">=3.1.0", "4.1.0")), "record") |
| 43 | + |
| 44 | + def test_capped_same_major_moves_the_cap(self) -> None: |
| 45 | + self.assertTrue(uc.in_range(">=2.0.0,<3.0", "2.3.0")) |
| 46 | + self.assertFalse(uc.in_range(">=1.2.0,<1.9.0", "1.9.3")) |
| 47 | + self.assertEqual(uc.widened_range(">=1.2.0,<1.9.0", "1.9.3"), ">=1.2.0,<2.0") |
| 48 | + self.assertFalse(uc.is_new_major(">=1.2.0,<1.9.0", "1.9.3")) |
| 49 | + |
| 50 | + def test_capped_new_major_starts_a_new_line(self) -> None: |
| 51 | + self.assertFalse(uc.in_range(">=2.0.0,<3.0", "3.1.0")) |
| 52 | + self.assertEqual(uc.widened_range(">=2.0.0,<3.0", "3.1.0"), ">=3.1.0,<4.0") |
| 53 | + self.assertTrue(uc.is_new_major(">=2.0.0,<3.0", "3.1.0")) |
| 54 | + self.assertIn( |
| 55 | + "breaking", uc.pr_body(MCP, "3.1.0", ">=2.0.0,<3.0", ">=3.1.0,<4.0", "") |
| 56 | + ) |
| 57 | + self.assertNotIn( |
| 58 | + "breaking", uc.pr_body(MCP, "1.9.3", ">=1.2.0,<1.9.0", ">=1.2.0,<2.0", "") |
| 59 | + ) |
| 60 | + |
| 61 | + def test_zero_x_treats_each_minor_as_breaking(self) -> None: |
| 62 | + self.assertFalse(uc.in_range(">=0.20.0,<0.21.0", "0.23.1")) |
| 63 | + self.assertEqual( |
| 64 | + uc.widened_range(">=0.20.0,<0.21.0", "0.23.1"), ">=0.23.1,<0.24.0" |
| 65 | + ) |
| 66 | + self.assertTrue(uc.is_new_major(">=0.20.0,<0.21.0", "0.23.1")) |
| 67 | + self.assertEqual( |
| 68 | + uc.widened_range(">=0.23.0,<0.23.1", "0.23.2"), ">=0.23.0,<0.24.0" |
| 69 | + ) |
| 70 | + self.assertTrue(uc.in_range(">=0.20.0", "0.23.1")) |
| 71 | + |
| 72 | + def test_other_comparators_are_kept(self) -> None: |
| 73 | + self.assertEqual( |
| 74 | + uc.widened_range(">=2.0.0,!=2.1.0,<3.0", "3.0.2"), ">=3.0.2,<4.0,!=2.1.0" |
| 75 | + ) |
| 76 | + |
| 77 | + def test_prereleases_and_yanked_files_are_not_latest(self) -> None: |
| 78 | + releases = { |
| 79 | + "4.1.0": [{"yanked": False}], |
| 80 | + "4.2.0rc1": [{"yanked": False}], |
| 81 | + "4.2.0.dev1": [{"yanked": False}], |
| 82 | + "4.1.1": [{"yanked": True}], |
| 83 | + "3.9.0": [{"yanked": False}], |
| 84 | + "garbage": [{"yanked": False}], |
| 85 | + } |
| 86 | + self.assertEqual(uc.latest_stable(releases), "4.1.0") |
| 87 | + |
| 88 | + def test_decision_matrix(self) -> None: |
| 89 | + self.assertEqual(uc.decide(True, True), "record") |
| 90 | + self.assertEqual(uc.decide(True, False), "pr") |
| 91 | + self.assertEqual(uc.decide(False, True), "issue") |
| 92 | + self.assertEqual(uc.decide(False, False), "issue") |
| 93 | + |
| 94 | + def test_issue_body_says_whether_users_are_affected(self) -> None: |
| 95 | + inside = uc.issue_body(FASTMCP, "4.1.0", ">=3.1.0", True, "E boom", "") |
| 96 | + self.assertIn( |
| 97 | + "users installing keycardai-fastmcp today already get this", inside |
| 98 | + ) |
| 99 | + outside = uc.issue_body(MCP, "3.1.0", ">=2.0.0,<3.0", False, "E boom", "") |
| 100 | + self.assertIn("users are not affected until the specifier widens", outside) |
| 101 | + |
| 102 | + |
| 103 | +class PyprojectTests(unittest.TestCase): |
| 104 | + def test_reads_dependencies_and_extras(self) -> None: |
| 105 | + self.assertEqual(uc.read_declared(PYPROJECT, "temporalio", None), ">=1.32.0") |
| 106 | + self.assertEqual( |
| 107 | + uc.read_declared(PYPROJECT, "openai-agents", "openai-agents"), ">=0.20.0" |
| 108 | + ) |
| 109 | + self.assertEqual(AGENTS.extra, "openai-agents") |
| 110 | + self.assertIsNone(TEMPORALIO.extra) |
| 111 | + |
| 112 | + def test_replace_touches_only_the_named_declaration(self) -> None: |
| 113 | + out = uc.replace_declared( |
| 114 | + PYPROJECT, "openai-agents", "openai-agents", ">=0.23.1,<0.24.0" |
| 115 | + ) |
| 116 | + self.assertIn('"openai-agents>=0.23.1,<0.24.0",', out) |
| 117 | + self.assertIn('"temporalio[openai-agents]>=1.32.0",', out) |
| 118 | + self.assertIn('"temporalio>=1.32.0",', out) |
| 119 | + self.assertEqual(len(out.splitlines()), len(PYPROJECT.splitlines())) |
| 120 | + out2 = uc.replace_declared(PYPROJECT, "temporalio", None, ">=1.34.0") |
| 121 | + self.assertIn('"temporalio>=1.34.0",', out2) |
| 122 | + self.assertIn('"temporalio[openai-agents]>=1.32.0",', out2) |
| 123 | + |
| 124 | + |
| 125 | +class DedupeTests(unittest.TestCase): |
| 126 | + def test_names(self) -> None: |
| 127 | + self.assertEqual( |
| 128 | + uc.branch_name(AGENTS, "0.23.1"), "upstream/temporal-openai-agents-0.23.1" |
| 129 | + ) |
| 130 | + self.assertEqual( |
| 131 | + uc.pr_title(MCP, "3.1.0"), "fix(keycardai-mcp): support mcp 3.1.0" |
| 132 | + ) |
| 133 | + self.assertEqual( |
| 134 | + uc.issue_title(FASTMCP, "4.1.0"), "fastmcp 4.1.0 breaks keycardai-fastmcp" |
| 135 | + ) |
| 136 | + self.assertEqual(AGENTS.key, "temporal:openai-agents") |
| 137 | + |
| 138 | + def test_pick_entry_by_key_or_unique_package(self) -> None: |
| 139 | + entries = [FASTMCP, MCP, TEMPORALIO, AGENTS] |
| 140 | + self.assertIs(uc.pick_entry(entries, "fastmcp"), FASTMCP) |
| 141 | + self.assertIs(uc.pick_entry(entries, "temporal:openai-agents"), AGENTS) |
| 142 | + with self.assertRaises(SystemExit): |
| 143 | + uc.pick_entry(entries, "temporal") |
| 144 | + with self.assertRaises(SystemExit): |
| 145 | + uc.pick_entry(entries, "langchain") |
| 146 | + |
| 147 | + def test_exact_title_match_only(self) -> None: |
| 148 | + items = [ |
| 149 | + {"number": 7, "title": "fastmcp 4.1.0 breaks keycardai-fastmcp"}, |
| 150 | + {"number": 8, "title": "fastmcp 4.1.1 breaks keycardai-fastmcp"}, |
| 151 | + ] |
| 152 | + self.assertEqual( |
| 153 | + uc.find_by_title(items, "fastmcp 4.1.0 breaks keycardai-fastmcp"), 7 |
| 154 | + ) |
| 155 | + self.assertIsNone( |
| 156 | + uc.find_by_title(items, "fastmcp 4.2.0 breaks keycardai-fastmcp") |
| 157 | + ) |
| 158 | + |
| 159 | + def test_failed_differently(self) -> None: |
| 160 | + body = uc.issue_body( |
| 161 | + FASTMCP, "4.1.0", ">=3.1.0", True, "E ImportError: x", "" |
| 162 | + ) |
| 163 | + self.assertFalse(uc.failed_differently(body, "E ImportError: x")) |
| 164 | + self.assertTrue(uc.failed_differently(body, "E TypeError: y")) |
| 165 | + |
| 166 | + def test_already_tested(self) -> None: |
| 167 | + state = {"fastmcp:fastmcp": {"version": "4.1.0", "run": "r"}} |
| 168 | + self.assertTrue(uc.already_tested(state, FASTMCP, "4.1.0")) |
| 169 | + self.assertFalse(uc.already_tested(state, FASTMCP, "4.1.1")) |
| 170 | + self.assertFalse(uc.already_tested(state, MCP, "4.1.0")) |
| 171 | + |
| 172 | + |
| 173 | +class StateWriteTests(unittest.TestCase): |
| 174 | + def test_merge_by_key_keeps_the_other_entries(self) -> None: |
| 175 | + start = {"mcp:mcp": {"version": "2.3.0", "run": "r1"}} |
| 176 | + a = uc.merge_state(start, "fastmcp:fastmcp", {"version": "4.1.0", "run": "r2"}) |
| 177 | + b = uc.merge_state( |
| 178 | + start, "temporal:temporalio", {"version": "1.34.0", "run": "r3"} |
| 179 | + ) |
| 180 | + self.assertEqual(set(a), {"mcp:mcp", "fastmcp:fastmcp"}) |
| 181 | + self.assertEqual(set(b), {"mcp:mcp", "temporal:temporalio"}) |
| 182 | + self.assertEqual(start, {"mcp:mcp": {"version": "2.3.0", "run": "r1"}}) |
| 183 | + |
| 184 | + def test_rejected_push_rereads_the_tip_and_retries_once(self) -> None: |
| 185 | + tips = iter([{}, {"mcp:mcp": {"version": "2.3.0", "run": "r1"}}]) |
| 186 | + results = iter([False, True]) |
| 187 | + writes: list[dict] = [] |
| 188 | + sleeps: list[float] = [] |
| 189 | + |
| 190 | + def write_once(repo_root: str, branch: str, state: dict, message: str) -> bool: |
| 191 | + writes.append(state) |
| 192 | + return next(results) |
| 193 | + |
| 194 | + final = uc.record_state( |
| 195 | + "/repo", |
| 196 | + "upstream-compat-state", |
| 197 | + "fastmcp:fastmcp", |
| 198 | + {"version": "4.1.0", "run": "r2"}, |
| 199 | + "msg", |
| 200 | + read=lambda root, branch: next(tips), |
| 201 | + write_once=write_once, |
| 202 | + sleep=sleeps.append, |
| 203 | + ) |
| 204 | + self.assertEqual(len(writes), 2) |
| 205 | + self.assertEqual(set(final), {"mcp:mcp", "fastmcp:fastmcp"}) |
| 206 | + self.assertEqual(len(sleeps), 1) |
| 207 | + |
| 208 | + def test_exhausted_attempts_fail_as_a_state_write(self) -> None: |
| 209 | + with self.assertRaises(SystemExit) as ctx: |
| 210 | + uc.record_state( |
| 211 | + "/repo", |
| 212 | + "upstream-compat-state", |
| 213 | + "mcp:mcp", |
| 214 | + {"version": "2.3.0", "run": ""}, |
| 215 | + "msg", |
| 216 | + attempts=3, |
| 217 | + read=lambda root, branch: {}, |
| 218 | + write_once=lambda *a: False, |
| 219 | + sleep=lambda s: None, |
| 220 | + ) |
| 221 | + self.assertIn("state write failed after 3 attempts", str(ctx.exception)) |
| 222 | + |
| 223 | + |
| 224 | +if __name__ == "__main__": |
| 225 | + unittest.main() |
0 commit comments