diff --git a/CLAUDE.md b/CLAUDE.md index 5c16326..a78fc37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,3 +74,14 @@ Config files for each service live under `config//`. Persistent data is ## Testing Tests live in `tests/` (28 files). Use mocking (`pytest-mock`) for external system calls. Integration tests in `test_integration.py` require Docker services running. + +## AI Collaboration Rules (Claude Code) + +These rules apply to every Claude Code session in this repo: + +- **Read memory first** — load `/home/roof/.claude/projects/-home-roof/memory/MEMORY.md` and referenced files at session start. +- **Dev machine context** — you are already on pic0 (192.168.31.51), the dev machine. Execute commands here directly; do not ask the user to run them. +- **Use all available agents** — spawn specialized sub-agents (pic-remote, pic-qa, pic-architect, etc.) for tasks that match their description. +- **make is the only interface** — never call docker/docker-compose directly. All container lifecycle operations go through `make start`, `make stop`, `make build`, `make logs`, etc. +- **Test every new feature** — after implementing any change, run `make test` before considering the task done. +- **Test before commit** — the pre-commit hook enforces this, but run `make test` manually first and fix all failures before staging files. diff --git a/Makefile b/Makefile index 32b2250..ffbd5d0 100644 --- a/Makefile +++ b/Makefile @@ -223,17 +223,18 @@ restore: # ── Tests ───────────────────────────────────────────────────────────────────── test: - @echo "Running all tests..." - pytest tests/ api/tests/ + @echo "Running unit tests..." + python3 -m pytest tests/ --ignore=tests/e2e --ignore=tests/integration -q -test-all: - python3 api/tests/run_tests.py +test-all: test test-integration test-e2e-api test-e2e-ui + @echo "All test suites complete." test-unit: - pytest tests/ + python3 -m pytest tests/ --ignore=tests/e2e --ignore=tests/integration -q test-coverage: - pytest tests/ api/tests/ --cov=api --cov-report=html --cov-report=term-missing --cov-fail-under=70 -v + python3 -m pytest tests/ --ignore=tests/e2e --ignore=tests/integration \ + --cov=api --cov-report=html --cov-report=term-missing --cov-fail-under=70 -v test-integration: @echo "Running full integration tests (requires running PIC stack)..." @@ -244,14 +245,15 @@ test-integration-readonly: PIC_HOST=$${PIC_HOST:-localhost} python3 -m pytest tests/integration/test_live_api.py tests/integration/test_webui.py -v test-api: - cd api && python3 -m pytest tests/test_api_endpoints.py -v + python3 -m pytest tests/test_api_endpoints.py -v test-cli: - cd api && python3 -m pytest tests/test_cli_tool.py -v + python3 -m pytest tests/test_cli_tool.py -v # ── E2E tests ───────────────────────────────────────────────────────────────── # Run `make test-e2e-deps` once to install dependencies, then use the other targets. -# WG tests require wg-quick and run under sudo (passwordless sudo assumed on this host). +# Admin password is read from data/api/.test_admin_pass (written by reset-test-admin-pass). +# Override: make test-e2e-api PIC_ADMIN_PASS=mypassword test-e2e-deps: sudo pip3 install --break-system-packages -r tests/e2e/requirements.txt @@ -282,20 +284,6 @@ show-admin-password: reset-admin-password: @sudo python3 scripts/reset_admin_password.py --generate -test-phase1: - cd api && python3 -m pytest tests/test_network_manager.py tests/test_phase1_endpoints.py -v - -test-phase2: - cd api && python3 -m pytest tests/test_wireguard_manager.py tests/test_phase2_endpoints.py -v - -test-phase3: - cd api && python3 -m pytest tests/test_phase3_managers.py tests/test_phase3_endpoints.py -v - -test-phase4: - cd api && python3 -m pytest tests/test_phase4_routing.py tests/test_phase4_endpoints.py -v - -test-all-phases: - cd api && python3 -m pytest tests/ -v # ── Network / peers ─────────────────────────────────────────────────────────── diff --git a/scripts/reset_admin_password.py b/scripts/reset_admin_password.py index bda885f..a684d40 100644 --- a/scripts/reset_admin_password.py +++ b/scripts/reset_admin_password.py @@ -15,6 +15,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'api')) ROOT = os.path.join(os.path.dirname(__file__), '..') INIT_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.admin_initial_password')) +TEST_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.test_admin_pass')) def _generate_password(length: int = 20) -> str: @@ -88,13 +89,19 @@ def main() -> None: _set_password(password) - # Also update the initial password file so show-admin-password works + # Write the initial password file (API reads it on first start, then deletes it) os.makedirs(os.path.dirname(INIT_PW_FILE), exist_ok=True) with open(INIT_PW_FILE, 'w') as f: f.write(password) + # Write the persistent test password file (never deleted by the API) + with open(TEST_PW_FILE, 'w') as f: + f.write(password) + os.chmod(TEST_PW_FILE, 0o600) + _print_banner(password) print(f'\n Also saved to: {INIT_PW_FILE}') + print(f' Test file: {TEST_PW_FILE} (persists across API restarts)') print(' Restart the API container for the change to take effect:') print(' docker restart cell-api') diff --git a/tests/e2e/helpers/admin_password.py b/tests/e2e/helpers/admin_password.py index 2a35672..4bdd098 100644 --- a/tests/e2e/helpers/admin_password.py +++ b/tests/e2e/helpers/admin_password.py @@ -1,15 +1,17 @@ import os +_DATA_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'data', 'api')) + + def resolve_admin_password() -> str: p = os.environ.get('PIC_ADMIN_PASS', '').strip() if p: return p - candidate = os.path.normpath( - os.path.join(os.path.dirname(__file__), '..', '..', '..', 'data', 'api', '.admin_initial_password') - ) - if os.path.exists(candidate): - return open(candidate).read().strip() + for fname in ('.test_admin_pass', '.admin_initial_password'): + candidate = os.path.join(_DATA_DIR, fname) + if os.path.exists(candidate): + return open(candidate).read().strip() raise RuntimeError( "Admin password unknown. Set PIC_ADMIN_PASS env var or run: " "make reset-test-admin-pass PIC_TEST_ADMIN_PASS=" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 2c8ed45..7713ac0 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -35,18 +35,19 @@ TEST_PEERS = ( TEST_PEER_PASSWORD = 'IntegrationTest123!' +_DATA_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'data', 'api')) + + def _resolve_admin_pass() -> str: if ADMIN_PASS: return ADMIN_PASS - # Try reading from the initial password file (present on first run before bootstrap) - candidate = os.path.join( - os.path.dirname(__file__), '..', '..', 'data', 'api', '.admin_initial_password' - ) - candidate = os.path.normpath(candidate) - if os.path.exists(candidate): - return open(candidate).read().strip() + for fname in ('.test_admin_pass', '.admin_initial_password'): + candidate = os.path.join(_DATA_DIR, fname) + if os.path.exists(candidate): + return open(candidate).read().strip() raise RuntimeError( - "Admin password unknown. Set PIC_ADMIN_PASS env var or run make setup first." + "Admin password unknown. Set PIC_ADMIN_PASS env var or run: " + "make reset-test-admin-pass PIC_TEST_ADMIN_PASS=" )