"""GC-4/GC-5: WOO_BASE_URL contract tests. GC-4: WOO_BASE_URL is compose-only HTTPS; Settings GET returns it read-only; PUT rejects write attempts with 422. GC-5: FastAPI refuses to start when WOO_BASE_URL is missing or http://. FUND-8: App factory creates fresh instance per test. FUND-9: TestClient context manager manages lifespan. FUND-10: dependency_overrides cleared after every test. """ import pytest from fastapi.testclient import TestClient from app.config import Settings from app.main import create_app class TestWooBaseUrlValidation: """GC-5: FastAPI refuses invalid WOO_BASE_URL at startup.""" def test_refuses_http_url(self) -> None: """GC-5: Settings.validate() raises ValueError for http:// URL.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="http://insecure.example.com", ) with pytest.raises(ValueError, match="(?i)https"): settings.validate() def test_refuses_missing_url(self) -> None: """GC-5: Settings.validate() raises ValueError when woo_base_url is None.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", ) with pytest.raises(ValueError): settings.validate() def test_accepts_https_url(self) -> None: """GC-5: Settings.validate() accepts HTTPS URL.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="https://secure.example.com", ) settings.validate() # Should not raise class TestWooBaseUrlReadOnly: """GC-4: Settings endpoints handle woo_base_url correctly.""" def test_get_settings_returns_woo_base_url(self) -> None: """GC-4: GET /api/v1/settings returns woo_base_url from compose env.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="https://store.example.com", ) app = create_app(settings) with TestClient(app) as client: response = client.get("/api/v1/settings") assert response.status_code == 200 data = response.json() assert data["woo_base_url"] == "https://store.example.com" app.dependency_overrides.clear() def test_put_settings_rejects_woo_base_url(self) -> None: """GC-4: PUT /api/v1/settings rejects woo_base_url field with 422.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="https://store.example.com", ) app = create_app(settings) with TestClient(app) as client: response = client.put( "/api/v1/settings", json={"woo_base_url": "https://hacked.example.com"}, ) assert response.status_code == 422 data = response.json() assert "detail" in data app.dependency_overrides.clear() def test_put_settings_allows_other_fields(self) -> None: """GC-4: PUT /api/v1/settings still accepts other settings fields.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="https://store.example.com", ) app = create_app(settings) with TestClient(app) as client: response = client.put( "/api/v1/settings", json={"woo_create_status": "publish"}, ) assert response.status_code == 200 data = response.json() assert data["woo_create_status"] == "publish" app.dependency_overrides.clear() def test_get_settings_never_returns_app_password(self) -> None: """GC-4: GET /api/v1/settings never returns woo_app_password.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="https://store.example.com", ) app = create_app(settings) with TestClient(app) as client: response = client.get("/api/v1/settings") assert response.status_code == 200 data = response.json() assert "woo_app_password" not in data app.dependency_overrides.clear() def test_put_settings_rejects_consumer_keys(self) -> None: """GC-4: PUT /api/v1/settings rejects consumer key fields with 422.""" settings = Settings( aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk", woo_base_url="https://store.example.com", ) app = create_app(settings) with TestClient(app) as client: response = client.put( "/api/v1/settings", json={ "woo_consumer_key": "ck_test", "woo_consumer_secret": "cs_test", }, ) assert response.status_code == 422 app.dependency_overrides.clear()