|
1 | | -"""Tests for the Middleware base class and function adapters.""" |
| 1 | +"""Tests for the Middleware base class, function adapters, and priority ordering.""" |
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
|
8 | 8 |
|
9 | 9 | from apcore.context import Context |
10 | 10 | from apcore.middleware import AfterMiddleware, BeforeMiddleware, Middleware |
| 11 | +from apcore.middleware.manager import MiddlewareManager |
11 | 12 |
|
12 | 13 |
|
13 | 14 | # === Middleware Base Class === |
@@ -169,3 +170,129 @@ def test_callback_receives_correct_args(self) -> None: |
169 | 170 | ctx = MagicMock(spec=Context) |
170 | 171 | am.after("mod.id", {"k": "v"}, {"out": 1}, ctx) |
171 | 172 | spy.assert_called_once_with("mod.id", {"k": "v"}, {"out": 1}, ctx) |
| 173 | + |
| 174 | + |
| 175 | +# === Middleware Priority Ordering === |
| 176 | + |
| 177 | + |
| 178 | +class TestMiddlewarePriority: |
| 179 | + """Tests for middleware priority ordering in MiddlewareManager.""" |
| 180 | + |
| 181 | + def test_default_priority_is_zero(self) -> None: |
| 182 | + """Middleware instances default to priority 0.""" |
| 183 | + mw = Middleware() |
| 184 | + assert mw.priority == 0 |
| 185 | + |
| 186 | + def test_custom_priority(self) -> None: |
| 187 | + """Middleware accepts a custom priority via constructor.""" |
| 188 | + mw = Middleware(priority=500) |
| 189 | + assert mw.priority == 500 |
| 190 | + |
| 191 | + def test_higher_priority_executes_first(self) -> None: |
| 192 | + """Middlewares with higher priority appear earlier in the list.""" |
| 193 | + manager = MiddlewareManager() |
| 194 | + low = Middleware(priority=100) |
| 195 | + high = Middleware(priority=900) |
| 196 | + mid = Middleware(priority=500) |
| 197 | + |
| 198 | + manager.add(low) |
| 199 | + manager.add(high) |
| 200 | + manager.add(mid) |
| 201 | + |
| 202 | + snapshot = manager.snapshot() |
| 203 | + assert snapshot == [high, mid, low] |
| 204 | + |
| 205 | + def test_equal_priority_preserves_registration_order(self) -> None: |
| 206 | + """Middlewares with the same priority are ordered by registration time.""" |
| 207 | + manager = MiddlewareManager() |
| 208 | + first = Middleware(priority=100) |
| 209 | + second = Middleware(priority=100) |
| 210 | + third = Middleware(priority=100) |
| 211 | + |
| 212 | + manager.add(first) |
| 213 | + manager.add(second) |
| 214 | + manager.add(third) |
| 215 | + |
| 216 | + snapshot = manager.snapshot() |
| 217 | + assert snapshot == [first, second, third] |
| 218 | + |
| 219 | + def test_mixed_priorities_with_ties(self) -> None: |
| 220 | + """Mixed priorities sort correctly with registration-order tiebreaking.""" |
| 221 | + manager = MiddlewareManager() |
| 222 | + a = Middleware(priority=500) |
| 223 | + b = Middleware(priority=100) |
| 224 | + c = Middleware(priority=500) |
| 225 | + d = Middleware(priority=1000) |
| 226 | + e = Middleware(priority=0) |
| 227 | + |
| 228 | + manager.add(a) |
| 229 | + manager.add(b) |
| 230 | + manager.add(c) |
| 231 | + manager.add(d) |
| 232 | + manager.add(e) |
| 233 | + |
| 234 | + snapshot = manager.snapshot() |
| 235 | + assert snapshot == [d, a, c, b, e] |
| 236 | + |
| 237 | + def test_default_priority_backward_compatible(self) -> None: |
| 238 | + """Middlewares without explicit priority still work (default 0).""" |
| 239 | + manager = MiddlewareManager() |
| 240 | + mw1 = Middleware() |
| 241 | + mw2 = Middleware() |
| 242 | + mw3 = Middleware() |
| 243 | + |
| 244 | + manager.add(mw1) |
| 245 | + manager.add(mw2) |
| 246 | + manager.add(mw3) |
| 247 | + |
| 248 | + snapshot = manager.snapshot() |
| 249 | + assert snapshot == [mw1, mw2, mw3] |
| 250 | + |
| 251 | + def test_subclass_without_super_init_defaults_to_zero(self) -> None: |
| 252 | + """Subclasses that don't call super().__init__() still have priority 0.""" |
| 253 | + |
| 254 | + class CustomMiddleware(Middleware): |
| 255 | + def __init__(self) -> None: |
| 256 | + self.custom_field = "hello" |
| 257 | + |
| 258 | + mw = CustomMiddleware() |
| 259 | + assert mw.priority == 0 |
| 260 | + |
| 261 | + def test_remove_preserves_priority_order(self) -> None: |
| 262 | + """Removing a middleware preserves the priority-sorted order.""" |
| 263 | + manager = MiddlewareManager() |
| 264 | + low = Middleware(priority=100) |
| 265 | + high = Middleware(priority=900) |
| 266 | + mid = Middleware(priority=500) |
| 267 | + |
| 268 | + manager.add(low) |
| 269 | + manager.add(high) |
| 270 | + manager.add(mid) |
| 271 | + manager.remove(mid) |
| 272 | + |
| 273 | + snapshot = manager.snapshot() |
| 274 | + assert snapshot == [high, low] |
| 275 | + |
| 276 | + def test_priority_below_zero_raises_value_error(self) -> None: |
| 277 | + """Priority below 0 raises ValueError.""" |
| 278 | + import pytest |
| 279 | + |
| 280 | + with pytest.raises(ValueError, match="priority must be between 0 and 1000"): |
| 281 | + Middleware(priority=-1) |
| 282 | + |
| 283 | + def test_priority_above_1000_raises_value_error(self) -> None: |
| 284 | + """Priority above 1000 raises ValueError.""" |
| 285 | + import pytest |
| 286 | + |
| 287 | + with pytest.raises(ValueError, match="priority must be between 0 and 1000"): |
| 288 | + Middleware(priority=1001) |
| 289 | + |
| 290 | + def test_before_middleware_accepts_priority(self) -> None: |
| 291 | + """BeforeMiddleware forwards priority to the base class.""" |
| 292 | + bm = BeforeMiddleware(lambda mid, inp, ctx: None, priority=42) |
| 293 | + assert bm.priority == 42 |
| 294 | + |
| 295 | + def test_after_middleware_accepts_priority(self) -> None: |
| 296 | + """AfterMiddleware forwards priority to the base class.""" |
| 297 | + am = AfterMiddleware(lambda mid, inp, out, ctx: None, priority=99) |
| 298 | + assert am.priority == 99 |
0 commit comments