feat(fc3b): /api/credentials blueprint (CRUD + X-Extension-Key auth)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import pytest
|
||||
|
||||
from backend.app import create_app
|
||||
from backend.app.models import AppSetting
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
_NETSCAPE = (
|
||||
"# Netscape HTTP Cookie File\n"
|
||||
".patreon.com\tTRUE\t/\tTRUE\t1700000000\tsession_id\tabc\n"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def app():
|
||||
return create_app()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(app):
|
||||
async with app.test_client() as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def ext_key(db):
|
||||
# Seed an extension API key for tests that want to assert the
|
||||
# X-Extension-Key path explicitly.
|
||||
db.add(AppSetting(key="extension_api_key", value="test-ext-key"))
|
||||
await db.commit()
|
||||
return "test-ext-key"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_empty(client):
|
||||
resp = await client.get("/api/credentials")
|
||||
assert resp.status_code == 200
|
||||
assert await resp.get_json() == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_and_get(client):
|
||||
resp = await client.post("/api/credentials", json={
|
||||
"platform": "patreon",
|
||||
"credential_type": "cookies",
|
||||
"data": _NETSCAPE,
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
body = await resp.get_json()
|
||||
assert body["platform"] == "patreon"
|
||||
assert body["credential_type"] == "cookies"
|
||||
assert "data" not in body # never echoed
|
||||
assert "encrypted_blob" not in body
|
||||
|
||||
one = await client.get("/api/credentials/patreon")
|
||||
assert one.status_code == 200
|
||||
assert (await one.get_json())["platform"] == "patreon"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_updates_existing(client):
|
||||
a = await client.post("/api/credentials", json={
|
||||
"platform": "patreon", "credential_type": "cookies", "data": _NETSCAPE,
|
||||
})
|
||||
assert a.status_code == 201
|
||||
b = await client.post("/api/credentials", json={
|
||||
"platform": "patreon", "credential_type": "cookies", "data": _NETSCAPE + "x",
|
||||
})
|
||||
# On update, our convention is 200 not 201
|
||||
assert b.status_code == 200
|
||||
listing = await (await client.get("/api/credentials")).get_json()
|
||||
assert len(listing) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unknown_platform_400(client):
|
||||
resp = await client.post("/api/credentials", json={
|
||||
"platform": "fanbox", "credential_type": "cookies", "data": "x",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert (await resp.get_json())["error"] == "unknown_platform"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrong_auth_type_400(client):
|
||||
resp = await client.post("/api/credentials", json={
|
||||
"platform": "discord", "credential_type": "cookies", "data": "x",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
body = await resp.get_json()
|
||||
assert body["error"] == "wrong_auth_type"
|
||||
assert body["expected"] == "token"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_data_400(client):
|
||||
resp = await client.post("/api/credentials", json={
|
||||
"platform": "patreon", "credential_type": "cookies", "data": " ",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert (await resp.get_json())["error"] == "empty_data"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_body_400(client):
|
||||
resp = await client.post("/api/credentials", json=[1, 2, 3])
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_404(client):
|
||||
resp = await client.get("/api/credentials/patreon")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_204_then_404(client):
|
||||
await client.post("/api/credentials", json={
|
||||
"platform": "patreon", "credential_type": "cookies", "data": _NETSCAPE,
|
||||
})
|
||||
dele = await client.delete("/api/credentials/patreon")
|
||||
assert dele.status_code == 204
|
||||
nope = await client.delete("/api/credentials/patreon")
|
||||
assert nope.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extension_key_correct_accepts(client, ext_key):
|
||||
resp = await client.post(
|
||||
"/api/credentials",
|
||||
json={"platform": "patreon", "credential_type": "cookies", "data": _NETSCAPE},
|
||||
headers={"X-Extension-Key": ext_key},
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extension_key_wrong_rejects(client, ext_key):
|
||||
resp = await client.post(
|
||||
"/api/credentials",
|
||||
json={"platform": "patreon", "credential_type": "cookies", "data": _NETSCAPE},
|
||||
headers={"X-Extension-Key": "WRONG"},
|
||||
)
|
||||
assert resp.status_code == 401
|
||||
Reference in New Issue
Block a user