agnes-the-ai-analyst/tests/test_metadata_api.py
ZdenekSrotyr 1824b9dd9c
feat(admin): #108 M1 — BigQuery table registration in UI + CLI (#119)
Issue #108 Milestone 1. Adds BigQuery table registration via /admin/tables UI and `da admin register-table` CLI without hand-editing table_registry. POST /api/admin/register-table/precheck for round-trip validation. --dry-run flag on CLI. Audit-log entries on register/update/unregister. PUT /api/admin/registry/{id} now preserves registered_at (closes #130).
2026-04-29 13:18:31 +02:00

177 lines
6.4 KiB
Python

"""Tests for admin metadata API — column metadata CRUD and push."""
import pytest
def _auth(token):
return {"Authorization": f"Bearer {token}"}
def _register_table(c, token, table_name="test_table"):
"""Helper to register a table in the registry."""
resp = c.post(
"/api/admin/register-table",
json={"name": table_name, "source_type": "keboola", "bucket": "in.c-test",
"source_table": table_name, "query_mode": "local"},
headers=_auth(token),
)
return resp.json().get("id", table_name.lower())
class TestGetMetadata:
def test_get_metadata_empty(self, seeded_app):
c = seeded_app["client"]
token = seeded_app["admin_token"]
resp = c.get("/api/admin/metadata/some_table", headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
assert data["table_id"] == "some_table"
assert "columns" in data
assert isinstance(data["columns"], list)
def test_get_metadata_requires_auth(self, seeded_app):
c = seeded_app["client"]
resp = c.get("/api/admin/metadata/some_table")
assert resp.status_code == 401
def test_get_metadata_analyst_allowed(self, seeded_app):
"""GET metadata is allowed for authenticated users (not admin-only)."""
c = seeded_app["client"]
token = seeded_app["analyst_token"]
resp = c.get("/api/admin/metadata/some_table", headers=_auth(token))
assert resp.status_code == 200
class TestSaveMetadata:
def test_save_column_metadata_as_admin(self, seeded_app):
c = seeded_app["client"]
token = seeded_app["admin_token"]
table_id = _register_table(c, token, "orders_meta")
resp = c.post(
f"/api/admin/metadata/{table_id}",
json={
"columns": [
{"column_name": "id", "basetype": "INTEGER", "description": "Primary key", "confidence": "manual"},
{"column_name": "name", "basetype": "VARCHAR", "description": "Customer name"},
]
},
headers=_auth(token),
)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "ok"
assert data["count"] == 2
def test_save_metadata_analyst_gets_403(self, seeded_app):
c = seeded_app["client"]
token = seeded_app["analyst_token"]
resp = c.post(
"/api/admin/metadata/some_table",
json={"columns": [{"column_name": "id", "basetype": "INTEGER"}]},
headers=_auth(token),
)
assert resp.status_code == 403
def test_save_metadata_requires_auth(self, seeded_app):
c = seeded_app["client"]
resp = c.post(
"/api/admin/metadata/some_table",
json={"columns": [{"column_name": "id"}]},
)
assert resp.status_code == 401
def test_save_metadata_missing_columns_returns_422(self, seeded_app):
c = seeded_app["client"]
token = seeded_app["admin_token"]
resp = c.post(
"/api/admin/metadata/some_table",
json={}, # missing 'columns'
headers=_auth(token),
)
assert resp.status_code == 422
def test_save_then_get_metadata(self, seeded_app):
"""Save metadata then verify it can be retrieved."""
c = seeded_app["client"]
token = seeded_app["admin_token"]
table_id = _register_table(c, token, "round_trip_table")
c.post(
f"/api/admin/metadata/{table_id}",
json={
"columns": [
{"column_name": "amount", "basetype": "DECIMAL", "description": "Order amount"},
]
},
headers=_auth(token),
)
resp = c.get(f"/api/admin/metadata/{table_id}", headers=_auth(token))
assert resp.status_code == 200
columns = resp.json()["columns"]
assert len(columns) >= 1
col_names = [c_["column_name"] for c_ in columns]
assert "amount" in col_names
class TestPushMetadata:
def test_push_metadata_table_not_found_returns_404(self, seeded_app):
c = seeded_app["client"]
token = seeded_app["admin_token"]
resp = c.post("/api/admin/metadata/nonexistent_table/push", headers=_auth(token))
assert resp.status_code == 404
def test_push_metadata_non_keboola_returns_400(self, seeded_app, monkeypatch):
"""Push is only supported for keboola tables.
Registering a BQ row through the API now requires a configured
BQ project + dataset + source_table (issue #108 M1 added strict
validation), so we seed the row directly via the repository to
keep this test focused on the push-path behavior under test.
"""
from src.db import get_system_db
from src.repositories.table_registry import TableRegistryRepository
c = seeded_app["client"]
token = seeded_app["admin_token"]
conn = get_system_db()
try:
TableRegistryRepository(conn).register(
id="bq_table",
name="bq_table",
source_type="bigquery",
bucket="analytics",
source_table="bq_table",
query_mode="remote",
profile_after_sync=False,
)
finally:
conn.close()
resp = c.post("/api/admin/metadata/bq_table/push", headers=_auth(token))
assert resp.status_code == 400
assert "keboola" in resp.json()["detail"].lower()
def test_push_metadata_analyst_gets_403(self, seeded_app):
c = seeded_app["client"]
token = seeded_app["analyst_token"]
resp = c.post("/api/admin/metadata/some_table/push", headers=_auth(token))
assert resp.status_code == 403
def test_push_metadata_requires_auth(self, seeded_app):
c = seeded_app["client"]
resp = c.post("/api/admin/metadata/some_table/push")
assert resp.status_code == 401
def test_push_keboola_table_without_env_vars_returns_500(self, seeded_app):
"""Keboola table without env vars configured should return 500."""
c = seeded_app["client"]
token = seeded_app["admin_token"]
table_id = _register_table(c, token, "kbc_push_table")
# Should fail with 500 because KBC_STACK_URL and KBC_STORAGE_TOKEN are not set
resp = c.post(f"/api/admin/metadata/{table_id}/push", headers=_auth(token))
assert resp.status_code == 500