Files
admin/_docs/02_document/tests/blackbox-tests.md
T
Oleksandr Bezdieniezhnykh c7b297de83
ci/woodpecker/push/01-test Pipeline failed
ci/woodpecker/push/02-build-push unknown status
refactor: remove deploy.cmd and update Dockerfile for health checks
- Deleted the deploy.cmd script as it was no longer needed.
- Updated Dockerfile to include curl for health checks and added a non-root user for improved security.
- Modified health check command to use curl for better reliability.
- Adjusted docker-compose.test.yml to reflect changes in health check configuration.
- Cleaned up appsettings.json and removed unused configuration properties.
- Removed Resource entity and related requests from the codebase as part of the architectural shift.
- Updated documentation to reflect the removal of hardware binding and related endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 08:47:21 +03:00

24 KiB

Blackbox Tests

Positive Scenarios

FT-P-01: Successful Login

Summary: User with valid credentials receives a JWT token. Traces to: AC-1 Category: Authentication

Preconditions:

  • Seed user admin@azaion.com exists in database

Input data: Valid email/password for seed admin user

Steps:

Step Consumer Action Expected System Response
1 POST /login with valid email and password HTTP 200, body contains non-empty token string

Expected outcome: HTTP 200 with JWT token in response body Max execution time: 5s


FT-P-02: Successful User Registration

Summary: ApiAdmin creates a new user account. Traces to: AC-5, AC-6, AC-7 Category: User Management

Preconditions:

  • Caller authenticated as ApiAdmin

Input data: {"email":"newuser@test.com","password":"validpwd1","role":"Operator"}

Steps:

Step Consumer Action Expected System Response
1 Login as admin to get JWT HTTP 200, JWT token
2 POST /users with valid registration data and ApiAdmin JWT HTTP 200

Expected outcome: HTTP 200, user created Max execution time: 5s


FT-P-03: JWT Token Structure Validation

Summary: JWT token contains correct issuer, audience, and lifetime claims. Traces to: AC-4 Category: Authentication

Preconditions:

  • Valid login completed

Input data: JWT token from login response

Steps:

Step Consumer Action Expected System Response
1 Login to get JWT HTTP 200, JWT token
2 Decode JWT payload (Base64) Claims contain iss, aud, exp
3 Validate iss == "AzaionApi" Match
4 Validate aud == "Annotators/OrangePi/Admins" Match
5 Validate exp - iat ≈ 14400s (4 hours) Within ± 60s

Expected outcome: All JWT claims match expected values Max execution time: 5s


FT-P-04: First Hardware Check Stores Fingerprint

Summary: On first hardware check, the fingerprint is stored for the user. Traces to: AC-10 Category: Hardware Binding

Preconditions:

  • User exists with no hardware bound

Input data: {"hardware":"test-hw-fingerprint-001"}

Steps:

Step Consumer Action Expected System Response
1 Register new user, login to get JWT HTTP 200
2 POST /resources/check with hardware string HTTP 200, body true

Expected outcome: HTTP 200, hardware stored Max execution time: 5s


FT-P-05: Subsequent Hardware Check Matches

Summary: Same hardware fingerprint passes validation on subsequent calls. Traces to: AC-11 Category: Hardware Binding

Preconditions:

  • User with hardware already bound (from FT-P-04)

Input data: Same hardware string as initial binding

Steps:

Step Consumer Action Expected System Response
1 POST /resources/check with same hardware HTTP 200, body true

Expected outcome: HTTP 200 Max execution time: 5s


FT-P-06: List All Users

Summary: ApiAdmin retrieves the user list. Traces to: AC-9 Category: User Management

Preconditions:

  • Caller authenticated as ApiAdmin

Input data: GET /users with ApiAdmin JWT

Steps:

Step Consumer Action Expected System Response
1 GET /users with ApiAdmin JWT HTTP 200, JSON array with >= 1 user

Expected outcome: HTTP 200, array containing at least seed users Max execution time: 5s


FT-P-07: Filter Users by Email

Summary: ApiAdmin filters users by email substring. Traces to: AC-9 Category: User Management

Preconditions:

  • Caller authenticated as ApiAdmin, seed users exist

Input data: GET /users?email=admin

Steps:

Step Consumer Action Expected System Response
1 GET /users?email=admin with ApiAdmin JWT HTTP 200, all returned emails contain "admin"

Expected outcome: HTTP 200, filtered list Max execution time: 5s


FT-P-08: Upload Resource File

Summary: Authenticated user uploads a file to a resource folder. Traces to: AC-13 Category: Resource Distribution

Preconditions:

  • Caller authenticated

Input data: Multipart form upload with 1 KB text file

Steps:

Step Consumer Action Expected System Response
1 POST /resources/testfolder with multipart file HTTP 200

Expected outcome: HTTP 200, file stored Max execution time: 5s


FT-P-09: Download Encrypted Resource

Summary: Authenticated user downloads an encrypted resource file. Traces to: AC-14, AC-18 Category: Resource Distribution

Preconditions:

  • User authenticated, hardware bound, resource file uploaded

Input data: {"password":"validpwd1","hardware":"test-hw-001","fileName":"test.txt"}

Steps:

Step Consumer Action Expected System Response
1 POST /resources/get with credentials HTTP 200, Content-Type: application/octet-stream, non-empty body

Expected outcome: HTTP 200 with encrypted binary content Max execution time: 10s


FT-P-10: Encryption Round-Trip Verification

Summary: Downloaded encrypted resource decrypts to original file content. Traces to: AC-15, AC-19 Category: Resource Distribution

Preconditions:

  • Known file uploaded, user credentials known

Input data: Original file content, user email, password, hardware hash

Steps:

Step Consumer Action Expected System Response
1 Upload known file HTTP 200
2 Download encrypted file via API HTTP 200, encrypted bytes
3 Derive AES key from email + password + hwHash Key bytes
4 Decrypt downloaded content with derived key Decrypted bytes
5 Compare decrypted bytes with original Byte-level equality

Expected outcome: Decrypted content matches original file exactly Max execution time: 10s


FT-P-11: Change User Role

Summary: ApiAdmin changes a user's role. Traces to: AC-9 Category: User Management

Preconditions:

  • Target user exists, caller is ApiAdmin

Input data: {"email":"testuser@test.com","role":"Admin"}

Steps:

Step Consumer Action Expected System Response
1 PUT /users/role with ApiAdmin JWT HTTP 200

Expected outcome: HTTP 200, role updated Max execution time: 5s


FT-P-12: Disable User Account

Summary: ApiAdmin disables a user account. Traces to: AC-9 Category: User Management

Preconditions:

  • Target user exists, caller is ApiAdmin

Input data: {"email":"testuser@test.com","isEnabled":false}

Steps:

Step Consumer Action Expected System Response
1 PUT /users/enable with ApiAdmin JWT HTTP 200

Expected outcome: HTTP 200, account disabled Max execution time: 5s


FT-P-13: Delete User

Summary: ApiAdmin deletes a user account. Traces to: AC-9 Category: User Management

Preconditions:

  • Target user exists, caller is ApiAdmin

Input data: DELETE /users?email=testuser@test.com

Steps:

Step Consumer Action Expected System Response
1 DELETE /users?email=testuser@test.com with ApiAdmin JWT HTTP 200

Expected outcome: HTTP 200, user deleted Max execution time: 5s


Negative Scenarios

FT-N-01: Login with Unknown Email

Summary: Login attempt with non-existent email returns appropriate error. Traces to: AC-2 Category: Authentication

Preconditions:

  • Email does not exist in database

Input data: {"email":"nonexistent@test.com","password":"anypass1"}

Steps:

Step Consumer Action Expected System Response
1 POST /login with unknown email HTTP 409, ExceptionEnum code 10 (NoEmailFound)

Expected outcome: HTTP 409 with error code 10 Max execution time: 5s


FT-N-02: Login with Wrong Password

Summary: Login attempt with correct email but wrong password returns error. Traces to: AC-3 Category: Authentication

Preconditions:

  • User exists in database

Input data: {"email":"admin@azaion.com","password":"wrongpassword123"}

Steps:

Step Consumer Action Expected System Response
1 POST /login with wrong password HTTP 409, ExceptionEnum code 30 (WrongPassword)

Expected outcome: HTTP 409 with error code 30 Max execution time: 5s


FT-N-03: Register with Short Email

Summary: Registration with email shorter than 8 characters is rejected. Traces to: AC-5 Category: User Management

Preconditions:

  • Caller authenticated as ApiAdmin

Input data: {"email":"short","password":"validpwd1","role":"Operator"}

Steps:

Step Consumer Action Expected System Response
1 POST /users with short email HTTP 400, validation error

Expected outcome: HTTP 400 with email length validation error Max execution time: 5s


FT-N-04: Register with Invalid Email Format

Summary: Registration with invalid email format (>= 8 chars but not email) is rejected. Traces to: AC-6 Category: User Management

Preconditions:

  • Caller authenticated as ApiAdmin

Input data: {"email":"notanemail","password":"validpwd1","role":"Operator"}

Steps:

Step Consumer Action Expected System Response
1 POST /users with invalid email format HTTP 400, validation error

Expected outcome: HTTP 400 with email format validation error Max execution time: 5s


FT-N-05: Upload Empty File

Summary: Upload request with no file attached returns error. Traces to: AC-16 Category: Resource Distribution

Preconditions:

  • Caller authenticated

Input data: POST /resources/testfolder with no file

Steps:

Step Consumer Action Expected System Response
1 POST /resources/testfolder with empty request HTTP 409, ExceptionEnum code 70 (NoFileProvided)

Expected outcome: HTTP 409 with error code 70 Max execution time: 5s


FT-N-06: Hardware Mismatch

Summary: Hardware check with different fingerprint after binding returns error. Traces to: AC-12 Category: Hardware Binding

Preconditions:

  • User has hardware already bound to a different fingerprint

Input data: {"hardware":"different-hardware-xyz"}

Steps:

Step Consumer Action Expected System Response
1 POST /resources/check with different hardware HTTP 409, ExceptionEnum code 40 (HardwareIdMismatch)

Expected outcome: HTTP 409 with error code 40 Max execution time: 5s


FT-N-07: Register Duplicate Email

Summary: Registration with already-existing email returns error. Traces to: AC-8 Category: User Management

Preconditions:

  • User with target email already exists

Input data: {"email":"admin@azaion.com","password":"validpwd1","role":"Operator"}

Steps:

Step Consumer Action Expected System Response
1 POST /users with existing email HTTP 409, ExceptionEnum code 20 (EmailExists)

Expected outcome: HTTP 409 with error code 20 Max execution time: 5s


FT-N-08: Register with Short Password

Summary: Registration with password shorter than 8 characters is rejected. Traces to: AC-7 Category: User Management

Preconditions:

  • Caller authenticated as ApiAdmin

Input data: {"email":"newuser@test.com","password":"short","role":"Operator"}

Steps:

Step Consumer Action Expected System Response
1 POST /users with short password HTTP 400, validation error

Expected outcome: HTTP 400 with password length validation error Max execution time: 5s


Cycle 1 Additions (2026-05-13)

The scenarios below were appended during the existing-code cycle 1 Test-Spec Sync (autodev Step 12) for tasks AZ-513, AZ-196, AZ-183, AZ-197. Numbering continues from the legacy IDs above; existing IDs are preserved.

Cycle 1 Obsoletion Note

The following legacy entries describe behaviour removed by AZ-197 (admin-side hardware-binding cleanup). Their bodies are intentionally left intact to preserve traceability IDs per the cycle-update rule "preserve existing traceability IDs"; they should be treated as obsolete and superseded by FT-N-15 below:

  • FT-P-04 (First Hardware Check Stores Fingerprint) — superseded; the POST /resources/check endpoint and the hardware-store side-effect were removed.
  • FT-P-05 (Subsequent Hardware Check Matches) — superseded; same endpoint removed.
  • FT-N-06 (Hardware Mismatch) — superseded; the HardwareIdMismatch / error code 40 path no longer exists in ExceptionEnum.
  • FT-P-09 / FT-P-10 wire shape — the hardware field on POST /resources/get/{dataFolder} is no longer required; the encryption key is now derived from email + password only. The tests still pass without the field; do not regenerate spec bodies until a full /test-spec rerun.

See _docs/03_implementation/batch_06_report.md for the full AZ-197 implementation rationale and the wire-compat policy decision (drop entirely).


Detection Classes CRUD (AZ-513)

FT-P-14: POST /classes Creates Detection Class

Summary: ApiAdmin creates a new detection class and the response includes the assigned id. Traces to: AZ-513 AC-1 Category: Detection Classes CRUD

Preconditions:

  • Caller authenticated as ApiAdmin
  • detection_classes table exists

Input data: {"name":"Tank","shortName":"T","color":"#FF0000","maxSizeM":5.0}

Steps:

Step Consumer Action Expected System Response
1 POST /classes with valid body and ApiAdmin JWT HTTP 200/201 with body containing assigned id and the submitted fields

Expected outcome: HTTP 200 or 201, response body has integer id and matches input fields Max execution time: 5s


FT-P-15: PATCH /classes/{id} Full Body Update

Summary: Updating a detection class with a full body replaces the changed fields. Traces to: AZ-513 AC-3 Category: Detection Classes CRUD

Preconditions:

  • A detection class with id 7 exists with name: "Tank"

Input data: {"name":"Heavy Tank","shortName":"T","color":"#FF0000","maxSizeM":5.0} to PATCH /classes/7

Steps:

Step Consumer Action Expected System Response
1 PATCH /classes/7 with full body and ApiAdmin JWT HTTP 200, response body shows name: "Heavy Tank"

Expected outcome: HTTP 200, updated entity reflects the changed field Max execution time: 5s


FT-P-16: PATCH /classes/{id} Partial Body Update

Summary: PATCH with only the changed field updates that field and leaves others intact. Traces to: AZ-513 AC-4 Category: Detection Classes CRUD

Preconditions:

  • A detection class with id 7 exists with name: "Tank", color: "#FF0000", maxSizeM: 5.0

Input data: {"color":"#00FF00"} to PATCH /classes/7

Steps:

Step Consumer Action Expected System Response
1 PATCH /classes/7 with partial body and ApiAdmin JWT HTTP 200, response body shows color: "#00FF00"; other fields unchanged

Expected outcome: HTTP 200, partial-merge semantics confirmed Max execution time: 5s


FT-P-17: DELETE /classes/{id} Removes Class

Summary: ApiAdmin deletes a detection class and it disappears from the DB. Traces to: AZ-513 AC-7 Category: Detection Classes CRUD

Preconditions:

  • A detection class with id 7 exists

Input data: DELETE /classes/7

Steps:

Step Consumer Action Expected System Response
1 DELETE /classes/7 with ApiAdmin JWT HTTP 200 or 204
2 GET the class list (or PATCH the same id) id 7 no longer present

Expected outcome: HTTP 200/204; class removed from DB Max execution time: 5s


FT-N-09: POST /classes Without ApiAdmin JWT

Summary: POST /classes requires the same apiAdminPolicy as /users; non-admin / unauthenticated calls are rejected. Traces to: AZ-513 AC-2 Category: Detection Classes CRUD

Preconditions: None (negative path)

Input data: Valid body, but caller has no JWT or a non-ApiAdmin JWT

Steps:

Step Consumer Action Expected System Response
1 POST /classes without JWT HTTP 401
2 POST /classes with non-ApiAdmin JWT HTTP 403

Expected outcome: HTTP 401 (no JWT) or 403 (non-admin) Max execution time: 5s


FT-N-10: PATCH /classes/{id} Unknown id Returns 404

Summary: PATCH against a non-existent id returns 404. Traces to: AZ-513 AC-5 Category: Detection Classes CRUD

Preconditions: No detection class with id 9999

Input data: PATCH /classes/9999 with any valid body

Steps:

Step Consumer Action Expected System Response
1 PATCH /classes/9999 with ApiAdmin JWT HTTP 404

Expected outcome: HTTP 404 Max execution time: 5s


FT-N-11: PATCH /classes/{id} Without ApiAdmin JWT

Summary: PATCH /classes/{id} requires apiAdminPolicy. Traces to: AZ-513 AC-6 Category: Detection Classes CRUD

Input data: Any valid body to PATCH /classes/{id}

Steps:

Step Consumer Action Expected System Response
1 PATCH /classes/{id} without JWT HTTP 401
2 PATCH /classes/{id} with non-ApiAdmin JWT HTTP 403

Expected outcome: HTTP 401 or 403 Max execution time: 5s


FT-N-12: DELETE /classes/{id} Unknown id Returns 404

Summary: DELETE against a non-existent id returns 404 (matching /users semantics — non-idempotent). Traces to: AZ-513 AC-8 Category: Detection Classes CRUD

Preconditions: No detection class with id 9999

Input data: DELETE /classes/9999

Steps:

Step Consumer Action Expected System Response
1 DELETE /classes/9999 with ApiAdmin JWT HTTP 404

Expected outcome: HTTP 404 Max execution time: 5s


FT-N-13: DELETE /classes/{id} Without ApiAdmin JWT

Summary: DELETE /classes/{id} requires apiAdminPolicy. Traces to: AZ-513 AC-9 Category: Detection Classes CRUD

Input data: DELETE /classes/{id}

Steps:

Step Consumer Action Expected System Response
1 DELETE /classes/{id} without JWT HTTP 401
2 DELETE /classes/{id} with non-ApiAdmin JWT HTTP 403

Expected outcome: HTTP 401 or 403 Max execution time: 5s


Device Auto-Registration (AZ-196)

FT-P-18: POST /devices Returns Serial / Email / Password

Summary: First call to POST /devices returns the next serial in the azj-NNNN sequence with a generated email and 32-char hex password. Traces to: AZ-196 AC-1 Category: Device Provisioning

Preconditions:

  • Caller authenticated as ApiAdmin
  • No (or known-prior) CompanionPC users in DB

Input data: POST /devices with no body, ApiAdmin JWT

Steps:

Step Consumer Action Expected System Response
1 POST /devices with ApiAdmin JWT HTTP 200 with serial matching ^azj-\d{4}$, email = {serial}@azaion.com, password = 32 lowercase hex chars

Expected outcome: HTTP 200, all three fields shaped per spec Max execution time: 5s


FT-P-19: Sequential Device Serials

Summary: Repeated calls to POST /devices yield strictly increasing serial numbers. Traces to: AZ-196 AC-2 Category: Device Provisioning

Preconditions:

  • Most recent CompanionPC user has a known serial azj-NNNN

Input data: POST /devices twice in succession

Steps:

Step Consumer Action Expected System Response
1 POST /devices → record serial S1 HTTP 200
2 POST /devices → record serial S2 HTTP 200
3 Parse the numeric suffix of both numeric(S2) == numeric(S1) + 1

Expected outcome: HTTP 200, suffix increments by exactly 1 Max execution time: 5s


FT-P-20: Returned Device Credentials Can Login

Summary: The plaintext password returned by POST /devices succeeds against POST /login (and the persisted hash is therefore correct). Traces to: AZ-196 AC-3, AZ-196 AC-4 Category: Device Provisioning

Preconditions:

  • Caller authenticated as ApiAdmin

Input data: Use the response from POST /devices as {Email, Password} to POST /login

Steps:

Step Consumer Action Expected System Response
1 POST /devices with ApiAdmin JWT HTTP 200, {Serial, Email, Password} returned
2 POST /login with the returned Email and Password HTTP 200 with non-empty JWT

Expected outcome: HTTP 200 on login; persisted user has Role=CompanionPC, IsEnabled=true (verified by AdminApi behaviour rather than direct DB inspection) Max execution time: 5s


FT-N-14: POST /devices Without ApiAdmin JWT

Summary: POST /devices requires apiAdminPolicy. Traces to: AZ-196 AC-5 Category: Device Provisioning

Input data: POST /devices

Steps:

Step Consumer Action Expected System Response
1 POST /devices without JWT HTTP 401
2 POST /devices with non-ApiAdmin JWT HTTP 403

Expected outcome: HTTP 401 or 403 Max execution time: 5s


Resources OTA Update Check (AZ-183) — REVERTED post-cycle-1

The OTA update check & publish feature shipped in cycle 1 was reverted later the same day after the security audit (finding F-1: /get-update disclosed plaintext per-resource encryption keys to any authenticated caller). The OTA delivery model itself was deemed obsolete in the target architecture.

The scenarios FT-P-21, FT-P-22, FT-P-23 are retained here as ID placeholders so previously-cited references resolve. Their bodies are intentionally collapsed because the underlying endpoints, service, entity, table, and the e2e test class ResourceUpdateTests.cs were all removed. See _docs/02_document/system-flows.md (Flow F10) and _docs/05_security/security_report.md (finding F-1) for context.

Removed Test ID Was tracing Disposition
FT-P-21 AZ-183 AC-2 Removed — endpoint and test deleted
FT-P-22 AZ-183 AC-3 Removed — endpoint and test deleted
FT-P-23 AZ-183 AC-5 Removed — endpoint and test deleted

Hardware-Binding Removal (AZ-197)

FT-N-15: Hardware Endpoints Removed

Summary: The legacy PUT /users/hardware/set endpoint and the POST /resources/check endpoint have been removed and now return 404. Traces to: AZ-197 AC-2 Category: Authorization & Routing

Preconditions:

  • Updated admin API build (post-AZ-197)

Input data: PUT /users/hardware/set and POST /resources/check

Steps:

Step Consumer Action Expected System Response
1 PUT /users/hardware/set with ApiAdmin JWT HTTP 404
2 POST /resources/check with ApiAdmin JWT HTTP 404

Expected outcome: HTTP 404 on both routes Max execution time: 5s

Note: AZ-197 AC-1 (resource download works without Hardware) is implicitly covered by the existing FT-P-09 / FT-P-10 scenarios once their request bodies are aligned with the new wire shape. AZ-197 AC-3..AC-8 are internal-signature / build-system invariants and are verified at build/CI time, not via a blackbox HTTP scenario.