import json import uuid import pytest from backend.app import create_app @pytest.fixture def client(): app = create_app() app.config["TESTING"] = True with app.test_client() as client: yield client @pytest.fixture def auth_headers(client): """ Registers a test user with unique username and returns auth headers. """ unique_suffix = str(uuid.uuid4())[:8] username = f"projtest_{unique_suffix}" email = f"{username}@example.com" payload = { "username": username, "email": email, "password": "Password123" } response = client.post("/api/register", data=json.dumps(payload), content_type="application/json") assert response.status_code == 201, f"Registration failed: {response.data.decode()}" data = json.loads(response.data) token = data["token"] return {"Authorization": f"Bearer {token}"} def test_create_project(client, auth_headers): payload = { "name": "Test Project", "topic": "Testing", "description": "A project for testing purposes." } response = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert response.status_code == 201, f"Create project failed: {response.data.decode()}" data = json.loads(response.data) assert "project_id" in data assert "passkey" in data def test_get_projects(client, auth_headers): # Create two projects. payload1 = {"name": "Test Project One", "description": "First test project."} payload2 = {"name": "Test Project Two", "description": "Second test project."} response1 = client.post("/api/projects", data=json.dumps(payload1), content_type="application/json", headers=auth_headers) response2 = client.post("/api/projects", data=json.dumps(payload2), content_type="application/json", headers=auth_headers) assert response1.status_code == 201, f"Project one creation failed: {response1.data.decode()}" assert response2.status_code == 201, f"Project two creation failed: {response2.data.decode()}" response = client.get("/api/projects", headers=auth_headers) assert response.status_code == 200, f"Get projects failed: {response.data.decode()}" data = json.loads(response.data) assert "projects" in data assert isinstance(data["projects"], list) assert len(data["projects"]) >= 2 def test_get_project_detail(client, auth_headers): payload = {"name": "Detail Project1111", "description": "A project for detail testing."} create_resp = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert create_resp.status_code == 201, f"Creation failed: {create_resp.data.decode()}" project_id = json.loads(create_resp.data)["project_id"] response = client.get(f"/api/projects/{project_id}", headers=auth_headers) assert response.status_code == 200, f"Get detail failed: {response.data.decode()}" data = json.loads(response.data) assert data.get("name") == "Detail Project1111" assert "updatedAt" in data def test_update_project(client, auth_headers): payload = {"name": "Update Project", "description": "Initial description."} create_resp = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert create_resp.status_code == 201, f"Creation failed: {create_resp.data.decode()}" project_id = json.loads(create_resp.data)["project_id"] update_payload = {"description": "Updated description.", "topic": "Updated Topic"} update_resp = client.put(f"/api/projects/{project_id}", data=json.dumps(update_payload), content_type="application/json", headers=auth_headers) assert update_resp.status_code == 200, f"Update failed: {update_resp.data.decode()}" detail_resp = client.get(f"/api/projects/{project_id}", headers=auth_headers) detail_data = json.loads(detail_resp.data) assert detail_data.get("description") == "Updated description." assert detail_data.get("topic") == "Updated Topic" def test_delete_project(client, auth_headers): payload = {"name": "Delete Project", "description": "Project to be deleted."} create_resp = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert create_resp.status_code == 201, f"Creation failed: {create_resp.data.decode()}" project_id = json.loads(create_resp.data)["project_id"] delete_resp = client.delete(f"/api/projects/{project_id}", headers=auth_headers) assert delete_resp.status_code == 200, f"Deletion failed: {delete_resp.data.decode()}" detail_resp = client.get(f"/api/projects/{project_id}", headers=auth_headers) assert detail_resp.status_code == 404, "Deleted project still accessible" def test_get_project_summaries(client, auth_headers): # Create a new project to ensure at least one exists. payload = {"name": "Summary Project", "description": "Project for summary test."} create_resp = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert create_resp.status_code == 201, f"Project creation failed: {create_resp.data.decode()}" response = client.get("/api/projects/summary", headers=auth_headers) assert response.status_code == 200, f"Summary fetch failed: {response.data.decode()}" data = json.loads(response.data) assert "projects" in data # Each project summary should include project_id, name, and updatedAt. for proj in data["projects"]: assert "project_id" in proj assert "name" in proj assert "updatedAt" in proj # ------------------------------------------------------------------------------ # New Tests # ------------------------------------------------------------------------------ def test_get_project_info(client, auth_headers): """ Tests the /api/projects//info endpoint, which returns name, topic, description, keywords, summary. """ payload = { "name": "Info Project", "topic": "InfoTopic", "description": "Project with an info endpoint." } create_resp = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert create_resp.status_code == 201, f"Project creation failed: {create_resp.data.decode()}" project_id = json.loads(create_resp.data)["project_id"] info_resp = client.get(f"/api/projects/{project_id}/info", headers=auth_headers) assert info_resp.status_code == 200, f"Get project info failed: {info_resp.data.decode()}" info_data = json.loads(info_resp.data) assert info_data.get("name") == "Info Project" assert info_data.get("topic") == "InfoTopic" assert info_data.get("description") == "Project with an info endpoint." assert isinstance(info_data.get("keywords", []), list) assert "summary" in info_data def test_recalc_project_keywords(client, auth_headers): """ Tests the /api/projects//recalc_keywords endpoint. It should gather all URL keywords, sum them, sort by top 20, and update the project. """ # 1) Create a project payload = {"name": "Keyword Recalc Project", "description": "Test for recalc keywords."} create_resp = client.post("/api/projects", data=json.dumps(payload), content_type="application/json", headers=auth_headers) assert create_resp.status_code == 201, f"Project creation failed: {create_resp.data.decode()}" project_id = json.loads(create_resp.data)["project_id"] # 2) Create multiple URLs with keywords url1_keywords = [{"word": "alpha", "percentage": 50}, {"word": "beta", "percentage": 10}] url2_keywords = [{"word": "alpha", "percentage": 20}, {"word": "gamma", "percentage": 15}] url_create_payload1 = {"url": "https://url1.com", "keywords": url1_keywords} url_create_payload2 = {"url": "https://url2.com", "keywords": url2_keywords} resp1 = client.post(f"/api/projects/{project_id}/urls", data=json.dumps(url_create_payload1), content_type="application/json", headers=auth_headers) resp2 = client.post(f"/api/projects/{project_id}/urls", data=json.dumps(url_create_payload2), content_type="application/json", headers=auth_headers) assert resp1.status_code == 201, f"URL1 creation failed: {resp1.data.decode()}" assert resp2.status_code == 201, f"URL2 creation failed: {resp2.data.decode()}" # 3) Recalc keywords recalc_resp = client.put(f"/api/projects/{project_id}/recalc_keywords", headers=auth_headers) assert recalc_resp.status_code == 200, f"Keyword recalc failed: {recalc_resp.data.decode()}" recalc_data = json.loads(recalc_resp.data) assert "keywords" in recalc_data top_keywords = recalc_data["keywords"] # Check that alpha is presumably 70, gamma=15, beta=10 => alpha, gamma, beta assert len(top_keywords) <= 20 # alpha => 70 (50 + 20) alpha_kw = next((k for k in top_keywords if k["word"] == "alpha"), None) assert alpha_kw is not None, "Expected alpha in combined keywords" assert alpha_kw["percentage"] == 70.0 # 4) Fetch the project detail or info to confirm stored result info_resp = client.get(f"/api/projects/{project_id}/info", headers=auth_headers) assert info_resp.status_code == 200, f"Get project info after recalc failed: {info_resp.data.decode()}" info_data = json.loads(info_resp.data) # The project now has 'keywords' -> top 20 project_keywords = info_data.get("keywords", []) alpha_in_project = next((k for k in project_keywords if k["word"] == "alpha"), None) assert alpha_in_project is not None, "Project keywords missing alpha" assert alpha_in_project["percentage"] == 70.0