- Refactor CORS middleware to echo request origin when wildcard + credentials is configured (browsers reject Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true) - Add _is_origin_allowed() and _cors_origin_header() helpers - Use CORS_SUPPORTS_CREDENTIALS config consistently - Ensure consistent Access-Control-Allow-Headers in all CORS paths - Fix redirect validation in get_token() to allow wildcard CORS origins - Add 46 unit tests covering encryption round-trips, idempotency, key derivation, thread safety, CORS origin matching, and preflight responses
Secuird Integration Test Suite
This directory contains the integration test suite for the Secuird IAM platform.
Quick Start
Run all integration tests:
cd backend
pytest tests/integration/
Run a specific test file:
pytest tests/integration/test_ssh_workflows.py -v
Run without coverage (faster):
pytest tests/integration/ --no-cov
Fail fast (stop on first failure):
pytest tests/integration/ -x
Run previously failed tests first:
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.
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:
-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:
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:
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
# 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:
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:
- Run the integration suite:
pytest tests/integration/ -x - Verify coverage hasn't decreased:
pytest tests/integration/ --cov=gatehouse_app --cov-fail-under=85 - 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.