| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- """
- Property-based tests for Project creation.
- Tests universal properties that should hold for all valid project data.
- Feature: annotation-platform
- Property 1: Project creation adds to list
- Validates: Requirements 1.3
- """
- import os
- import pytest
- from hypothesis import given, strategies as st, settings, HealthCheck
- from fastapi.testclient import TestClient
- # Use a test database
- TEST_DB_PATH = "test_property_annotation_platform.db"
- @pytest.fixture(scope="function", autouse=True)
- def setup_test_db():
- """Setup test database before each test and cleanup after."""
- # Set test database path
- original_db_path = os.environ.get("DATABASE_PATH")
- os.environ["DATABASE_PATH"] = TEST_DB_PATH
-
- # Remove existing test database
- if os.path.exists(TEST_DB_PATH):
- os.remove(TEST_DB_PATH)
-
- # Import after setting env var
- from database import init_database
- init_database()
-
- yield
-
- # Cleanup
- if os.path.exists(TEST_DB_PATH):
- os.remove(TEST_DB_PATH)
-
- # Restore original path
- if original_db_path:
- os.environ["DATABASE_PATH"] = original_db_path
- elif "DATABASE_PATH" in os.environ:
- del os.environ["DATABASE_PATH"]
- def get_test_client():
- """Create a test client."""
- from main import app
- return TestClient(app)
- # Strategy for generating valid project names (non-empty strings)
- valid_project_names = st.text(
- alphabet=st.characters(
- whitelist_categories=("Lu", "Ll", "Nd", "P", "Zs"),
- min_codepoint=32,
- max_codepoint=126
- ),
- min_size=1,
- max_size=100
- ).filter(lambda x: x.strip() != "") # Ensure not just whitespace
- # Strategy for generating project descriptions
- project_descriptions = st.text(
- alphabet=st.characters(
- whitelist_categories=("Lu", "Ll", "Nd", "P", "Zs"),
- min_codepoint=32,
- max_codepoint=126
- ),
- max_size=500
- )
- # Strategy for generating valid Label Studio configs (non-empty strings)
- valid_configs = st.text(
- alphabet=st.characters(
- whitelist_categories=("Lu", "Ll", "Nd", "P", "Zs"),
- min_codepoint=32,
- max_codepoint=126
- ),
- min_size=1,
- max_size=200
- ).filter(lambda x: x.strip() != "") # Ensure not just whitespace
- @settings(max_examples=100, deadline=None, suppress_health_check=[HealthCheck.function_scoped_fixture])
- @given(
- name=valid_project_names,
- description=project_descriptions,
- config=valid_configs
- )
- def test_property_1_project_creation_adds_to_list(
- name: str,
- description: str,
- config: str
- ):
- """
- Feature: annotation-platform, Property 1: Project creation adds to list
-
- Property: For any valid project data (non-empty name and config),
- creating a project should result in the project appearing in the
- projects list with a unique ID.
-
- Validates: Requirements 1.3
-
- This property tests that:
- 1. Creating a project with valid data succeeds
- 2. The created project has a unique ID
- 3. The project appears in the list of all projects
- 4. The project data is preserved correctly
- """
- # Create test client for this example
- test_client = get_test_client()
-
- # Get initial project count
- initial_response = test_client.get("/api/projects")
- assert initial_response.status_code == 200
- initial_projects = initial_response.json()
- initial_count = len(initial_projects)
-
- # Create project with generated data
- project_data = {
- "name": name,
- "description": description,
- "config": config
- }
-
- create_response = test_client.post("/api/projects", json=project_data)
-
- # Verify creation succeeded
- assert create_response.status_code == 201, \
- f"Project creation failed with status {create_response.status_code}"
-
- created_project = create_response.json()
-
- # Verify project has a unique ID
- assert "id" in created_project, "Created project should have an ID"
- assert created_project["id"] is not None, "Project ID should not be None"
- assert created_project["id"] != "", "Project ID should not be empty"
- assert created_project["id"].startswith("proj_"), \
- "Project ID should start with 'proj_' prefix"
-
- # Verify project data is preserved
- assert created_project["name"] == name, \
- "Created project name should match input"
- assert created_project["description"] == description, \
- "Created project description should match input"
- assert created_project["config"] == config, \
- "Created project config should match input"
-
- # Verify project appears in list
- list_response = test_client.get("/api/projects")
- assert list_response.status_code == 200
- projects_list = list_response.json()
-
- # Verify list grew by exactly one
- assert len(projects_list) == initial_count + 1, \
- "Project list should grow by exactly one after creation"
-
- # Verify the created project is in the list
- project_ids = [p["id"] for p in projects_list]
- assert created_project["id"] in project_ids, \
- "Created project should appear in projects list"
-
- # Find the created project in the list and verify data
- created_in_list = next(
- (p for p in projects_list if p["id"] == created_project["id"]),
- None
- )
- assert created_in_list is not None, \
- "Created project should be findable in list"
- assert created_in_list["name"] == name, \
- "Project name in list should match input"
- assert created_in_list["description"] == description, \
- "Project description in list should match input"
- assert created_in_list["config"] == config, \
- "Project config in list should match input"
-
- # Verify task_count is initialized to 0
- assert created_project["task_count"] == 0, \
- "New project should have task_count of 0"
- assert created_in_list["task_count"] == 0, \
- "New project in list should have task_count of 0"
- @settings(max_examples=50, deadline=None, suppress_health_check=[HealthCheck.function_scoped_fixture])
- @given(
- name1=valid_project_names,
- name2=valid_project_names,
- config=valid_configs
- )
- def test_property_1_multiple_projects_have_unique_ids(
- name1: str,
- name2: str,
- config: str
- ):
- """
- Feature: annotation-platform, Property 1: Project creation adds to list
-
- Property: Creating multiple projects should result in each project
- having a unique ID, even if they have the same name.
-
- Validates: Requirements 1.3
-
- This property tests that:
- 1. Multiple projects can be created
- 2. Each project gets a unique ID
- 3. Projects with the same name still get different IDs
- """
- # Create test client for this example
- test_client = get_test_client()
-
- # Create first project
- project1_data = {
- "name": name1,
- "description": "First project",
- "config": config
- }
- response1 = test_client.post("/api/projects", json=project1_data)
- assert response1.status_code == 201
- project1 = response1.json()
-
- # Create second project
- project2_data = {
- "name": name2,
- "description": "Second project",
- "config": config
- }
- response2 = test_client.post("/api/projects", json=project2_data)
- assert response2.status_code == 201
- project2 = response2.json()
-
- # Verify both projects have IDs
- assert "id" in project1
- assert "id" in project2
-
- # Verify IDs are unique
- assert project1["id"] != project2["id"], \
- "Different projects should have unique IDs"
-
- # Verify both projects appear in list
- list_response = test_client.get("/api/projects")
- assert list_response.status_code == 200
- projects_list = list_response.json()
-
- project_ids = [p["id"] for p in projects_list]
- assert project1["id"] in project_ids, \
- "First project should be in list"
- assert project2["id"] in project_ids, \
- "Second project should be in list"
-
- # Verify we can retrieve each project individually
- get_response1 = test_client.get(f"/api/projects/{project1['id']}")
- assert get_response1.status_code == 200
- assert get_response1.json()["id"] == project1["id"]
-
- get_response2 = test_client.get(f"/api/projects/{project2['id']}")
- assert get_response2.status_code == 200
- assert get_response2.json()["id"] == project2["id"]
|