"""P14.1 — Projects API tests. Covers: - List projects - Get project by ID - Create project - Update project - Delete project - Project ownership and permissions """ import pytest class TestProjects: """Project management endpoints.""" def test_list_projects(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions): """User can list projects they have access to.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() project1 = make_project(name="Project 1") project2 = make_project(name="Project 2") resp = client.get("/projects", headers=auth_header(user)) assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) def test_get_project_by_id(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions): """Get specific project details.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() project = make_project(name="Test Project", owner_id=user.id) resp = client.get(f"/projects/{project.id}", headers=auth_header(user)) assert resp.status_code == 200 data = resp.json() assert data["id"] == project.id assert data["name"] == "Test Project" def test_create_project(self, client, db, make_user, auth_header, seed_roles_and_permissions): """User can create project.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() resp = client.post( "/projects", json={ "name": "New Project", "description": "Test description", "project_code": "TEST" }, headers=auth_header(user) ) assert resp.status_code == 201 data = resp.json() assert data["name"] == "New Project" assert data["project_code"] == "TEST" assert "id" in data def test_update_project(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions): """Project owner can update project.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() project = make_project(name="Old Name", owner_id=user.id) resp = client.patch( f"/projects/{project.id}", json={"name": "Updated Name"}, headers=auth_header(user) ) assert resp.status_code == 200 data = resp.json() assert data["name"] == "Updated Name" def test_delete_project(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions): """Project owner can delete project.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() project = make_project(owner_id=user.id) resp = client.delete(f"/projects/{project.id}", headers=auth_header(user)) assert resp.status_code == 204 def test_non_owner_cannot_delete_project(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """Non-owner cannot delete project.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions owner = make_user(username="owner") other = make_user(username="other") project = make_project(owner_id=owner.id) make_member(project.id, other.id, dev_role.id) resp = client.delete(f"/projects/{project.id}", headers=auth_header(other)) assert resp.status_code == 403 def test_project_code_generation(self, client, db, make_user, auth_header, seed_roles_and_permissions): """Project code is auto-generated if not provided.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() resp = client.post( "/projects", json={"name": "Auto Code Project"}, headers=auth_header(user) ) assert resp.status_code == 201 data = resp.json() assert data["project_code"].startswith("P")