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).
177 lines
6.4 KiB
Python
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
|