"""Contract Cache-Control của tầng static (31/07/2026 — việc C, gate G5.3). Nguyên nhân gốc bug tối 30/07: ``app.js`` bị trình duyệt cache hàng giờ sau deploy → alt không có quỹ đạo cho tới Ctrl+F5. Fix là header ``Cache-Control: no-cache`` trên ``.js``/``.css``/``.html`` — NO-CACHE chứ không phải no-store: vẫn cho revalidate 304, chỉ cấm dùng bản cũ không hỏi. Test đọc FILE THẬT trong ``app/static/`` (không cần pooltool — tầng static không đụng sim), nên nó cũng khoá luôn việc ba file FE còn tồn tại. """ from __future__ import annotations import pytest from fastapi.testclient import TestClient from app import main as app_main @pytest.fixture def client(): return TestClient(app_main.app) @pytest.mark.parametrize("path", ["/", "/app.js", "/style.css"]) def test_static_js_css_html_co_no_cache(client, path): """G5.3: ``curl -I`` một file ``.js`` và ``/`` → ``Cache-Control: no-cache``. ``/`` đi qua nhánh html=True (index.html) — nhánh resolve KHÁC file thẳng, phải kiểm riêng.""" r = client.get(path) assert r.status_code == 200 assert r.headers.get("cache-control") == "no-cache" def test_304_van_mang_no_cache(client): """no-cache sống bằng revalidate: trình duyệt gửi If-None-Match mỗi lần. Nếu response 304 rơi mất header thì một số cache trung gian coi như hết chỉ thị và bug cũ quay lại đúng ở lần thứ hai.""" first = client.get("/app.js") etag = first.headers.get("etag") assert etag, "starlette phải trả ETag để revalidate được" r = client.get("/app.js", headers={"If-None-Match": etag}) assert r.status_code == 304 assert r.headers.get("cache-control") == "no-cache" def test_api_khong_bi_dinh_header_static(client): """Header thuộc TẦNG STATIC, không phải toàn app — /api/health không được lây (nó là JSON động, chính sách cache của nó là chuyện khác).""" r = client.get("/api/health") assert r.status_code == 200 assert r.headers.get("cache-control") != "no-cache"