Test Framework Documentation
This document describes the unit testing framework setup for the Wegent project.
Overviewβ
The project includes comprehensive unit testing support across all modules:
- Backend (FastAPI): pytest + pytest-asyncio + pytest-cov + pytest-mock
- Executor (AI Agent Engine): pytest + pytest-mock + pytest-asyncio
- Executor Manager (Task Management): pytest + pytest-mock + pytest-cov
- Shared (Utilities): pytest + pytest-cov
- Frontend (Next.js + React 19): Jest + @testing-library/react
Current Test Coverageβ
Backend (backend/)β
- β Core security: Authentication, JWT tokens, password hashing
- β Configuration management
- β Exception handling
- β User service and models
- β GitHub repository provider
- β³ API endpoints (placeholder directory exists)
Executor (executor/)β
- β Agent factory
- β Base agent classes
- β Mocked AI client interactions (Anthropic, OpenAI)
Executor Manager (executor_manager/)β
- β Base executor classes
- β Task dispatcher
- β Docker executor and utilities
- β Docker constants and configuration
Shared (shared/)β
- β Cryptography utilities
- β Sensitive data masking (tokens, API keys, etc.)
Frontend (frontend/)β
- β³ Component tests (basic setup in place)
- β³ Hook tests
- β³ Utility tests
Test Coverage Goalsβ
- Target: 40-60% code coverage initially
- Priority: Core business logic and critical paths
- Strategy: Incremental coverage improvement
Backend Testsβ
Running Testsβ
cd backend
pytest # Run all tests
pytest tests/core/ # Run core tests only
pytest --cov=app # Run with coverage report
pytest -v # Verbose output
pytest -k test_security # Run specific test pattern
pytest -m unit # Run only unit tests
pytest -m integration # Run only integration tests
Test Structureβ
backend/tests/
βββ conftest.py # Global test fixtures
βββ core/ # Core infrastructure tests
β βββ test_security.py # Authentication & JWT tests
β βββ test_config.py # Configuration tests
β βββ test_exceptions.py # Exception handler tests
βββ services/ # Service layer tests
β βββ test_user_service.py # User service tests
βββ models/ # Data model tests
β βββ test_user_model.py # User model tests
βββ repository/ # Repository integration tests
β βββ test_github_provider.py
βββ api/ # API endpoint tests (placeholder)
Test Configurationβ
The backend uses pytest.ini for configuration with the following settings:
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--strict-markers
--cov=app
--cov-report=term-missing
--cov-report=html
--cov-report=xml
asyncio_mode = auto
markers =
unit: Unit tests
integration: Integration tests
slow: Slow running tests
Key Fixturesβ
test_db: SQLite in-memory database session (function scope)test_settings: Test settings with overridden valuestest_user: Test user instancetest_admin_user: Test admin user instancetest_inactive_user: Inactive test user instancetest_token: Valid JWT token for test usertest_admin_token: Valid JWT token for admin usertest_client: FastAPI test client with database overridemock_redis: Mocked Redis client
Executor Testsβ
Running Testsβ
cd executor
pytest tests/ --cov=agents
Test Structureβ
executor/tests/
βββ conftest.py # Executor-specific fixtures
βββ agents/ # Agent tests
Key Fixturesβ
mock_anthropic_client: Mocked Anthropic API client for testing Claude modelsmock_openai_client: Mocked OpenAI API client for testing GPT modelsmock_callback_client: Mocked callback HTTP client for agent responsessuppress_resource_warnings: Session-scoped fixture to suppress ResourceWarning messagescleanup_logging: Session-scoped fixture to clean up logging handlers and prevent daemon thread errors
Executor Manager Testsβ
Running Testsβ
cd executor_manager
pytest tests/ --cov=executors
Key Fixturesβ
mock_docker_client: Mocked Docker SDK client for container operationsmock_executor_config: Mock executor configuration with image, CPU, memory, and network settings
Test Structureβ
executor_manager/tests/
βββ conftest.py # Executor manager fixtures
βββ executors/ # Executor tests
βββ test_base.py
βββ test_dispatcher.py
βββ test_docker_executor.py
βββ test_docker_utils.py
βββ test_docker_constants.py
Shared Testsβ
Running Testsβ
cd shared
pytest tests/ --cov=utils
Test Structureβ
shared/tests/
βββ utils/
βββ test_crypto.py # Encryption/decryption tests
βββ test_sensitive_data_masker.py # Sensitive data masking tests
Key Features Testedβ
- Cryptography: Encryption and decryption of sensitive data (Git tokens, API keys)
- Data Masking: Automatic masking of sensitive information in logs and outputs
- GitHub tokens (
github_pat_*) - Anthropic API keys (
sk-ant-api03-*) - OpenAI API keys
- Generic API keys and secrets
- File path protection (no false positives)
- URL protection (no false positives)
- GitHub tokens (
Frontend Testsβ
Running Testsβ
cd Wegent
pnpm --dir frontend test # Run all tests
pnpm --dir frontend run test:watch # Watch mode
pnpm --dir frontend run test:coverage # With coverage report
Test Structureβ
frontend/src/__tests__/
βββ utils/ # Utility function tests
βββ hooks/ # React hooks tests
βββ components/ # Component tests
Continuous Integrationβ
GitHub Actions Workflowβ
The test and cache workflows run automatically as follows:
.github/workflows/test.ymlresponds to PRs targetingmainand merge-queue checks..github/workflows/ci-cache-warmup.ymlprewarms shared caches after related dependencies, source, or CI configuration entermain.
CI first runs .github/scripts/classify-ci-changes.sh to select module jobs from
the changed paths. Dependency relationships are included. For example, a
shared/ change runs the Backend, Executor Manager, Knowledge Engine, Shared,
and CLI suites, while a packages/chat-core/ change runs both Frontend and
Wework checks. Changes to the test workflows or the classifier itself select
every module so CI orchestration changes receive full validation.
Apply the ci:all label to a PR to bypass path classification and force every
module test, the platform E2E suite, Wework browser and desktop E2E, and the
Wework macOS memory gate. This is useful when changed paths might not capture
the full blast radius of a change. All three test workflows respond to label
events; without ci:all, test selection still follows the changed paths.
ci:memory continues to trigger only the Wework macOS memory gate.
Feature-branch pushes do not run a duplicate CI suite; the pull_request event
validates the branch after a PR is opened. A newer commit to the same PR or to
main cancels the older in-progress run. test-summary and lint-summary
always appear and verify that every selected job actually succeeded. Skipped,
unrelated modules do not make the summary fail.
CI Cache Ownershipβ
Large caches are prewarmed and owned by main. Pull requests and merge-queue
runs may restore the default-branch cache, but they do not save another copy:
- Linux workspace
node_moduleshas one shared cache keyed by the Node version andpnpm-lock.yaml. Tests, Lint, platform E2E, and Wework E2E reuse it. - uv caches downloads and build results only, not project
.venvdirectories. Python 3.10, 3.11, and 3.12 each have one shared cache whose key includes the dependency lockfiles, and CI pruning runs before save. - Playwright browsers, the Next.js build cache, the Claude Code CLI, and desktop
Cargo targets use explicit restore/save steps; only
refs/heads/mainsaves. The Claude Code CLI uses a repositorypackage-lock.jsonto pin the complete dependency graph and package integrity. - Rust unit tests, the Windows check, release and snapshot binaries, and the
macOS memory gate use sccache. The
mainwarmup runs the same desktop--build-onlyflow onmacos-14; non-mainjobs access the shared compiler cache in read-only mode. - Wework Desktop Core E2E retains its
main-owned Cargo target cache because several desktop jobs must reuse the same complete binary output. - Platform E2E, Release, and Snapshot Docker BuildKit caches live in corresponding GHCR build-cache tags instead of consuming GitHub Actions dependency cache storage.
- Executor and Tauri system dependency installers first check packages already
present on the runner. Only
mainwrites the APT download cache; PRs restore it read-only.
.github/workflows/ci-cache-warmup.yml runs after related source, dependency
lockfile, or cache implementation changes enter main, and rejects manual
warmups from non-main refs. It downloads dependencies or compiles cache
entries without repeating tests already validated by the merge queue. Do not add large automatically saved PR or
merge-group caches. Use actions/cache/restore plus a main-only
actions/cache/save, or reuse the existing shared action.
Writing GitHub Actions Outputsβ
Every line written to $GITHUB_OUTPUT must use the name=value format. Do not
write the substituted output of a command that can emit progress or diagnostic
messages directly to that file. Extract the needed value first, then write the
output. For example, to resolve the Playwright version:
version="$(pnpm exec playwright --version | sed -n 's/^Version //p')"
echo "version=$version" >> "$GITHUB_OUTPUT"
This prevents package-manager supply-chain checks or other auxiliary logs from invalidating the workflow output format.
Workflow Jobsβ
Regular PR checks must cover module tests; E2E jobs do not replace them:
- test-backend runs the Backend suite with Python 3.11 and
uv run pytest. - test-executor runs
cargo fmt --check,cargo test --all-features, and Clippy for the Rust Executor. It placesCARGO_TARGET_DIRunder the runner's temporary directory to avoid shared build contamination and workspace disk pressure. - test-executor-manager, test-shared, and test-knowledge-engine run
their Python suites with
uv run pytest. - test-frontend runs the Chat Core and Frontend unit tests.
- test-wework runs the Wework unit suite with Vitest and generates coverage.
- test-wegent-cli and test-wegent-cli-integration run CLI unit tests and integration tests backed by MySQL, Redis, and a real Backend, respectively.
- test-summary always runs and depends on every job above. It must fail when any module test job fails.
E2E coverage lives in dedicated workflows: .github/workflows/e2e-tests.yml
covers product end-to-end flows, while .github/workflows/wework-e2e.yml covers
Wework flows. They add cross-module verification and do not replace the module
tests in .github/workflows/test.yml. Platform E2E runs only when Backend,
Frontend, Executor, Executor Manager, Shared, Chat Shell, Chat Core, Docker, or
related build configuration changes, so a Wework-only PR no longer starts an
unrelated platform E2E suite. Draft PRs skip expensive E2E and run it after they
become ready for review.
The full platform E2E suite runs daily at 02:00 UTC, and the full Wework E2E suite runs daily at 04:00 UTC. Scheduled runs use a different concurrency group from PR and merge-queue runs, so they do not cancel each other.
Coverage Reportsβ
Coverage reports are automatically uploaded to Codecov (if configured).
Mocking Strategyβ
External APIsβ
- GitHub/GitLab/Gitee: Mock with
httpx-mockorpytest-mock - Anthropic/OpenAI: Mock SDK clients
- Redis: Use
fakeredisor mock
Databaseβ
- Test DB: SQLite in-memory database
- Isolation: Each test gets a fresh transaction
- Cleanup: Automatic rollback after each test
Dockerβ
- Mock
docker.from_env()and container operations
Best Practicesβ
Writing Testsβ
- One assertion per test: Each test should verify one specific behavior
- Descriptive names: Use clear, descriptive test function names that explain what is being tested
- AAA pattern: Arrange, Act, Assert - structure your tests clearly
- Mock external dependencies: Never call real external services (APIs, databases, etc.)
- Use fixtures: Share common test setup via fixtures to reduce duplication
- Test edge cases: Include tests for error conditions, boundary values, and unusual inputs
- Keep tests independent: Each test should be able to run independently without relying on other tests
Security Testing Best Practicesβ
The project includes comprehensive security testing examples in backend/tests/core/test_security.py:
- Password hashing and verification (bcrypt)
- JWT token creation and validation
- Token expiration handling
- User authentication with valid/invalid credentials
- Inactive user detection
- Role-based access control (admin vs regular users)
Example test pattern for security features:
@pytest.mark.unit
class TestPasswordHashing:
"""Test password hashing and verification functions"""
def test_verify_password_with_correct_password(self):
"""Test password verification with correct password"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password(password, hashed) is True
def test_verify_password_with_incorrect_password(self):
"""Test password verification with incorrect password"""
password = "testpassword123"
hashed = get_password_hash(password)
assert verify_password("wrongpassword", hashed) is False
Test Organizationβ
@pytest.mark.unit
class TestFeatureName:
"""Test feature description"""
def test_success_case(self):
"""Test successful operation"""
# Arrange
data = {"key": "value"}
# Act
result = function_under_test(data)
# Assert
assert result == expected_value
def test_error_case(self):
"""Test error handling"""
with pytest.raises(ExpectedException):
function_under_test(invalid_data)
Using Test Markersβ
Test markers help categorize and selectively run tests:
# Run only unit tests
pytest -m unit
# Run only integration tests
pytest -m integration
# Run slow tests
pytest -m slow
# Skip slow tests
pytest -m "not slow"
Async Testsβ
@pytest.mark.asyncio
async def test_async_function():
"""Test asynchronous function"""
result = await async_function()
assert result is not None
The backend's pytest.ini has asyncio_mode = auto which automatically detects and runs async tests.
Adding New Testsβ
Backendβ
- Create test file in appropriate
tests/subdirectory (e.g.,tests/services/test_new_service.py) - Import necessary fixtures from
conftest.py - Use
@pytest.mark.unitor@pytest.mark.integrationto categorize tests - Follow the AAA (Arrange-Act-Assert) pattern
- Write test classes and methods with descriptive names
- Run tests locally before committing:
pytest tests/ -v - Ensure coverage is maintained or improved:
pytest --cov=app --cov-report=term-missing
Frontendβ
- Create test file in
src/__tests__/matching source structure - Use
@testing-library/reactfor component tests - Mock API calls and external dependencies
- Ensure tests pass with
pnpm --filter wecode-ai-assistant test
Debugging Testsβ
Backendβ
# Run specific test with verbose output
pytest tests/core/test_security.py::TestPasswordHashing::test_verify_password_with_correct_password -v
# Drop into debugger on failure
pytest --pdb
# Show print statements
pytest -s
Frontendβ
# Run tests in watch mode
pnpm --dir frontend run test:watch
# Debug specific test file
pnpm --dir frontend test -- src/__tests__/utils/test_example.test.ts
Configuration Filesβ
Backendβ
backend/pytest.ini: pytest configuration with coverage settings and test markers- Enables verbose output, strict markers, and automatic async mode
- Configures coverage reports in terminal, HTML, and XML formats
- Defines custom markers:
unit,integration,slow
Executor/Executor Manager/Sharedβ
pytest.ini: Module-specific pytest configuration- Similar setup to backend but with module-specific coverage targets
Frontendβ
frontend/jest.config.ts: Jest configurationfrontend/jest.setup.js: Test environment setup
Future Improvementsβ
- Increase coverage to 70-80%
- Add integration tests for API endpoints (currently placeholder)
- Add E2E tests for critical user flows
- Performance/load testing
- Mutation testing with
mutmut - Add more frontend component tests
- Implement database migration tests
- Add tests for WebSocket connections and real-time features
Troubleshootingβ
Common Issuesβ
Import errors in tests:
- Ensure you're running pytest from the correct directory
- Check that modules are installed:
uv sync
Database errors:
- Tests use SQLite in-memory DB, no setup needed
- Check that fixtures are imported correctly
Frontend test failures:
- Ensure Node.js 20+ is installed
- Run
pnpm install --frozen-lockfilefrom the repository root to install exact dependency versions - Clear Jest cache:
pnpm --dir frontend exec jest --clearCache