229 lines
8.0 KiB
Markdown
229 lines
8.0 KiB
Markdown
|
|
# Secuird Integration Test Suite
|
||
|
|
|
||
|
|
This directory contains the integration test suite for the Secuird IAM platform.
|
||
|
|
|
||
|
|
## Quick Start
|
||
|
|
|
||
|
|
Run all integration tests:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd backend
|
||
|
|
pytest tests/integration/
|
||
|
|
```
|
||
|
|
|
||
|
|
Run a specific test file:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pytest tests/integration/test_ssh_workflows.py -v
|
||
|
|
```
|
||
|
|
|
||
|
|
Run without coverage (faster):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pytest tests/integration/ --no-cov
|
||
|
|
```
|
||
|
|
|
||
|
|
Fail fast (stop on first failure):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pytest tests/integration/ -x
|
||
|
|
```
|
||
|
|
|
||
|
|
Run previously failed tests first:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pytest tests/integration/ --ff
|
||
|
|
```
|
||
|
|
|
||
|
|
## Test Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
tests/
|
||
|
|
├── conftest.py # Base pytest fixtures (app, client, test_user)
|
||
|
|
├── integration/ # Integration tests
|
||
|
|
│ ├── conftest.py # Integration-specific fixtures and factories
|
||
|
|
│ ├── client/ # Reusable API client library
|
||
|
|
│ │ ├── base.py # SecuirdClient with session management
|
||
|
|
│ │ ├── auth.py # Authentication operations
|
||
|
|
│ │ ├── users.py # User self-service operations
|
||
|
|
│ │ ├── orgs.py # Organization operations
|
||
|
|
│ │ ├── ssh.py # SSH key/cert operations
|
||
|
|
│ │ ├── mfa.py # TOTP/WebAuthn operations
|
||
|
|
│ │ ├── zerotier.py # ZeroTier network operations
|
||
|
|
│ │ └── admin.py # Admin operations
|
||
|
|
│ ├── fixtures/ # Test data and helpers
|
||
|
|
│ │ ├── ssh_keys.py # Test SSH key pairs and helpers
|
||
|
|
│ │ └── test_data.py # Common test data generators
|
||
|
|
│ ├── test_auth_flows.py # Authentication flows (24 tests)
|
||
|
|
│ ├── test_totp_workflows.py # TOTP MFA flows (15 tests)
|
||
|
|
│ ├── test_ssh_workflows.py # SSH key/cert flows (34 tests)
|
||
|
|
│ ├── test_org_workflows.py # Organization & invite flows (27 tests)
|
||
|
|
│ ├── test_multi_org.py # Multi-organization access (4 tests)
|
||
|
|
│ ├── test_self_service.py # User self-service features (9 tests)
|
||
|
|
│ ├── test_admin_ops.py # Admin user management (9 tests)
|
||
|
|
│ ├── test_authorization.py # RBAC & access control (8 tests)
|
||
|
|
│ ├── test_security.py # Security & edge cases (5 tests)
|
||
|
|
│ ├── test_dept_principal.py # Department & principal management (5 tests)
|
||
|
|
│ ├── test_ca_management.py # Certificate authority management (4 tests)
|
||
|
|
│ ├── test_policy_compliance.py # Security policy & compliance (4 tests)
|
||
|
|
│ ├── test_webauthn_workflows.py# WebAuthn passkey flows (5 tests)
|
||
|
|
│ └── test_zerotier.py # ZeroTier network access (8 tests)
|
||
|
|
└── unit/ # Unit tests (existing)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Environment
|
||
|
|
|
||
|
|
- **Python**: 3.10+
|
||
|
|
- **Database**: SQLite in-memory (`sqlite:///:memory:`)
|
||
|
|
- **Rate Limiting**: Disabled in tests (`RATELIMIT_ENABLED = False`)
|
||
|
|
- **CSRF**: Disabled (`WTF_CSRF_ENABLED = False`)
|
||
|
|
- **Email**: Suppressed (`MAIL_SUPPRESS_SEND = True`)
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
The `pytest.ini` file configures:
|
||
|
|
|
||
|
|
- Verbose output (`-v`)
|
||
|
|
- Coverage reporting (`--cov=gatehouse_app`)
|
||
|
|
- Disabled maas plugins that cause import errors (see Known Issues below)
|
||
|
|
- Custom markers for `unit`, `integration`, `slow`, etc.
|
||
|
|
|
||
|
|
## Coverage
|
||
|
|
|
||
|
|
Coverage reports are generated automatically:
|
||
|
|
|
||
|
|
- **Terminal**: printed after each run
|
||
|
|
- **HTML**: `backend/htmlcov/index.html`
|
||
|
|
|
||
|
|
Target coverage: **85% minimum**.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pytest tests/integration/ --cov=gatehouse_app --cov-fail-under=85
|
||
|
|
```
|
||
|
|
|
||
|
|
## Known Issues
|
||
|
|
|
||
|
|
### maastesting Plugin Import Error
|
||
|
|
|
||
|
|
The `maas` system package installs pytest entry points that fail to load in this environment. The `pytest.ini` file disables them automatically with:
|
||
|
|
|
||
|
|
```ini
|
||
|
|
-p no:maas-django
|
||
|
|
-p no:maas-perftest
|
||
|
|
-p no:maas-seeds
|
||
|
|
```
|
||
|
|
|
||
|
|
If you see `ModuleNotFoundError: No module named 'maastesting'`, these flags are not being applied. Ensure you run pytest from the `backend/` directory.
|
||
|
|
|
||
|
|
### ssh-keygen Not Available
|
||
|
|
|
||
|
|
One test (`test_verify_key_positive` in `test_ssh_workflows.py`) requires `ssh-keygen` to generate real Ed25519 key pairs for signature verification. It is automatically skipped when `ssh-keygen` is not available:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
sudo apt-get install openssh-client # Debian/Ubuntu
|
||
|
|
```
|
||
|
|
|
||
|
|
Other certificate signing tests use a DB helper (`_mark_key_verified`) to bypass the signature requirement in CI environments.
|
||
|
|
|
||
|
|
## Writing New Tests
|
||
|
|
|
||
|
|
### Pattern
|
||
|
|
|
||
|
|
Every test must include a verbose docstring with `WHAT`, `WHY`, and `EXPECTED`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def test_add_key_positive(self, integration_client, create_test_user):
|
||
|
|
"""TEST: SSH-KEY-01 — Add a new SSH public key.
|
||
|
|
|
||
|
|
WHAT: Authenticated user POSTs a valid public key with a description.
|
||
|
|
WHY: Users must be able to register their SSH keys for later
|
||
|
|
certificate signing and server access.
|
||
|
|
EXPECTED: 201 Created, response contains key id and metadata.
|
||
|
|
"""
|
||
|
|
```
|
||
|
|
|
||
|
|
### Fixtures
|
||
|
|
|
||
|
|
| Fixture | Purpose |
|
||
|
|
|---------|---------|
|
||
|
|
| `integration_client` | Fresh `SecuirdClient` instance per test |
|
||
|
|
| `create_test_user` | Factory returning `{"id", "email", "password", "full_name"}` |
|
||
|
|
| `create_test_org` | Factory returning `{"id", "name", "slug"}` |
|
||
|
|
| `create_test_membership` | Links user to org with a role |
|
||
|
|
| `create_test_ca` | Creates a Certificate Authority for an org |
|
||
|
|
|
||
|
|
### Client Usage
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Authentication
|
||
|
|
integration_client.auth.register(email, password, full_name)
|
||
|
|
integration_client.auth.login(email, password)
|
||
|
|
integration_client.auth.logout()
|
||
|
|
|
||
|
|
# SSH
|
||
|
|
integration_client.ssh.add_key(public_key, description)
|
||
|
|
integration_client.ssh.sign_certificate(key_id=key_id, principals=["deploy"])
|
||
|
|
integration_client.ssh.revoke_certificate(cert_id)
|
||
|
|
|
||
|
|
# Organizations
|
||
|
|
integration_client.orgs.create(name, slug)
|
||
|
|
integration_client.orgs.create_principal(org_id, name)
|
||
|
|
integration_client.orgs.create_ca(org_id, name, ca_type="user")
|
||
|
|
```
|
||
|
|
|
||
|
|
### Assertions
|
||
|
|
|
||
|
|
Use the standard helpers:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def assert_success(response: dict, message_contains: str = "") -> dict:
|
||
|
|
data = response.get("data", {})
|
||
|
|
assert response.get("success") is not False
|
||
|
|
if message_contains:
|
||
|
|
assert message_contains.lower() in response.get("message", "").lower()
|
||
|
|
return data
|
||
|
|
|
||
|
|
# Negative tests
|
||
|
|
with pytest.raises(ApiError) as exc_info:
|
||
|
|
integration_client.ssh.get_key(str(uuid.uuid4()))
|
||
|
|
assert exc_info.value.status_code == 404
|
||
|
|
assert exc_info.value.error_type == "NOT_FOUND"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Test Counts
|
||
|
|
|
||
|
|
| Module | Tests | Focus |
|
||
|
|
|--------|-------|-------|
|
||
|
|
| test_auth_flows.py | 24 | Registration, login, logout, sessions, password reset, email verification |
|
||
|
|
| test_totp_workflows.py | 15 | TOTP enrollment, verification, backup codes, disable, regenerate |
|
||
|
|
| test_ssh_workflows.py | 34 | Key CRUD, verification, certificate signing & management |
|
||
|
|
| test_org_workflows.py | 27 | Org CRUD, members, roles, invites, ownership transfer |
|
||
|
|
| test_multi_org.py | 4 | Cross-org isolation, role-based access |
|
||
|
|
| test_self_service.py | 9 | Profile, password change, account deletion |
|
||
|
|
| test_admin_ops.py | 9 | Suspend, unsuspend, verify email, set password, remove MFA, hard delete |
|
||
|
|
| test_authorization.py | 8 | RBAC, cross-user isolation, soft-delete behavior |
|
||
|
|
| test_security.py | 5 | SQL injection, XSS, oversized payload, malformed JSON, empty body |
|
||
|
|
| test_dept_principal.py | 5 | Department/principal CRUD, membership, linking |
|
||
|
|
| test_ca_management.py | 4 | CA creation, listing, rotation |
|
||
|
|
| test_policy_compliance.py | 4 | Security policy, MFA compliance |
|
||
|
|
| test_webauthn_workflows.py | 5 | WebAuthn registration/login (mocked) |
|
||
|
|
| test_zerotier.py | 8 | Network CRUD, devices, approvals, memberships (mocked) |
|
||
|
|
| **Total** | **162** | |
|
||
|
|
|
||
|
|
## Pre-Commit Checklist
|
||
|
|
|
||
|
|
Before committing backend changes:
|
||
|
|
|
||
|
|
1. Run the integration suite: `pytest tests/integration/ -x`
|
||
|
|
2. Verify coverage hasn't decreased: `pytest tests/integration/ --cov=gatehouse_app --cov-fail-under=85`
|
||
|
|
3. If tests fail, fix before committing
|
||
|
|
|
||
|
|
## CI/CD
|
||
|
|
|
||
|
|
Integration tests run automatically on:
|
||
|
|
- Every pull request
|
||
|
|
- Every push to main
|
||
|
|
- Nightly builds
|
||
|
|
|
||
|
|
**Failure policy**: Integration test failures block merging.
|