/prowler-test-api
Testing patterns for Prowler API: JSON:API, Celery tasks, RLS isolation, RBAC. Trigger: When writing tests for api/ (JSON:API requests/assertions, cross-tenant isolation, RBAC, Celery tasks, viewsets/serializers).
$ npx -y skills add prowler-cloud/prowler --skill prowler-test-api --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/prowler-test-api
Context preview
The summary Claude sees to decide when to auto-load this skill.
Testing patterns for Prowler API: JSON:API, Celery tasks, RLS isolation, RBAC. Trigger: When writing tests for api/ (JSON:API requests/assertions, cross-tenant isolation, RBAC, Celery tasks, viewsets/serializers).
SKILL.md
prowler-test-api.SKILL.mdname: prowler-test-api
description: >
Testing patterns for Prowler API: JSON:API, Celery tasks, RLS isolation, RBAC.
Trigger: When writing tests for api/ (JSON:API requests/assertions, cross-tenant isolation, RBAC, Celery tasks, viewsets/serializers).
license: Apache-2.0
metadata:
author: prowler-cloud
version: "1.1.0"
scope: [root, api]
auto_invoke:
- "Writing Prowler API tests"
- "Testing RLS tenant isolation"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, TaskCritical 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
create_test_user (session) ─► tenants_fixture (function) ─► authenticated_client
│
└─► aws_provider ─► scans_fixture ─► findings_fixtureKey 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)
response = client.post(
reverse("provider-list"),
data={"data": {"type": "providers", "attributes": {...}}},
format="vnd.api+json", # NOT content_type!
)PATCH (Update)
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
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.
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)
# 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
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`
Read more
name: prowler-test-api
description: >
Testing patterns for Prowler API: JSON:API, Celery tasks, RLS isolation, RBAC.
Trigger: When writing tests for api/ (JSON:API requests/assertions, cross-tenant isolation, RBAC, Celery tasks, viewsets/serializers).
license: Apache-2.0
metadata:
author: prowler-cloud
version: "1.1.0"
scope: [root, api]
auto_invoke:
- "Writing Prowler API tests"
- "Testing RLS tenant isolation"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, TaskCritical 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
create_test_user (session) ─► tenants_fixture (function) ─► authenticated_client
│
└─► aws_provider ─► scans_fixture ─► findings_fixtureKey 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)
response = client.post(
reverse("provider-list"),
data={"data": {"type": "providers", "attributes": {...}}},
format="vnd.api+json", # NOT content_type!
)PATCH (Update)
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
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.
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)
# 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
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`
Prowler is the world’s most widely used Open-Source Cloud Security Platform that automates security and compliance across any cloud environment.
Repo: prowler-cloud/prowler
Other skills on prowler.
- /framework-compliance-triage
Make a cloud account compliant with a security or industry framework using Prowler Cloud.
Open skill - /ai-sdk-5
Vercel AI SDK 5 patterns. Trigger: When building AI features with AI SDK v5 (chat, streaming, tools/function calling, UIMessage parts), including migration from v4.
Open skill - /django-drf
Django REST Framework patterns. Trigger: When implementing generic DRF APIs (ViewSets, serializers, routers, permissions, filtersets). For Prowler API specifics (RLS/RBAC/Providers), also use prowler-api.
Open skill - /django-migration-psql
Reviews Django migration files for PostgreSQL best practices specific to Prowler. Trigger: When creating migrations, running makemigrations/pgmakemigrations, reviewing migration PRs, adding indexes or constraints to database tables, modifying existing migration files, or writing
Open skill - /gh-aw
Create and maintain GitHub Agentic Workflows (gh-aw) for Prowler. Trigger: When creating agentic workflows, modifying gh-aw frontmatter, configuring safe-outputs, setting up MCP servers in workflows, importing Copilot Custom Agents, or debugging gh-aw compilation.
Open skill - /jsonapi
Strict JSON:API v1.1 specification compliance. Trigger: When creating or modifying API endpoints, reviewing API responses, or validating JSON:API compliance.
Open skill

