|
| 1 | +import typing as t |
1 | 2 | from unittest import mock |
2 | 3 |
|
3 | 4 | import pytest |
@@ -84,3 +85,89 @@ def example(self): |
84 | 85 | res = client.get("/another/example") |
85 | 86 | assert res.status_code == 200 |
86 | 87 | assert res.content == b'"Create Response Works"' |
| 88 | + |
| 89 | + |
| 90 | +def test_same_controller_two_apis_works(): |
| 91 | + @api_controller("/ping") |
| 92 | + class P: |
| 93 | + @http_get("") |
| 94 | + def ping(self): |
| 95 | + return {"ok": True} |
| 96 | + |
| 97 | + a = NinjaExtraAPI(urls_namespace="a") |
| 98 | + b = NinjaExtraAPI(urls_namespace="b") |
| 99 | + |
| 100 | + a.register_controllers(P) |
| 101 | + b.register_controllers(P) # triggers clone path |
| 102 | + |
| 103 | + assert TestClient(a).get("/ping").json() == {"ok": True} |
| 104 | + assert TestClient(b).get("/ping").json() == {"ok": True} |
| 105 | + |
| 106 | + |
| 107 | +def test_openapi_schema_params_are_correct_on_two_apis(): |
| 108 | + @api_controller("/") |
| 109 | + class ItemsController: |
| 110 | + @http_get("/items_1") |
| 111 | + def items_1(self, ordering: t.Optional[str] = None): |
| 112 | + return {"ok": True} |
| 113 | + |
| 114 | + # Two independent API instances |
| 115 | + api_a = NinjaExtraAPI(title="A") |
| 116 | + api_b = NinjaExtraAPI(title="B") |
| 117 | + |
| 118 | + api_a.register_controllers(ItemsController) |
| 119 | + api_b.register_controllers(ItemsController) |
| 120 | + |
| 121 | + expected_params = [ |
| 122 | + { |
| 123 | + "in": "query", |
| 124 | + "name": "ordering", |
| 125 | + "required": False, |
| 126 | + "schema": { |
| 127 | + "anyOf": [{"type": "string"}, {"type": "null"}], |
| 128 | + "title": "Ordering", |
| 129 | + }, |
| 130 | + } |
| 131 | + ] |
| 132 | + |
| 133 | + # Check API A schema |
| 134 | + schema_a = api_a.get_openapi_schema() |
| 135 | + op_a = schema_a["paths"]["/api/items_1"]["get"] |
| 136 | + assert op_a["parameters"] == expected_params |
| 137 | + |
| 138 | + # Check API B schema |
| 139 | + schema_b = api_b.get_openapi_schema() |
| 140 | + op_b = schema_b["paths"]["/api/items_1"]["get"] |
| 141 | + assert op_b["parameters"] == expected_params |
| 142 | + |
| 143 | + # (Optional) also confirm the route actually works on both APIs |
| 144 | + ca = TestClient(api_a) |
| 145 | + cb = TestClient(api_b) |
| 146 | + assert ca.get("/items_1").status_code == 200 |
| 147 | + assert cb.get("/items_1").status_code == 200 |
| 148 | + |
| 149 | + |
| 150 | +def test_clone_is_cached_per_api_not_recreated(): |
| 151 | + """Register the same original class twice on the same API -> reuse cached clone, no new routers.""" |
| 152 | + |
| 153 | + @api_controller("/x") |
| 154 | + class X: |
| 155 | + @http_get("") |
| 156 | + def ok(self): |
| 157 | + return {"ok": True} |
| 158 | + |
| 159 | + a = NinjaExtraAPI(urls_namespace="a") |
| 160 | + b = NinjaExtraAPI(urls_namespace="b") |
| 161 | + |
| 162 | + # Mount on A (original) |
| 163 | + a.register_controllers(X) |
| 164 | + # Mount on B (clone) |
| 165 | + b.register_controllers(X) |
| 166 | + # Re-register same original on B (should reuse the cached clone; no new routers added) |
| 167 | + before = len(b._routers) |
| 168 | + b.register_controllers(X) |
| 169 | + after = len(b._routers) |
| 170 | + assert before == after |
| 171 | + |
| 172 | + # Optional: ensure path exists and works |
| 173 | + assert TestClient(b).get("/x").json() == {"ok": True} |
0 commit comments