What We’re Testing
All export operations are handled by handleExportData in backend/src/handlers/export-data.ts, registered at:
POST /api/export
The handler accepts a JSON body with three required fields: action, org_id, and an optional format. When format is omitted (or anything other than "csv"), the response is JSON. Three actions exist:
| Action | Requires admin | Returns |
|---|---|---|
machines | Yes | { machines: [...], count: N } |
audit_logs | Yes | { logs: [...], count: N } |
compliance_report | Yes | Full compliance report object |
The handler enforces two access checks in sequence: first isOrgMember, then isOrgAdmin. A plain org member (non-admin) receives 403 FORBIDDEN even if they are authenticated.
Audit logs are read from Loki via queryAuditLogs (not from PostgreSQL). The days parameter controls the lookback window; it defaults to 30 if omitted or non-numeric.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | Admin — all API calls issued from here |
You will need:
- A valid JWT token for an org admin account. Obtain it via
POST /api/auth/login. - Your org ID (UUID). Find it in the dashboard URL or from
GET /api/user-orgs.
Store these in your shell session before running any sub-test:
TOKEN="eyJhbGciOiJFUzI1NiIsInR..." # your JWT
ORG_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
ST1 — Export Machines as JSON (Happy Path)
What it verifies: The machines action returns all org machines with the correct fields in a JSON envelope when format is not "csv".
Steps:
On ⊞ Win-A , run:
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"machines\",\"org_id\":\"$ORG_ID\"}" \
| python3 -m json.tool
Expected response structure:
{
"success": true,
"data": {
"machines": [
{
"id": "...",
"name": "Win-A",
"tailnet_ip": "100.x.x.x",
"os": "windows",
"status": "online",
"last_seen": "2026-03-17T10:00:00.000Z",
"public_key": "...",
"tags": "[]",
"advertised_routes": null,
"created_at": "2026-03-10T09:00:00.000Z"
}
],
"count": 3
}
}
The handler selects exactly these columns from the machines table:
id, name, tailnet_ip, os, status, last_seen, public_key, tags, advertised_routes, created_at
Results are ordered by name ASC.
Pass: success: true, data.machines is an array, data.count matches the array length. Each machine object contains all ten fields listed above.
Fail / Common issues:
401 UNAUTHORIZED— token is missing, expired, or malformed.400 MISSING_FIELDS—actionororg_idis absent from the body.403 FORBIDDENwith message “Not a member” — the user account does not belong to this org.403 FORBIDDENwith message “Admin required” — user is an org member but not an admin.
ST2 — Export Audit Logs as JSON (Happy Path)
What it verifies: The audit_logs action queries Loki and returns log entries in JSON format with the correct envelope.
Steps:
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"audit_logs\",\"org_id\":\"$ORG_ID\",\"days\":7}" \
| python3 -m json.tool
Expected response structure:
{
"success": true,
"data": {
"logs": [
{
"id": "...",
"action": "machine.registered",
"resource_type": "machine",
"resource_id": "...",
"user_id": "...",
"created_at": "2026-03-16T14:22:01.000Z",
"details": "{...}"
}
],
"count": 42
}
}
The days field is parsed with parseInt; if omitted or non-numeric it defaults to 30. The Loki query uses date_from set to now - days * 86400000ms and a hard limit of 10,000 entries.
Pass: success: true, data.logs is an array, data.count reflects the number of entries. Each log entry contains id, action, resource_type, resource_id, user_id, created_at, and details.
Fail / Common issues:
- Empty
logsarray withcount: 0— no audit events in the past 7 days for this org. Try"days": 90to widen the window. - Loki connectivity error surfaces as a 500 — check that the Loki service is reachable from the API container.
ST3 — Export Compliance Report (JSON)
What it verifies: The compliance_report action aggregates data from eight database tables plus Loki and returns a structured report.
Steps:
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"compliance_report\",\"org_id\":\"$ORG_ID\"}" \
| python3 -m json.tool
Expected top-level response keys:
{
"success": true,
"data": {
"generated_at": "2026-03-17T10:00:00.000Z",
"org_id": "...",
"report_type": "compliance",
"summary": {
"machines": { "total": 3, "online": 2, "quarantined": 0 },
"posture": { "total": 2, "compliant": 2, "compliance_rate": 100 },
"acl_rules": 5,
"abac_policies": 1,
"certificates": { "total": 4, "active": 3 },
"threats": { "total": 10, "blocked": 2 },
"ip_allowlist_entries": 3,
"quarantine_events_30d": 0
},
"posture_policy": { ... },
"non_compliant_machines": [],
"recent_quarantine_events": []
}
}
The compliance_rate is computed as Math.round((compliant / total) * 100). If no posture reports exist, it returns 100. The posture_policy key is null if no enabled posture policy exists.
Pass: success: true, all seven summary keys are present, report_type is "compliance", generated_at is an ISO 8601 timestamp.
Fail / Common issues:
- Any summary field showing
0when you expect a non-zero value — verify there is actual data in the corresponding table for this org. posture_policy: null— no posture policy has been created or enabled yet for this org.
ST4 — Reject Non-Admin Access
What it verifies: A non-admin org member cannot access any export action — the handler returns 403 FORBIDDEN with "Admin required".
Steps:
- Log in as a non-admin member of the org:
curl -s -X POST https://login.quickztna.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"member@example.com","password":"MemberPass123"}' \
| python3 -m json.tool
Save the returned token:
MEMBER_TOKEN="eyJ..."
- Attempt to export machines:
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $MEMBER_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"machines\",\"org_id\":\"$ORG_ID\"}" \
| python3 -m json.tool
Expected response:
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Admin required"
}
}
HTTP status will be 403.
- Repeat for
audit_logsandcompliance_reportactions — both must return the same403 FORBIDDEN.
Pass: All three export actions return 403 with code: "FORBIDDEN" and message: "Admin required" when called with a non-admin token.
Fail / Common issues:
- The member token returns
403with “Not a member” instead — the account used is not a member of this org at all. Use an account that is a member but not an admin. - The export succeeds (200) — the account was promoted to admin. Verify the user’s role in
org_members.
ST5 — Reject Missing Fields and Invalid JSON
What it verifies: The handler validates the request body and returns appropriate 400 errors for malformed or incomplete requests.
Steps:
- Send a request with no body (invalid JSON):
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "not-json" \
| python3 -m json.tool
Expected:
{
"success": false,
"error": {
"code": "INVALID_JSON",
"message": "Request body must be valid JSON"
}
}
- Send valid JSON but omit
action:
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"org_id\":\"$ORG_ID\"}" \
| python3 -m json.tool
Expected:
{
"success": false,
"error": {
"code": "MISSING_FIELDS",
"message": "action and org_id required"
}
}
- Send a valid body with an unknown
action:
curl -s -X POST https://login.quickztna.com/api/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"nonexistent\",\"org_id\":\"$ORG_ID\"}" \
| python3 -m json.tool
Expected:
{
"success": false,
"error": {
"code": "UNKNOWN_ACTION",
"message": "Unknown action: nonexistent"
}
}
Pass: All three malformed requests are rejected with the correct error code and HTTP 400. No 500 errors.
Fail / Common issues:
- 500 returned instead of 400 — the body parser or JSON.parse threw an unhandled exception. Check API container logs.
MISSING_FIELDSfires even when fields are present — ensureContent-Type: application/jsonis set; without it the body may not parse correctly.
Summary
| Sub-test | What it proves | Pass condition |
|---|---|---|
| ST1 | Machines JSON export | success: true, array with 10 fields per machine, ordered by name |
| ST2 | Audit log JSON export | success: true, Loki logs returned, days param respected |
| ST3 | Compliance report | All seven summary keys present, report_type: "compliance" |
| ST4 | Non-admin blocked | 403 FORBIDDEN "Admin required" for all three actions |
| ST5 | Input validation | INVALID_JSON, MISSING_FIELDS, and UNKNOWN_ACTION each return 400 |