2026-06-01 15:54:25 +00:00
|
|
|
"""Tests for multi-tenant auth (without WhisperX dependency)."""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import tempfile
|
|
|
|
|
import unittest
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
|
|
|
|
from backend.auth.database import bootstrap_from_config, init_db
|
2026-06-01 16:16:23 +00:00
|
|
|
from backend.auth.models import UserContext
|
2026-06-01 15:54:25 +00:00
|
|
|
from backend.auth.routes import admin_router, router as auth_router
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_test_app() -> FastAPI:
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
app.include_router(auth_router)
|
|
|
|
|
app.include_router(admin_router)
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthTestCase(unittest.TestCase):
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
cls._tmpdir = tempfile.TemporaryDirectory()
|
|
|
|
|
cls.db_path = Path(cls._tmpdir.name) / "test.db"
|
|
|
|
|
os.environ["JWT_SECRET"] = "test-secret-key"
|
|
|
|
|
os.environ["AUTH_ADMIN_PASSWORD"] = "admin123"
|
|
|
|
|
os.environ["AUTH_DATABASE_PATH"] = str(cls.db_path)
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
|
"auth": {
|
|
|
|
|
"database_path": str(cls.db_path),
|
|
|
|
|
"bootstrap": {
|
|
|
|
|
"org_slug": "merakom",
|
|
|
|
|
"org_name": "Test Org",
|
|
|
|
|
"admin_username": "admin",
|
|
|
|
|
"default_projects": [
|
|
|
|
|
{"slug": "2026", "name": "2026"},
|
|
|
|
|
{"slug": "gp-merakom", "name": "GP"},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
init_db(config)
|
|
|
|
|
bootstrap_from_config(config)
|
|
|
|
|
cls.client = TestClient(_build_test_app())
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def tearDownClass(cls):
|
|
|
|
|
cls._tmpdir.cleanup()
|
|
|
|
|
os.environ.pop("AUTH_DATABASE_PATH", None)
|
|
|
|
|
|
|
|
|
|
def test_login_admin(self):
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "admin", "password": "admin123"},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("access_token", data)
|
|
|
|
|
self.assertTrue(data["user"]["is_admin"])
|
|
|
|
|
|
|
|
|
|
def test_create_user_and_project_acl(self):
|
|
|
|
|
admin_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "admin", "password": "admin123"},
|
|
|
|
|
).json()
|
|
|
|
|
headers = {"Authorization": f"Bearer {admin_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
create_user = self.client.post(
|
|
|
|
|
"/api/admin/users",
|
|
|
|
|
headers=headers,
|
|
|
|
|
json={
|
|
|
|
|
"username": "worker",
|
|
|
|
|
"password": "worker123",
|
|
|
|
|
"role": "user",
|
|
|
|
|
"projects": ["2026"],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(create_user.status_code, 200)
|
|
|
|
|
|
|
|
|
|
user_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "worker", "password": "worker123"},
|
|
|
|
|
).json()
|
|
|
|
|
user_headers = {"Authorization": f"Bearer {user_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
projects = self.client.get("/api/auth/projects", headers=user_headers).json()
|
|
|
|
|
slugs = {p["slug"] for p in projects["projects"]}
|
|
|
|
|
self.assertEqual(slugs, {"2026"})
|
|
|
|
|
|
|
|
|
|
def test_director_has_all_projects_and_no_admin(self):
|
|
|
|
|
admin_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "admin", "password": "admin123"},
|
|
|
|
|
).json()
|
|
|
|
|
headers = {"Authorization": f"Bearer {admin_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
create_director = self.client.post(
|
|
|
|
|
"/api/admin/users",
|
|
|
|
|
headers=headers,
|
|
|
|
|
json={
|
|
|
|
|
"username": "director1",
|
|
|
|
|
"password": "dir123",
|
|
|
|
|
"role": "director",
|
|
|
|
|
"projects": [],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(create_director.status_code, 200)
|
|
|
|
|
self.assertTrue(create_director.json()["user"]["all_projects_access"])
|
|
|
|
|
self.assertFalse(create_director.json()["user"]["is_admin"])
|
|
|
|
|
|
|
|
|
|
director_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "director1", "password": "dir123"},
|
|
|
|
|
).json()
|
|
|
|
|
d_headers = {"Authorization": f"Bearer {director_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
projects = self.client.get("/api/auth/projects", headers=d_headers).json()
|
|
|
|
|
slugs = {p["slug"] for p in projects["projects"]}
|
2026-06-01 16:16:23 +00:00
|
|
|
self.assertEqual(slugs, {"2026", "gp-merakom", "org-gp"})
|
2026-06-01 15:54:25 +00:00
|
|
|
|
|
|
|
|
global_query = self.client.post(
|
|
|
|
|
"/api/rag/query-global",
|
|
|
|
|
headers=d_headers,
|
|
|
|
|
json={"question": "test"},
|
|
|
|
|
)
|
|
|
|
|
self.assertNotEqual(global_query.status_code, 403)
|
|
|
|
|
|
|
|
|
|
def test_user_creates_personal_project(self):
|
|
|
|
|
admin_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "admin", "password": "admin123"},
|
|
|
|
|
).json()
|
|
|
|
|
headers = {"Authorization": f"Bearer {admin_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
self.client.post(
|
|
|
|
|
"/api/admin/users",
|
|
|
|
|
headers=headers,
|
|
|
|
|
json={
|
|
|
|
|
"username": "builder",
|
|
|
|
|
"password": "build123",
|
|
|
|
|
"role": "user",
|
|
|
|
|
"projects": [],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
user_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "builder", "password": "build123"},
|
|
|
|
|
).json()
|
|
|
|
|
user_headers = {"Authorization": f"Bearer {user_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
created = self.client.post(
|
|
|
|
|
"/api/auth/projects",
|
|
|
|
|
headers=user_headers,
|
|
|
|
|
json={"slug": "my-gp", "name": "Мой ГП"},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(created.status_code, 200)
|
|
|
|
|
self.assertEqual(created.json()["project"]["scope"], "personal")
|
|
|
|
|
self.assertTrue(created.json()["project"]["is_owner"])
|
|
|
|
|
|
|
|
|
|
projects = self.client.get("/api/auth/projects", headers=user_headers).json()
|
|
|
|
|
slugs = {p["slug"] for p in projects["projects"]}
|
|
|
|
|
self.assertEqual(slugs, {"my-gp"})
|
|
|
|
|
|
2026-06-01 16:16:23 +00:00
|
|
|
def test_admin_create_project_normalizes_slug(self):
|
|
|
|
|
admin_login = self.client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"org_slug": "merakom", "username": "admin", "password": "admin123"},
|
|
|
|
|
).json()
|
|
|
|
|
headers = {"Authorization": f"Bearer {admin_login['access_token']}"}
|
|
|
|
|
|
|
|
|
|
created = self.client.post(
|
|
|
|
|
"/api/admin/projects",
|
|
|
|
|
headers=headers,
|
|
|
|
|
json={"slug": "Org-GP!!", "name": "ГП org"},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(created.status_code, 200)
|
|
|
|
|
self.assertEqual(created.json()["project"]["slug"], "org-gp")
|
|
|
|
|
|
|
|
|
|
def test_task_progress_visible_only_to_owner(self):
|
|
|
|
|
task = {"org_slug": "merakom", "user_id": 2, "file": "secret.mp3"}
|
|
|
|
|
owner = UserContext(
|
|
|
|
|
user_id=2, username="worker", role="user",
|
|
|
|
|
org_id=1, org_slug="merakom", org_name="Test",
|
|
|
|
|
)
|
|
|
|
|
other = UserContext(
|
|
|
|
|
user_id=3, username="other", role="user",
|
|
|
|
|
org_id=1, org_slug="merakom", org_name="Test",
|
|
|
|
|
)
|
|
|
|
|
admin = UserContext(
|
|
|
|
|
user_id=1, username="admin", role="admin",
|
|
|
|
|
org_id=1, org_slug="merakom", org_name="Test",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.assertTrue(owner.can_see_task(task))
|
|
|
|
|
self.assertFalse(other.can_see_task(task))
|
|
|
|
|
self.assertTrue(admin.can_see_task(task))
|
|
|
|
|
|
2026-06-01 15:54:25 +00:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|