-
Notifications
You must be signed in to change notification settings - Fork 13
feat: replace legacy /update_policy* 307 redirects with direct gated calls (PER-15246) #320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dshoen619
merged 12 commits into
main
from
david/per-15246-pdp-replace-legacy-update_policy-307-redirects-with-direct
Jul 9, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f23883e
feat: replace legacy /update_policy* 307 redirects with direct gated …
dshoen619 d0875d6
test: address Copilot review on legacy update-route tests
dshoen619 ba8cef6
fix: restore canonical trigger log lines on legacy alias routes
dshoen619 9e076a1
test: fix CI collection break and #317 status-code collision (review)
dshoen619 504d64d
ci: pin aiohttp<3.14 in dev requirements (mirrors #317)
dshoen619 25dbeb3
Merge branch 'main' into david/per-15246-pdp-replace-legacy-update_po…
dshoen619 b6a486d
test: correct two comment inaccuracies flagged in review
dshoen619 8280204
ci: drop aiohttp<3.14 dev pin, superseded by conftest shim from main
dshoen619 3c5e50f
Merge branch 'main' into david/per-15246-pdp-replace-legacy-update_po…
dshoen619 6e73d65
Merge branch 'main' into david/per-15246-pdp-replace-legacy-update_po…
dshoen619 ce6c609
Merge branch 'main' into david/per-15246-pdp-replace-legacy-update_po…
dshoen619 65e1b84
docs: note deliberate canonical parity on unguarded policy_updater (r…
dshoen619 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| from unittest.mock import AsyncMock | ||
|
|
||
| import pytest | ||
| from fastapi.testclient import TestClient | ||
| from horizon.config import sidecar_config | ||
|
|
||
| # Basename import (not horizon.tests.*): CI installs the package non-editably, so | ||
| # the wheel ships no tests/ package; pytest's prepend import mode puts this | ||
| # directory on sys.path and imports test modules by basename. | ||
| from test_enforcer_api import MockPermitPDP | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def pdp() -> MockPermitPDP: | ||
| # Fresh instance per test: keeps these tests independent of the shared | ||
| # module-level `sidecar` singleton in test_enforcer_api (cheap defensive | ||
| # isolation; monkeypatch already reverts this file's mutations at teardown). | ||
| return MockPermitPDP() | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def auth() -> dict[str, str]: | ||
| return {"authorization": f"Bearer {sidecar_config.API_KEY}"} | ||
|
|
||
|
|
||
| def test_update_policy_triggers_updater(pdp: MockPermitPDP, auth: dict[str, str], monkeypatch): | ||
| trigger = AsyncMock() | ||
| monkeypatch.setattr(pdp._opal.policy_updater, "trigger_update_policy", trigger) | ||
|
|
||
| response = TestClient(pdp._app).post("/update_policy", headers=auth, follow_redirects=False) | ||
|
|
||
| assert response.status_code == 200 | ||
| assert response.json() == {"status": "ok"} | ||
| trigger.assert_awaited_once_with(force_full_update=True) | ||
|
|
||
|
|
||
| def test_update_policy_rejects_unauthenticated(pdp: MockPermitPDP, monkeypatch): | ||
| trigger = AsyncMock() | ||
| monkeypatch.setattr(pdp._opal.policy_updater, "trigger_update_policy", trigger) | ||
| client = TestClient(pdp._app) | ||
|
|
||
| # A missing header is rejected before the handler runs: 422 while the | ||
| # `authorization` param has no default (FastAPI required-param validation), | ||
| # 401 once enforce_pdp_token gains `= None` (PER-15244 / #317). Accept both | ||
| # so this survives either merge order, while still failing on an accidental | ||
| # 200 (auth bypass) or 500. An invalid token is 401 in both regimes, and the | ||
| # updater must never run for an unauthenticated caller either way. | ||
| missing = client.post("/update_policy", follow_redirects=False) | ||
| assert missing.status_code in (401, 422) | ||
| invalid = client.post("/update_policy", headers={"authorization": "Bearer wrong"}, follow_redirects=False) | ||
| assert invalid.status_code == 401 | ||
| trigger.assert_not_awaited() | ||
|
|
||
|
|
||
| def test_update_policy_data_triggers_updater(pdp: MockPermitPDP, auth: dict[str, str], monkeypatch): | ||
| get_base = AsyncMock() | ||
| monkeypatch.setattr(pdp._opal.data_updater, "get_base_policy_data", get_base) | ||
|
|
||
| response = TestClient(pdp._app).post("/update_policy_data", headers=auth, follow_redirects=False) | ||
|
|
||
| assert response.status_code == 200 | ||
| assert response.json() == {"status": "ok"} | ||
| get_base.assert_awaited_once_with(data_fetch_reason="request from sdk (legacy alias)") | ||
|
|
||
|
|
||
| def test_update_policy_data_returns_503_when_updater_disabled(pdp: MockPermitPDP, auth: dict[str, str], monkeypatch): | ||
| monkeypatch.setattr(pdp._opal, "data_updater", None) | ||
|
|
||
| response = TestClient(pdp._app).post("/update_policy_data", headers=auth, follow_redirects=False) | ||
|
|
||
| assert response.status_code == 503 | ||
| # Exact parity with the canonical data route (opal_client/data/api.py). | ||
| assert response.json()["detail"] == "Data Updater is currently disabled. Dynamic data updates are not available." | ||
|
|
||
|
|
||
| def test_update_policy_data_rejects_unauthenticated(pdp: MockPermitPDP, monkeypatch): | ||
| get_base = AsyncMock() | ||
| monkeypatch.setattr(pdp._opal.data_updater, "get_base_policy_data", get_base) | ||
| client = TestClient(pdp._app) | ||
|
|
||
| # See test_update_policy_rejects_unauthenticated for the 401/422 dual regime. | ||
| missing = client.post("/update_policy_data", follow_redirects=False) | ||
| assert missing.status_code in (401, 422) | ||
| invalid = client.post("/update_policy_data", headers={"authorization": "Bearer wrong"}, follow_redirects=False) | ||
| assert invalid.status_code == 401 | ||
| get_base.assert_not_awaited() | ||
|
|
||
|
|
||
| def test_legacy_routes_do_not_redirect(pdp: MockPermitPDP, auth: dict[str, str], monkeypatch): | ||
| # Lock in the fix: the aliases must call the updaters directly, never redirect | ||
| # to the canonical routes. Clients drop Authorization on redirects, and | ||
| # httpx's is_redirect flags any 3xx — stronger than asserting != 307 alone. | ||
| monkeypatch.setattr(pdp._opal.policy_updater, "trigger_update_policy", AsyncMock()) | ||
| monkeypatch.setattr(pdp._opal.data_updater, "get_base_policy_data", AsyncMock()) | ||
| client = TestClient(pdp._app) | ||
|
|
||
| assert not client.post("/update_policy", headers=auth, follow_redirects=False).is_redirect | ||
| assert not client.post("/update_policy_data", headers=auth, follow_redirects=False).is_redirect |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.