Merge branch 'worktree-agent-a417e289' into feature/v2-fastapi-duckdb-docker-cli
This commit is contained in:
commit
ed58075419
4 changed files with 85 additions and 0 deletions
|
|
@ -237,3 +237,16 @@ class TestUpload:
|
||||||
headers=_h(client["admin"]),
|
headers=_h(client["admin"]),
|
||||||
)
|
)
|
||||||
assert resp.status_code == 413
|
assert resp.status_code == 413
|
||||||
|
|
||||||
|
def test_upload_does_not_leak_absolute_path(self, client):
|
||||||
|
"""Upload response should not contain absolute filesystem paths."""
|
||||||
|
import io
|
||||||
|
resp = client["client"].post(
|
||||||
|
"/api/upload/artifacts",
|
||||||
|
files={"file": ("test.txt", io.BytesIO(b"hello"), "text/plain")},
|
||||||
|
headers=_h(client["admin"]),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.json()
|
||||||
|
assert not data.get("path", "").startswith("/"), "Response should not leak absolute path"
|
||||||
|
assert "filename" in data, "Response should contain filename"
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,27 @@ class TestInstanceConfig:
|
||||||
from app.instance_config import get_instance_name
|
from app.instance_config import get_instance_name
|
||||||
name = get_instance_name()
|
name = get_instance_name()
|
||||||
assert isinstance(name, str)
|
assert isinstance(name, str)
|
||||||
|
|
||||||
|
def test_reads_nested_instance_name(self, tmp_path, monkeypatch):
|
||||||
|
"""get_instance_name should read instance.name from YAML, not flat instance_name."""
|
||||||
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||||
|
monkeypatch.setenv("TESTING", "1")
|
||||||
|
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-min-32-characters!!")
|
||||||
|
|
||||||
|
state_dir = tmp_path / "state"
|
||||||
|
state_dir.mkdir(exist_ok=True)
|
||||||
|
(state_dir / "instance.yaml").write_text(
|
||||||
|
"instance:\n name: Acme Analytics\n subtitle: Data Team\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import app.instance_config as mod
|
||||||
|
# Reset cached config to force reload
|
||||||
|
mod._instance_config = None
|
||||||
|
importlib.reload(mod)
|
||||||
|
|
||||||
|
assert mod.get_instance_name() == "Acme Analytics"
|
||||||
|
assert mod.get_instance_subtitle() == "Data Team"
|
||||||
|
|
||||||
|
# Cleanup: reset cache after test
|
||||||
|
mod._instance_config = None
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,50 @@ class TestAuthSecurity:
|
||||||
assert resp.status_code == 401
|
assert resp.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Script RBAC ----
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def viewer_client(tmp_path, monkeypatch):
|
||||||
|
"""TestClient with a viewer-role user seeded."""
|
||||||
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||||
|
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-32chars-minimum!!!!!")
|
||||||
|
monkeypatch.setenv("SCRIPT_TIMEOUT", "5")
|
||||||
|
|
||||||
|
from app.main import create_app
|
||||||
|
from src.db import get_system_db
|
||||||
|
from src.repositories.users import UserRepository
|
||||||
|
from app.auth.jwt import create_access_token
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
conn = get_system_db()
|
||||||
|
UserRepository(conn).create(id="viewer1", email="viewer@test.com", name="Viewer", role="viewer")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
c = TestClient(app)
|
||||||
|
token = create_access_token(user_id="viewer1", email="viewer@test.com", role="viewer")
|
||||||
|
return c, token
|
||||||
|
|
||||||
|
|
||||||
|
class TestScriptRBAC:
|
||||||
|
def test_viewer_cannot_run_scripts(self, viewer_client):
|
||||||
|
"""Viewers should not be able to execute scripts."""
|
||||||
|
c, token = viewer_client
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
resp = c.post("/api/scripts/run", json={
|
||||||
|
"name": "test", "source": "print('hi')"
|
||||||
|
}, headers=headers)
|
||||||
|
assert resp.status_code == 403
|
||||||
|
|
||||||
|
def test_viewer_cannot_deploy_scripts(self, viewer_client):
|
||||||
|
c, token = viewer_client
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
resp = c.post("/api/scripts/deploy", json={
|
||||||
|
"name": "test", "source": "print('hi')", "schedule": ""
|
||||||
|
}, headers=headers)
|
||||||
|
assert resp.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
# ---- JWT Claims ----
|
# ---- JWT Claims ----
|
||||||
|
|
||||||
class TestJwtClaims:
|
class TestJwtClaims:
|
||||||
|
|
|
||||||
|
|
@ -109,3 +109,7 @@ class TestAdminRoleGuards:
|
||||||
def test_admin_can_access_admin_permissions(self, web_client, admin_cookie):
|
def test_admin_can_access_admin_permissions(self, web_client, admin_cookie):
|
||||||
resp = web_client.get("/admin/permissions", cookies=admin_cookie)
|
resp = web_client.get("/admin/permissions", cookies=admin_cookie)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
def test_analyst_cannot_access_corporate_memory_admin(self, web_client, admin_cookie, analyst_cookie):
|
||||||
|
resp = web_client.get("/corporate-memory/admin", cookies=analyst_cookie)
|
||||||
|
assert resp.status_code == 403
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue