testing-guide.md
1 # Testing Guide 2 3 All tests run **inside Docker** via `docker-compose.test.yml` (root → drops to ag3ntum_api via `setpriv --init-groups`, `AG3NTUM_TEST_MODE=true`). 4 5 **Test entrypoint** (`entrypoint-test.sh`): Installs test sudoers, syncs Linux users from DB, creates fully-equipped test users (`ag3ntum_tester_a` UID 59990, `ag3ntum_tester_b` UID 59991) with DB entries, venvs, persistent storage, and shared GID memberships, then drops privileges. Test users are at the high end of the isolated range to avoid conflicts with real users. Credentials: email `ag3ntum_tester_a@test.local`, password `TestPassword123!`. 6 7 --- 8 9 ## Writing Backend Tests 10 11 ```python 12 # tests/backend/test_<module>.py 13 class TestFeature: 14 @pytest.mark.unit 15 async def test_behavior(self, test_app, auth_headers): 16 response = await test_app.get("/endpoint", headers=auth_headers) 17 assert response.status_code == 200 18 19 @pytest.mark.e2e 20 async def test_full_flow(self, test_app): ... 21 ``` 22 23 **Backend fixtures** (`conftest.py`): `db_engine`/`db_session` (in-memory SQLite) | `test_app` (FastAPI client) | `auth_headers` (JWT) | `mock_agent_runner` | `temp_session_dir` | `test_user_manager` 24 25 **Redis fixtures** (`redis/conftest.py`): `redis_connection` | `event_hub` | `tracer_factory` 26 27 --- 28 29 ## Writing E2E / Functional Tests (Real Users) 30 31 Tests that need real Linux users (sandbox execution, filesystem permissions, mount access, user isolation) **must reuse pre-built test users**, not create them dynamically. 32 33 **Why**: The API process gets its supplementary groups at startup via `setpriv --init-groups`. Dynamically-created users add `ag3ntum_api` to the new user's group, but the already-running API process doesn't pick up the change. This causes `Permission denied` on session workspace directories. Restarting the container mid-test is not viable. 34 35 **Pre-built test users** (created by `entrypoint-test.sh`): 36 37 | Field | tester_a | tester_b | 38 |-------|----------|----------| 39 | Username | `ag3ntum_tester_a` | `ag3ntum_tester_b` | 40 | UID/GID | 59990 | 59991 | 41 | Email | `ag3ntum_tester_a@test.local` | `ag3ntum_tester_b@test.local` | 42 | Password | `TestPassword123!` | `TestPassword123!` | 43 | Home | `/users/ag3ntum_tester_a` | `/users/ag3ntum_tester_b` | 44 45 Both have: Linux accounts, DB entries, Python venvs, persistent storage, shared GID memberships, `.claude/skills/` dirs. 46 47 **Pattern for E2E tests**: 48 ```python 49 from types import SimpleNamespace 50 51 # Constants (reuse across test files) 52 PREBUILT_USER_A_USERNAME = "ag3ntum_tester_a" 53 PREBUILT_USER_A_UID = 59990 54 55 def _prebuilt_user(username: str, uid: int) -> SimpleNamespace: 56 return SimpleNamespace(username=username, linux_uid=uid) 57 58 # Fixture — no DB session needed 59 @pytest.fixture 60 def test_user(self) -> SimpleNamespace: 61 return _prebuilt_user(PREBUILT_USER_A_USERNAME, PREBUILT_USER_A_UID) 62 63 # For API auth, login with known credentials 64 response = await client.post("/auth/token", json={ 65 "email": "ag3ntum_tester_a@test.local", 66 "password": "TestPassword123!", 67 }) 68 ``` 69 70 **Rules**: 71 - Prefix test artifacts with `_test_` or `_e2e_` for easy cleanup 72 - Always clean up test files in fixture teardown (pre-built users persist across runs) 73 - Use `try/finally` for cleanup in test bodies that create files 74 - Only `TestRealUserCreation` in `test_real_user_integration.py` creates users dynamically (it tests the creation flow itself) 75 - For two-user isolation tests, use both `ag3ntum_tester_a` and `ag3ntum_tester_b` 76 77 --- 78 79 ## Frontend Test Infrastructure 80 81 Frontend tests (`./run.sh test --ui`) always run in **dev mode** regardless of the current deployment mode. The test runner: 82 83 1. Starts the web container with `docker-compose.dev.yml` overlay (Vite dev server + node_modules) 84 2. Runs `npm install` if needed (copies `package.json` to `/app/` as safety net) 85 3. Executes `vitest run` inside the container 86 4. Restores the previous deployment mode (prod/dev) after tests complete 87 88 This means UI tests work correctly even after a `./run.sh build` (production mode) — the test infrastructure automatically switches the web container to dev mode. 89 90 **If UI tests fail with `ENOENT /app/package.json`**: The web container may be running in prod mode without node_modules. Run `./run.sh test --ui` again — it will recreate the container in dev mode. 91 92 --- 93 94 ## Writing Frontend Tests 95 96 vitest + React Testing Library + MSW: 97 ```typescript 98 // tests/web_terminal_console/unit/<Component>.test.tsx 99 import { renderWithProviders } from '../utils/renderWithProviders'; 100 test('renders', () => { 101 renderWithProviders(<MyComponent />); 102 expect(screen.getByText('expected')).toBeInTheDocument(); 103 }); 104 ``` 105 Setup: `setup.ts` (MSW, jest-dom, window mocks). Mocks: `mocks/handlers.ts`. 106 107 --- 108 109 ## Reseller/Admin Integration Tests 110 111 Backend test suites for reselling features follow a pattern of HTTP integration tests using the FastAPI test client with fixtures from `conftest.py`. 112 113 **Key fixtures** (`tests/backend/conftest.py`): `client` (TestClient), `admin_auth_headers` (admin JWT), `reseller_auth_headers` (reseller JWT), `second_reseller_auth_headers` (for IDOR tests), `db` (async session). 114 115 **Test files** (Phase 1 + Phase 2): 116 - `test_reseller.py` — Service-layer reseller tests (CRUD, quotas, flags, spending) 117 - `test_reseller_http.py` — HTTP integration tests for reseller endpoints 118 - `test_reseller_config_http.py` — HTTP tests for reseller config/security/ssh endpoints 119 - `test_reseller_metrics_http.py` — WHMCS metrics + usage export HTTP tests 120 - `test_admin.py` — Admin service-layer tests + platform config 121 - `test_admin_http.py` — Admin HTTP integration tests (reseller lifecycle, suspension) 122 - `test_admin_config_http.py` — Platform config GET/PUT HTTP tests 123 - `test_cidr_and_audit.py` — CIDR IP allowlisting + audit log tests 124 - `test_webhook_service.py` — Webhook service unit tests (HMAC, delivery, retry) 125 - `test_webhook_http.py` — Webhook CRUD HTTP integration tests + IDOR 126 - `test_processor_lifecycle.py` — WebhookProcessor + RetentionProcessor lifecycle 127 - `test_feature_flag_service.py` — FeatureFlagService unit tests (defaults, overrides, DB ops) 128 - `test_data_retention.py` — Data retention config + purge tests 129 - `test_simplify_fixes.py` — Regression tests for code quality fixes 130 131 **QA playbooks** (manual testing): `docs/plans/enable-reselling/qa-test-playbook.md` (Phase 1, 96 tests), `docs/plans/enable-reselling/qa-test-stage2.md` (Phase 2/3, 81 tests). 132 133 --- 134 135 ## Test Output 136 137 `logs/latest-test-results.log` (overwritten each run): 138 ```bash 139 grep -A 10 "FAILED\|ERROR" logs/latest-test-results.log 140 ```