import tempfile from pathlib import Path from flask import Flask from sqlalchemy import create_engine from app.api import graph_bp from app.api.auth import auth_bp from app.db import Base, create_session_factory from app.models.project import ProjectManager from app.models.saas import Organization from app.services.identity import IdentityRepository, PasswordService def make_graph_app(): engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) session_factory = create_session_factory(engine) app = Flask(__name__) app.config.update(TESTING=True, SECRET_KEY="test-secret", SESSION_COOKIE_SECURE=False) app.extensions["crowdsight_session_factory"] = session_factory app.register_blueprint(auth_bp, url_prefix="/api/auth") app.register_blueprint(graph_bp, url_prefix="/api/graph") with session_factory() as session: repo = IdentityRepository(session) org = repo.create_organization(name="Org A", slug="org-a") user_a = repo.create_user( email="user-a@example.com", password_hash=PasswordService.hash_password("correct horse battery staple"), ) repo.create_membership(user_a.id, org.id, "user") user_b = repo.create_user( email="user-b@example.com", password_hash=PasswordService.hash_password("correct horse battery staple"), ) repo.create_membership(user_b.id, org.id, "user") admin = repo.create_user( email="admin@example.com", password_hash=PasswordService.hash_password("correct horse battery staple"), ) repo.create_membership(admin.id, org.id, "admin") session.commit() return app, engine, user_a.id, user_b.id def login(client, email): response = client.post( "/api/auth/login", json={"email": email, "password": "correct horse battery staple"}, ) assert response.status_code == 200 def test_graph_data_and_task_routes_require_auth(): original_dir = ProjectManager.PROJECTS_DIR app, engine, _user_a_id, _user_b_id = make_graph_app() with tempfile.TemporaryDirectory() as temp_dir: ProjectManager.PROJECTS_DIR = str(Path(temp_dir) / "projects") try: client = app.test_client() assert client.get("/api/graph/data/graph-any").status_code == 401 login(client, "user-a@example.com") assert client.get("/api/graph/data/arbitrary-graph").status_code == 404 assert client.get("/api/graph/task/arbitrary-task").status_code == 404 finally: ProjectManager.PROJECTS_DIR = original_dir engine.dispose() def test_graph_project_routes_require_auth_and_scope_records(): original_dir = ProjectManager.PROJECTS_DIR app, engine, user_a_id, user_b_id = make_graph_app() with tempfile.TemporaryDirectory() as temp_dir: ProjectManager.PROJECTS_DIR = str(Path(temp_dir) / "projects") try: project_a = ProjectManager.create_project( "A", organization_id="org_a_placeholder", owner_user_id=user_a_id ) project_b = ProjectManager.create_project( "B", organization_id="org_a_placeholder", owner_user_id=user_b_id ) # The app's organization id is discovered from the seeded user. with app.extensions["crowdsight_session_factory"]() as session: org_id = session.query(Organization).one().id for project in (project_a, project_b): project.organization_id = org_id ProjectManager.save_project(project) client = app.test_client() assert client.get(f"/api/graph/project/{project_a.project_id}").status_code == 401 login(client, "user-a@example.com") assert client.get(f"/api/graph/project/{project_a.project_id}").status_code == 200 assert client.get(f"/api/graph/project/{project_b.project_id}").status_code == 404 listed = client.get("/api/graph/project/list").get_json() assert [item["project_id"] for item in listed["data"]] == [project_a.project_id] finally: ProjectManager.PROJECTS_DIR = original_dir engine.dispose()