--- id: prowler-cloud/prowler/prowler-test-api version: "de3012bb" license: Apache-2.0 install: manual updated: 2026-07-27 --- # prowler-test-api — This skill equips you with battle-tested patterns for writing Prowler API tests, covering JSON:API request formatting, cross-tenant isolation via row-level security, role-based access control, and Celery task mocking. It includes a fixture dependency chain, response status code reference, and explicit rules for avoiding common pitfalls like TruffleHog false positives and incorrect content-type headers. Publisher: prowler-cloud · Stars: 14491 · Updated: 2026-07-27 Install (manual): `git clone https://github.com/prowler-cloud/prowler` ## SKILL.md ## Critical Rules - ALWAYS use `response.json()["data"]` not `response.data` - ALWAYS use `content_type = "application/vnd.api+json"` for PATCH/PUT requests - ALWAYS use `format="vnd.api+json"` for POST requests - ALWAYS test cross-tenant isolation - RLS returns 404, NOT 403 - NEVER skip RLS isolation tests when adding new endpoints - NEVER use realistic-looking API keys in tests (TruffleHog will flag them) - ALWAYS mock BOTH `.delay()` AND `Task.objects.get` for async task tests --- ## 1. Fixture Dependency Chain ```text create_test_user (session) ─► tenants_fixture (function) ─► authenticated_client │ └─► aws_provider ─► scans_fixture ─► findings_fixture ``` ### Key Fixtures | Fixture | Description | |---------|-------------| | `create_test_user` | Session user (`dev@prowler.com`) | | `tenants_fixture` | 3 tenants: [0],[1] have membership, [2] isolated | | `authenticated_client` | Django test client with JWT for tenant[0] | | `authenticated_client_for_tenant_factory` | Creates a Django test client with JWT for a specific user and tenant | | `provider_factory` | Creates one validated provider with provider-specific defaults | | `aws_provider` | 1 AWS provider in tenant[0] | | `aws_provider_pair` | 2 AWS providers in tenant[0] | | `all_provider_types_fixture` | 1 provider for every supported provider type | | `tasks_fixture` | 2 Celery tasks with TaskResult | ### RBAC Fixtures | Fixture | Permissions | |---------|-------------| | `authenticated_client_rbac` | All permissions (admin) | | `authenticated_client_rbac_noroles` | Membership but NO roles | | `authenticated_client_no_permissions_rbac` | All permissions = False | Use `authenticated_client` for normal view behavior tests. It uses a cheap JWT and still runs the real request authentication path. Use serializer-generated JWTs or API-key clients only when the test is specifically about token obtain/refresh, invalid tokens, expired tokens, tenant switching by token, API keys, or unauthenticated 401 behavior. Use `authenticated_client_for_tenant_factory` when a test needs a cheap JWT client for a different user or tenant. --- ## 2. JSON:API Requests ### POST (Create) ```python response = client.post( reverse("provider-list"), data={"data": {"type": "providers", "attributes": {...}}}, format="vnd.api+json", # NOT content_type! ) ``` ### PATCH (Update) ```python response = client.patch( reverse("provider-detail", kwargs={"pk": provider.id}), data={"data": {"type": "providers", "id": str(provider.id), "attributes": {...}}}, content_type="application/vnd.api+json", # NOT format! ) ``` ### Reading Responses ```python data = response.json()["data"] attrs = data["attributes"] errors = response.json()["errors"] # For 400 responses ``` --- ## 3. RLS Isolation (Cross-Tenant) **RLS returns 404, NOT 403** - the resource is invisible, not forbidden. ```python def test_cross_tenant_access_denied(self, authenticated_client, tenants_fixture): other_tenant = tenants_fixture[2] # Isolated tenant foreign_provider = Provider.objects.create(tenant_id=other_tenant.id, ...) response = authenticated_client.get(reverse("provider-detail", args=[foreign_provider.id])) assert response.status_code == status.HTTP_404_NOT_FOUND # NOT 403! ``` --- ## 4. Celery Task Testing ### Testing Strategies | Strategy | Use For | |----------|---------| | Mock `.delay()` + `Task.objects.get` | Testing views that trigger tasks | | `task.apply()` | Synchronous task logic testing | | Mock `chain`/`group` | Testing Canvas orchestration | | Mock `connection` | Testing `@set_tenant` decorator | | Mock `apply_async` | Testing Beat scheduled tasks | ### Why NOT `task_always_eager` | Problem | Impact | |---------|--------| | No task serialization | Misses argument type errors | | No broker interaction | Hides connection issues | | Different execution context | `self.request` behaves differently | **Instead, use:** `task.apply()` for sync execution, mocking for isolation. > **Full examples:** See [assets/api_test.py](assets/api_test.py) for `TestCeleryTaskLogic`, `TestCeleryCanvas`, `TestSetTenantDecorator`, `TestBeatScheduling`. --- ## 5. Fake Secrets (TruffleHog) ```python # BAD - TruffleHog flags these: api_key = "sk-test1234567890T3BlbkFJtest1234567890" # GOOD - obviously fake: api_key = "sk-fake-test-key-for-unit-testing-only" ``` --- ## 6. Response Status Codes | Scenario | Code | |----------|------| | Successful GET | 200 | | Successful POST | 201 | | Async operation (DELETE/scan trigger) | 202 | | Sync DELETE | 204 | | Validation error | 400 | | Missing permission (RBAC) | 403 | | RLS isolation / not found | 404 | --- ## Commands ```bash cd api && uv run pytest -x --tb=short cd api && uv run pytest -k "test_provider" cd api && uv run pytest api/src/backend/api/tests/test_rbac.py ``` --- ## Resources - **Full Examples**: See [assets/api_test.py](assets/api_test.py) for complete test patterns - **Fixture Reference**: See [references/test-api-docs.md](references/test-api-docs.md) - **Fixture Source**: `api/src/backend/conftest.py` [View on SkillFed](https://skillfed.io/prowler-cloud/prowler/prowler-test-api) · [View on GitHub](https://github.com/prowler-cloud/prowler)