def967a1a8
Removed the app/client fixtures duplicated across 36 test files (two variants: separate app + client(app), and a self-contained client() that called create_app inline) and the now-unused create_app imports. Both fixtures now live once in conftest.py. test_suggestions_bulk keeps its import (builds the app inline in two tests); test_health drops its local client + unused pytest_asyncio. Net -415 lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
"""CORS preflight + response headers for moz-extension:// + chrome-extension://.
|
|
|
|
Operator-flagged 2026-05-26: extension's first 'Test connection' tap
|
|
returned `NetworkError` because /api/credentials had no OPTIONS handler
|
|
and no Access-Control-Allow-Origin response header. Browser preflight
|
|
failed → fetch blocked.
|
|
|
|
These tests pin the contract: any request from a moz-extension:// or
|
|
chrome-extension:// origin gets the right ACL headers. Plain browser
|
|
requests (no Origin header, or a regular https:// Origin) get nothing
|
|
— we don't want to open CORS up generally.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_extension_preflight_returns_204_with_acl_headers(client):
|
|
resp = await client.options(
|
|
"/api/credentials",
|
|
headers={
|
|
"Origin": "moz-extension://abcd1234-uuid-fake",
|
|
"Access-Control-Request-Method": "GET",
|
|
"Access-Control-Request-Headers": "X-Extension-Key",
|
|
},
|
|
)
|
|
assert resp.status_code == 204
|
|
assert resp.headers["Access-Control-Allow-Origin"] == "moz-extension://abcd1234-uuid-fake"
|
|
assert "OPTIONS" in resp.headers["Access-Control-Allow-Methods"]
|
|
assert "X-Extension-Key" in resp.headers["Access-Control-Allow-Headers"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_extension_get_carries_acl_headers(client):
|
|
# The actual response (post-preflight) also needs ACL headers — the
|
|
# browser checks them again before exposing the response body.
|
|
resp = await client.get(
|
|
"/api/credentials",
|
|
headers={"Origin": "moz-extension://abcd1234-uuid-fake"},
|
|
)
|
|
# 200 with empty list (no creds seeded) — what matters here is the
|
|
# CORS header is present.
|
|
assert resp.headers["Access-Control-Allow-Origin"] == "moz-extension://abcd1234-uuid-fake"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_chrome_extension_origin_also_allowed(client):
|
|
resp = await client.options(
|
|
"/api/credentials",
|
|
headers={
|
|
"Origin": "chrome-extension://abcd1234-uuid-fake",
|
|
"Access-Control-Request-Method": "POST",
|
|
},
|
|
)
|
|
assert resp.status_code == 204
|
|
assert resp.headers["Access-Control-Allow-Origin"] == "chrome-extension://abcd1234-uuid-fake"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_normal_browser_request_gets_no_cors_headers(client):
|
|
# A regular browser tab (https://example.com / file:// / no Origin
|
|
# at all) should NOT get any Access-Control-Allow-* — the extension
|
|
# whitelist is intentionally narrow.
|
|
resp = await client.get(
|
|
"/api/credentials",
|
|
headers={"Origin": "https://evil.example.com"},
|
|
)
|
|
assert "Access-Control-Allow-Origin" not in resp.headers
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_origin_header_unaffected(client):
|
|
# Same-origin requests (no Origin header) — unaffected.
|
|
resp = await client.get("/api/credentials")
|
|
assert "Access-Control-Allow-Origin" not in resp.headers
|