G Rohith Yadav
Phase 1 + 2 + 2.5 complete: simulator, FastAPI server, seedable env (83 tests passing)
b914800 | """Unit tests for SimulatedNginx and SimulatedExpress.""" | |
| from __future__ import annotations | |
| import pytest | |
| from sre_arena_env.server.simulator.sim_nginx import SimulatedNginx | |
| # ββ helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _req( | |
| ip: str, | |
| path: str = "/", | |
| method: str = "GET", | |
| body: dict | None = None, | |
| headers: dict | None = None, | |
| ) -> dict: | |
| return { | |
| "ip": ip, | |
| "path": path, | |
| "method": method, | |
| "body": body or {}, | |
| "headers": headers or {}, | |
| "is_malicious": False, | |
| } | |
| # ββ SimulatedNginx ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestDenyAllow: | |
| def test_deny_blocks_matching_ip(self): | |
| nginx = SimulatedNginx() | |
| assert nginx.add_rule("deny 10.0.0.1;") is True | |
| assert nginx.process_request(_req("10.0.0.1")) == 403 | |
| def test_deny_does_not_block_other_ip(self): | |
| nginx = SimulatedNginx() | |
| nginx.add_rule("deny 10.0.0.1;") | |
| assert nginx.process_request(_req("10.0.0.2")) == 200 | |
| def test_deny_cidr_blocks_matching(self): | |
| nginx = SimulatedNginx() | |
| nginx.add_rule("deny 10.0.0.0/24;") | |
| assert nginx.process_request(_req("10.0.0.55")) == 403 | |
| def test_deny_cidr_does_not_block_outside(self): | |
| nginx = SimulatedNginx() | |
| nginx.add_rule("deny 10.0.0.0/24;") | |
| assert nginx.process_request(_req("10.0.1.1")) == 200 | |
| def test_allow_before_deny_permits_specific_ip(self): | |
| nginx = SimulatedNginx() | |
| nginx.add_rule("allow 10.0.0.5;") | |
| nginx.add_rule("deny 10.0.0.0/24;") | |
| assert nginx.process_request(_req("10.0.0.5")) == 200 | |
| assert nginx.process_request(_req("10.0.0.9")) == 403 | |
| def test_no_rules_allows_all(self): | |
| nginx = SimulatedNginx() | |
| assert nginx.process_request(_req("1.2.3.4")) == 200 | |
| def test_invalid_rule_rejected_returns_false(self): | |
| nginx = SimulatedNginx() | |
| assert nginx.add_rule("block all traffic please;") is False | |
| def test_invalid_rule_not_added_to_rules_list(self): | |
| nginx = SimulatedNginx() | |
| nginx.add_rule("garbage;") | |
| assert nginx.get_rules() == [] | |
| class TestRateLimit: | |
| def _nginx_with_zone( | |
| self, zone: str = "flood", rate: int = 10, burst: int = 5 | |
| ) -> SimulatedNginx: | |
| nginx = SimulatedNginx() | |
| nginx.add_rule( | |
| f"limit_req_zone $binary_remote_addr zone={zone}:10m rate={rate}r/s;" | |
| ) | |
| nginx.add_rule(f"limit_req zone={zone} burst={burst} nodelay;") | |
| return nginx | |
| def test_burst_allows_up_to_burst_requests(self): | |
| nginx = self._nginx_with_zone(burst=5) | |
| for i in range(5): | |
| assert nginx.process_request(_req("10.0.0.1")) == 200, ( | |
| f"request {i + 1} should be 200" | |
| ) | |
| def test_burst_blocks_on_burst_plus_one(self): | |
| nginx = self._nginx_with_zone(burst=5) | |
| for _ in range(5): | |
| nginx.process_request(_req("10.0.0.1")) | |
| assert nginx.process_request(_req("10.0.0.1")) == 429 | |
| def test_rate_limit_is_per_ip_not_global(self): | |
| nginx = self._nginx_with_zone(burst=3) | |
| for _ in range(3): | |
| nginx.process_request(_req("10.0.0.1")) | |
| assert nginx.process_request(_req("10.0.0.2")) == 200 | |
| def test_reset_step_counters_refills_bucket(self): | |
| nginx = self._nginx_with_zone(burst=2) | |
| for _ in range(2): | |
| nginx.process_request(_req("10.0.0.1")) | |
| assert nginx.process_request(_req("10.0.0.1")) == 429 | |
| nginx.reset_step_counters() | |
| assert nginx.process_request(_req("10.0.0.1")) == 200 | |
| class TestLog: | |
| def test_get_log_tail_returns_n_lines(self): | |
| nginx = SimulatedNginx() | |
| for i in range(10): | |
| nginx.process_request(_req(f"1.2.3.{i}")) | |
| tail = nginx.get_log_tail(3) | |
| assert len(tail.strip().split("\n")) == 3 | |
| def test_get_log_tail_empty_when_no_requests(self): | |
| nginx = SimulatedNginx() | |
| assert nginx.get_log_tail(5) == "" | |
| # ββ SimulatedExpress ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| from sre_arena_env.server.simulator.sim_express import SimulatedExpress # noqa: E402 | |
| class TestExpressMiddleware: | |
| def _req( | |
| self, | |
| path: str = "/api/process", | |
| ip: str = "1.2.3.4", | |
| body: dict | None = None, | |
| headers: dict | None = None, | |
| ) -> dict: | |
| return { | |
| "ip": ip, | |
| "path": path, | |
| "method": "POST", | |
| "body": body or {}, | |
| "headers": headers or {}, | |
| "is_malicious": False, | |
| } | |
| # ββ body-field check ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_body_field_check_blocks_matching_value(self): | |
| ex = SimulatedExpress() | |
| ok = ex.add_middleware( | |
| "/api/process", | |
| "if (req.body.command === 'rm') return res.status(403)", | |
| ) | |
| assert ok is True | |
| assert ex.process_request(self._req(body={"command": "rm"})) == 403 | |
| def test_body_field_check_allows_different_value(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware( | |
| "/api/process", | |
| "if (req.body.command === 'rm') return res.status(403)", | |
| ) | |
| assert ex.process_request(self._req(body={"command": "echo"})) == 200 | |
| def test_body_field_check_allows_missing_field(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware( | |
| "/api/process", | |
| "if (req.body.command === 'rm') return res.status(403)", | |
| ) | |
| assert ex.process_request(self._req(body={})) == 200 | |
| # ββ header check βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_header_check_blocks_when_header_present(self): | |
| ex = SimulatedExpress() | |
| ok = ex.add_middleware( | |
| "/login", | |
| "if (req.headers['X-Forwarded-For']) return res.status(403)", | |
| ) | |
| assert ok is True | |
| assert ( | |
| ex.process_request( | |
| self._req(path="/login", headers={"X-Forwarded-For": "1.2.3.4"}) | |
| ) | |
| == 403 | |
| ) | |
| def test_header_check_allows_when_header_absent(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware( | |
| "/login", | |
| "if (req.headers['X-Forwarded-For']) return res.status(403)", | |
| ) | |
| assert ex.process_request(self._req(path="/login", headers={})) == 200 | |
| # ββ IP check βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_ip_check_blocks_matching_ip(self): | |
| ex = SimulatedExpress() | |
| ok = ex.add_middleware( | |
| "/api/admin", | |
| "if (req.ip === '10.0.0.1') return res.status(403)", | |
| ) | |
| assert ok is True | |
| assert ex.process_request(self._req(path="/api/admin", ip="10.0.0.1")) == 403 | |
| def test_ip_check_allows_different_ip(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware( | |
| "/api/admin", | |
| "if (req.ip === '10.0.0.1') return res.status(403)", | |
| ) | |
| assert ex.process_request(self._req(path="/api/admin", ip="10.0.0.2")) == 200 | |
| # ββ unrecognised middleware βββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_unrecognized_middleware_returns_false(self): | |
| ex = SimulatedExpress() | |
| assert ex.add_middleware("/login", "console.log('hello');") is False | |
| def test_unrecognized_middleware_has_no_effect_on_requests(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware("/login", "console.log('hello');") | |
| assert ex.process_request(self._req(path="/login")) == 200 | |
| def test_unrecognized_count_increments(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware("/login", "bad js 1;") | |
| ex.add_middleware("/login", "bad js 2;") | |
| assert ex.unrecognized_count == 2 | |
| # ββ route scoping βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_middleware_only_applies_to_its_route(self): | |
| ex = SimulatedExpress() | |
| ex.add_middleware( | |
| "/login", | |
| "if (req.body.command === 'rm') return res.status(403)", | |
| ) | |
| assert ( | |
| ex.process_request( | |
| { | |
| "ip": "1.2.3.4", | |
| "path": "/api/data", | |
| "body": {"command": "rm"}, | |
| "headers": {}, | |
| "is_malicious": False, | |
| } | |
| ) | |
| == 200 | |
| ) | |
| def test_invalid_route_rejected(self): | |
| ex = SimulatedExpress() | |
| assert ( | |
| ex.add_middleware( | |
| "/nonexistent", | |
| "if (req.body.x === 'y') return res.status(403)", | |
| ) | |
| is False | |
| ) | |
| def test_get_middleware_summary_returns_dict(self): | |
| ex = SimulatedExpress() | |
| js = "if (req.body.command === 'rm') return res.status(403)" | |
| ex.add_middleware("/api/process", js) | |
| summary = ex.get_middleware_summary() | |
| assert "/api/process" in summary | |
| assert summary["/api/process"] == js | |