Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
410 changes: 410 additions & 0 deletions TESTING.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ dependencies = [
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"pytest-cov>=4.0.0",
"pytest-mock>=3.10.0",
"pipreqs>=0.5.0",
]

Expand All @@ -49,7 +51,15 @@ build-backend = "hatchling.build"
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"pytest-cov>=4.0.0",
"pytest-mock>=3.10.0",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
addopts = "-v --cov=app --cov-report=html --cov-report=term-missing"
asyncio_mode = "auto"

[tool.hatch.build.targets.wheel]
packages = ["app"]
1 change: 1 addition & 0 deletions backend/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Backend test suite for MiroFish"""
1 change: 1 addition & 0 deletions backend/tests/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""API integration tests"""
89 changes: 89 additions & 0 deletions backend/tests/api/test_simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Tests for simulation API routes"""

import pytest
from unittest.mock import Mock, patch, MagicMock

from app.api.simulation import (
optimize_interview_prompt,
INTERVIEW_PROMPT_PREFIX
)


class TestInterviewPromptOptimization:
"""Test cases for interview prompt optimization"""

def test_optimize_empty_prompt(self):
"""Test optimizing empty prompt"""
result = optimize_interview_prompt("")
assert result == ""

def test_optimize_none_prompt(self):
"""Test optimizing None prompt"""
result = optimize_interview_prompt(None)
assert result is None

def test_optimize_regular_prompt(self):
"""Test optimizing a regular prompt"""
prompt = "What is your opinion on this topic?"
result = optimize_interview_prompt(prompt)

assert result.startswith(INTERVIEW_PROMPT_PREFIX)
assert prompt in result
assert "工具" in result or "tools" in result or "直接" in result

def test_optimize_prompt_avoids_duplication(self):
"""Test that optimizing already optimized prompt doesn't duplicate prefix"""
original_prompt = "What do you think?"
optimized_once = optimize_interview_prompt(original_prompt)
optimized_twice = optimize_interview_prompt(optimized_once)

# Should not have doubled prefix
assert optimized_once.count(INTERVIEW_PROMPT_PREFIX) == 1
assert optimized_twice.count(INTERVIEW_PROMPT_PREFIX) == 1
assert optimized_once == optimized_twice

def test_optimize_prompt_preserves_content(self):
"""Test that optimization preserves original prompt content"""
prompt = "Tell me about your experience in software development"
result = optimize_interview_prompt(prompt)

assert "software development" in result

def test_optimize_long_prompt(self):
"""Test optimizing a long prompt"""
prompt = "This is a very long prompt. " * 10
result = optimize_interview_prompt(prompt)

assert INTERVIEW_PROMPT_PREFIX in result
assert prompt in result

def test_optimize_prompt_with_special_characters(self):
"""Test optimizing prompt with special characters"""
prompt = "What about China's 政策? And émojis 🚀"
result = optimize_interview_prompt(prompt)

assert INTERVIEW_PROMPT_PREFIX in result
assert "China" in result or "政策" in result

def test_optimize_prompt_idempotent(self):
"""Test that optimization is idempotent"""
prompt = "Initial prompt"
result1 = optimize_interview_prompt(prompt)
result2 = optimize_interview_prompt(result1)
result3 = optimize_interview_prompt(result2)

assert result1 == result2 == result3


class TestSimulationAPIHelper:
"""Test helper functions for simulation API"""

def test_interview_prompt_prefix_not_empty(self):
"""Test that INTERVIEW_PROMPT_PREFIX is defined"""
assert INTERVIEW_PROMPT_PREFIX is not None
assert len(INTERVIEW_PROMPT_PREFIX) > 0
assert "结合你的人设" in INTERVIEW_PROMPT_PREFIX or "文本回复" in INTERVIEW_PROMPT_PREFIX

def test_interview_prompt_prefix_contains_chinese(self):
"""Test that prefix contains Chinese instructions"""
assert any(ord(c) > 127 for c in INTERVIEW_PROMPT_PREFIX) # Contains non-ASCII
93 changes: 93 additions & 0 deletions backend/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Shared pytest configuration and fixtures"""

import os
import sys
import tempfile
import pytest
from pathlib import Path

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

from app.config import Config


@pytest.fixture
def temp_file():
"""Create a temporary file for testing"""
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("Test content for MiroFish")
temp_path = f.name

yield temp_path

# Cleanup
if os.path.exists(temp_path):
os.unlink(temp_path)


@pytest.fixture
def temp_pdf_file():
"""Create a temporary PDF file for testing"""
import fitz # PyMuPDF

# Create a simple PDF
doc = fitz.open()
page = doc.new_page()
page.insert_text((50, 50), "MiroFish Test Document\n\nThis is a test PDF for file parsing.")

with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as f:
doc.save(f.name)
temp_path = f.name

yield temp_path

# Cleanup
if os.path.exists(temp_path):
os.unlink(temp_path)


@pytest.fixture
def sample_json_data():
"""Sample JSON data for testing"""
return {
"graph_id": "test_graph_123",
"entities": [
{
"uuid": "entity_1",
"label": "Person",
"name": "John Doe",
"description": "Test person"
},
{
"uuid": "entity_2",
"label": "Organization",
"name": "MiroFish Inc",
"description": "Test organization"
}
],
"relationships": [
{
"source": "entity_1",
"target": "entity_2",
"relationship_type": "works_for"
}
]
}


@pytest.fixture
def mock_config(monkeypatch):
"""Mock configuration for testing"""
monkeypatch.setenv("LLM_API_KEY", "test_key_123")
monkeypatch.setenv("LLM_MODEL_NAME", "test-model")
monkeypatch.setenv("LLM_BASE_URL", "https://test.api.com/v1")
monkeypatch.setenv("ZEP_API_KEY", "test_zep_key")

# Reload config to pick up environment variables
Config.LLM_API_KEY = "test_key_123"
Config.LLM_MODEL_NAME = "test-model"
Config.LLM_BASE_URL = "https://test.api.com/v1"
Config.ZEP_API_KEY = "test_zep_key"

return Config
1 change: 1 addition & 0 deletions backend/tests/unit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Unit tests for MiroFish backend"""
1 change: 1 addition & 0 deletions backend/tests/unit/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for service modules"""
Loading