Skip to main content Home Skills Automation & Agents prowler-test-api prowler-test-api prowler-cloud
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).
bunx add-skill prowler-cloud/prowler -s prowler-test-api Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task aws azure cis-benchmark cloud cloudsecurity compliance
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
create_test_user (session) ββΊ tenants_fixture (function) ββΊ authenticated_client
β
βββΊ providers_fixture ββΊ scans_fixture ββΊ findings_fixture
Key Fixtures Fixture Description create_test_userSession user (dev@prowler.com) tenants_fixture3 tenants: [0],[1] have membership, [2] isolated authenticated_clientJWT client for tenant[0] providers_fixture9 providers in tenant[0] tasks_fixture2 Celery tasks with TaskResult
RBAC Fixtures Fixture Permissions authenticated_client_rbacAll permissions (admin) authenticated_client_rbac_norolesMembership but NO roles authenticated_client_no_permissions_rbacAll permissions = False
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 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 && poetry run pytest -x --tb=short
cd api && poetry run pytest -k "test_provider"
cd api && poetry run pytest api/src/backend/api/tests/test_rbac.py
Resources