From b96403d46d17f3d0b01344cd7b8d067f0d8efa48 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 22 Jun 2025 22:21:32 +0200 Subject: [PATCH] Cleanup. --- tests.py | 246 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 tests.py diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..f4409ce --- /dev/null +++ b/tests.py @@ -0,0 +1,246 @@ +import pytest +import httpx +import asyncio +import os +import random +import string +from typing import Dict, Any, List + +# Configuration for test environment +BASE_URL = "http://localhost:8000" # Adjust if needed +TEST_FILE_DIR = "./test_uploads" + +# Utility Functions +def generate_random_string(length: int = 10) -> str: + """Generate a random string for testing.""" + return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) + +def generate_test_note(tags: List[str] = None, attachments: List[Dict] = None) -> Dict[str, Any]: + """Generate a test note with optional tags and attachments.""" + return { + "title": f"Test Note {generate_random_string()}", + "body": f"This is a test note content {generate_random_string(20)}", + "tags": tags or [generate_random_string(5)], + "attachments": attachments or [] + } + +@pytest.fixture +async def async_client(): + """Create an async HTTP client for testing.""" + async with httpx.AsyncClient(base_url=BASE_URL) as client: + yield client + +@pytest.fixture +def test_file(): + """Create a test file for upload.""" + os.makedirs(TEST_FILE_DIR, exist_ok=True) + test_file_path = os.path.join(TEST_FILE_DIR, f"test_file_{generate_random_string()}.txt") + with open(test_file_path, 'w') as f: + f.write(f"Test file content {generate_random_string(20)}") + yield test_file_path + # Cleanup + os.remove(test_file_path) + +@pytest.mark.asyncio +async def test_health_check(async_client): + """Test the health check endpoint.""" + response = await async_client.get("/api/health") + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + +@pytest.mark.asyncio +async def test_create_note_basic(async_client): + """Test creating a basic note.""" + note_data = generate_test_note() + response = await async_client.post("/api/notes", json=note_data) + + assert response.status_code == 200 + created_note = response.json() + + assert created_note["title"] == note_data["title"] + assert created_note["body"] == note_data["body"] + assert set(created_note["tags"]) == set(note_data["tags"]) + assert "id" in created_note + assert "created_at" in created_note + assert "updated_at" in created_note + +@pytest.mark.asyncio +async def test_create_note_with_multiple_tags(async_client): + """Test creating a note with multiple tags.""" + note_data = generate_test_note( + tags=["work", "personal", generate_random_string(5)] + ) + response = await async_client.post("/api/notes", json=note_data) + + assert response.status_code == 200 + created_note = response.json() + + assert set(created_note["tags"]) == set(note_data["tags"]) + +@pytest.mark.asyncio +async def test_update_note(async_client): + """Test updating an existing note.""" + # First create a note + initial_note = generate_test_note() + create_response = await async_client.post("/api/notes", json=initial_note) + created_note = create_response.json() + + # Now update the note + update_data = { + "title": f"Updated {created_note['title']}", + "body": f"Updated {created_note['body']}", + "tags": ["updated", "test"] + } + update_response = await async_client.put(f"/api/notes/{created_note['id']}", json=update_data) + + assert update_response.status_code == 200 + updated_note = update_response.json() + + assert updated_note["title"] == update_data["title"] + assert updated_note["body"] == update_data["body"] + assert set(updated_note["tags"]) == set(update_data["tags"]) + +@pytest.mark.asyncio +async def test_file_upload(async_client, test_file): + """Test file upload functionality.""" + with open(test_file, 'rb') as f: + files = {'file': f} + response = await async_client.post("/api/upload", files=files) + + assert response.status_code == 200 + upload_result = response.json() + + assert "url" in upload_result + assert "type" in upload_result + assert upload_result["type"] in ["file", "image"] + assert "/static/" in upload_result["url"] + +@pytest.mark.asyncio +async def test_note_with_attachments(async_client, test_file): + """Test creating a note with file attachments.""" + # First upload a file + with open(test_file, 'rb') as f: + files = {'file': f} + upload_response = await async_client.post("/api/upload", files=files) + + file_upload = upload_response.json() + + # Create note with attachment + note_data = generate_test_note( + attachments=[{ + "url": file_upload["url"], + "type": file_upload["type"] + }] + ) + + response = await async_client.post("/api/notes", json=note_data) + + assert response.status_code == 200 + created_note = response.json() + + assert len(created_note["attachments"]) == 1 + assert created_note["attachments"][0]["url"] == file_upload["url"] + +@pytest.mark.asyncio +async def test_list_notes_with_tag_filter(async_client): + """Test listing notes with tag filtering.""" + unique_tag = generate_random_string(5) + + # Create multiple notes with the same tag + for _ in range(3): + note_data = generate_test_note(tags=[unique_tag]) + await async_client.post("/api/notes", json=note_data) + + # List notes with the tag + response = await async_client.get(f"/api/notes?tag={unique_tag}") + + assert response.status_code == 200 + notes = response.json() + + assert len(notes) >= 3 + assert all(unique_tag in note["tags"] for note in notes) + +@pytest.mark.asyncio +async def test_list_tags(async_client): + """Test listing all tags.""" + # Create some notes with unique tags + unique_tags = [generate_random_string(5) for _ in range(5)] + for tag in unique_tags: + note_data = generate_test_note(tags=[tag]) + await async_client.post("/api/notes", json=note_data) + + # List tags + response = await async_client.get("/api/tags") + + assert response.status_code == 200 + tags = response.json() + + # Check that all unique tags are present + assert all({"name": tag} in tags for tag in unique_tags) + +@pytest.mark.asyncio +async def test_concurrent_note_creation(async_client): + """Test concurrent note creation to check for race conditions.""" + async def create_note(): + note_data = generate_test_note() + return await async_client.post("/api/notes", json=note_data) + + # Create 10 notes concurrently + responses = await asyncio.gather(*[create_note() for _ in range(10)]) + + # Check all notes were created successfully + assert all(response.status_code == 200 for response in responses) + + # Ensure unique notes + note_ids = [response.json()["id"] for response in responses] + assert len(set(note_ids)) == 10 + +# Performance and Stress Tests +@pytest.mark.asyncio +async def test_create_many_notes(async_client): + """Stress test: Create a large number of notes.""" + async def create_note(): + note_data = generate_test_note() + return await async_client.post("/api/notes", json=note_data) + + # Create 100 notes + responses = await asyncio.gather(*[create_note() for _ in range(100)]) + + assert all(response.status_code == 200 for response in responses) + +# Edge Cases and Error Handling +@pytest.mark.asyncio +async def test_update_nonexistent_note(async_client): + """Test updating a non-existent note.""" + non_existent_id = 999999 # Assuming this ID doesn't exist + update_data = generate_test_note() + + response = await async_client.put(f"/api/notes/{non_existent_id}", json=update_data) + + assert response.status_code == 404 + +# Bonus: Randomized Testing +@pytest.mark.asyncio +async def test_random_note_operations(async_client): + """Perform a series of randomized note operations.""" + operations = [] + + # Create some initial notes + for _ in range(5): + note_data = generate_test_note() + response = await async_client.post("/api/notes", json=note_data) + operations.append(("create", response.json())) + + # Perform random updates and deletions (simulated) + for _ in range(10): + if operations and random.random() < 0.5: + # Randomly select a note to update + note = random.choice(operations) + if note[0] == "create": + update_data = generate_test_note() + response = await async_client.put(f"/api/notes/{note[1]['id']}", json=update_data) + assert response.status_code == 200 + +# Configuration for running tests +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) \ No newline at end of file