|
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"]) |