mirror of
https://github.com/azaion/admin.git
synced 2026-04-22 10:06:33 +00:00
[AZ-189] [AZ-190] [AZ-191] [AZ-192] [AZ-193] [AZ-194] [AZ-195] Add e2e blackbox test suite
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# Input Data Parameters
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Table: `users`
|
||||
|
||||
| Column | Type | Nullable | Description |
|
||||
|--------|------|----------|-------------|
|
||||
| `id` | uuid | No | Primary key |
|
||||
| `email` | varchar(160) | No | User identifier |
|
||||
| `password_hash` | varchar(255) | No | SHA-384 hash (Base64) |
|
||||
| `hardware` | text | Yes | Raw hardware fingerprint |
|
||||
| `hardware_hash` | varchar(120) | Yes | Unused column (legacy) |
|
||||
| `role` | varchar(20) | No | Text enum: None, Operator, Validator, CompanionPC, Admin, ResourceUploader, ApiAdmin |
|
||||
| `user_config` | varchar(512) | Yes | JSON: `{ QueueOffsets: { AnnotationsOffset, AnnotationsConfirmOffset, AnnotationsCommandsOffset } }` |
|
||||
| `created_at` | timestamp | No | Default: `now()` |
|
||||
| `last_login` | timestamp | Yes | Updated on hardware check |
|
||||
| `is_enabled` | bool | No | Default: `true` |
|
||||
|
||||
## API Request Schemas
|
||||
|
||||
### POST /login
|
||||
```json
|
||||
{ "Email": "string", "Password": "string" }
|
||||
```
|
||||
|
||||
### POST /users
|
||||
```json
|
||||
{ "Email": "string", "Password": "string", "Role": "RoleEnum (int)" }
|
||||
```
|
||||
|
||||
### PUT /users/hardware/set
|
||||
```json
|
||||
{ "Email": "string", "Hardware": "string|null" }
|
||||
```
|
||||
|
||||
### PUT /users/queue-offsets/set
|
||||
```json
|
||||
{ "Email": "string", "Offsets": { "AnnotationsOffset": 0, "AnnotationsConfirmOffset": 0, "AnnotationsCommandsOffset": 0 } }
|
||||
```
|
||||
|
||||
### POST /resources/get/{dataFolder?}
|
||||
```json
|
||||
{ "Password": "string", "Hardware": "string", "FileName": "string" }
|
||||
```
|
||||
|
||||
### POST /resources/check
|
||||
```json
|
||||
{ "Hardware": "string" }
|
||||
```
|
||||
|
||||
### POST /resources/{dataFolder?}
|
||||
Multipart form data with `IFormFile` field.
|
||||
|
||||
## Configuration Sections
|
||||
|
||||
### ConnectionStrings
|
||||
```json
|
||||
{ "AzaionDb": "Host=...;Database=azaion;Username=azaion_reader;Password=...", "AzaionDbAdmin": "Host=...;Database=azaion;Username=azaion_admin;Password=..." }
|
||||
```
|
||||
|
||||
### JwtConfig
|
||||
```json
|
||||
{ "Issuer": "AzaionApi", "Audience": "Annotators/OrangePi/Admins", "Secret": "...", "TokenLifetimeHours": 4 }
|
||||
```
|
||||
|
||||
### ResourcesConfig
|
||||
```json
|
||||
{ "ResourcesFolder": "Content", "SuiteInstallerFolder": "suite", "SuiteStageInstallerFolder": "suite-stage" }
|
||||
```
|
||||
|
||||
## Resource Files
|
||||
|
||||
The system stores and serves:
|
||||
- **AI models and DLLs** — stored in `ResourcesFolder`, served encrypted per-user
|
||||
- **Production installers** — files matching `AzaionSuite.Iterative*` in `SuiteInstallerFolder`
|
||||
- **Staging installers** — files matching `AzaionSuite.Iterative*` in `SuiteStageInstallerFolder`
|
||||
@@ -0,0 +1,86 @@
|
||||
# Expected Results
|
||||
|
||||
Maps every input data item to its quantifiable expected result.
|
||||
Tests use this mapping to compare actual system output against known-correct answers.
|
||||
|
||||
## Result Format Legend
|
||||
|
||||
| Result Type | When to Use | Example |
|
||||
|-------------|-------------|---------|
|
||||
| Exact value | Output must match precisely | `status_code: 200`, `error_code: 10` |
|
||||
| Threshold | Output must exceed or stay below a limit | `latency < 500ms` |
|
||||
| Pattern match | Output must match a string/regex pattern | `body contains "token"` |
|
||||
| Schema match | Output structure must conform to a schema | `response matches { Token: string }` |
|
||||
|
||||
## Input → Expected Result Mapping
|
||||
|
||||
### Authentication
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 1 | `POST /login { "Email": "admin@azaion.com", "Password": "<valid>" }` | Valid credentials | HTTP 200, body: `{ "token": "<non-empty JWT>" }` | exact (status), pattern (token is non-empty string) | N/A | N/A |
|
||||
| 2 | `POST /login { "Email": "nonexistent@x.com", "Password": "any" }` | Unknown email | HTTP 409, body: `{ "ErrorCode": 10, "Message": "No such email found." }` | exact (status, ErrorCode, Message) | N/A | N/A |
|
||||
| 3 | `POST /login { "Email": "admin@azaion.com", "Password": "wrongpw" }` | Wrong password | HTTP 409, body: `{ "ErrorCode": 30, "Message": "Passwords do not match." }` | exact (status, ErrorCode, Message) | N/A | N/A |
|
||||
|
||||
### User Registration
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 4 | `POST /users { "Email": "newuser@azaion.com", "Password": "validpass123", "Role": 10 }` | Valid registration (ApiAdmin auth) | HTTP 200, user created in DB | exact (status) | N/A | N/A |
|
||||
| 5 | `POST /users { "Email": "short", "Password": "validpass123", "Role": 10 }` | Email too short (<8 chars) | HTTP 400, validation error with code `EmailLengthIncorrect` | exact (status), substring (error code) | N/A | N/A |
|
||||
| 6 | `POST /users { "Email": "notanemail", "Password": "validpass123", "Role": 10 }` | Invalid email format (>=8 chars but not email) | HTTP 400, validation error with code `WrongEmail` | exact (status), substring (error code) | N/A | N/A |
|
||||
| 7 | `POST /users { "Email": "valid@azaion.com", "Password": "short", "Role": 10 }` | Password too short (<8 chars) | HTTP 400, validation error with code `PasswordLengthIncorrect` | exact (status), substring (error code) | N/A | N/A |
|
||||
| 8 | `POST /users { "Email": "admin@azaion.com", "Password": "validpass123", "Role": 10 }` | Duplicate email | HTTP 409, body: `{ "ErrorCode": 20, "Message": "Email already exists." }` | exact (status, ErrorCode, Message) | N/A | N/A |
|
||||
| 9 | `POST /users` (no auth header) | Unauthorized registration attempt | HTTP 401 | exact (status) | N/A | N/A |
|
||||
| 10 | `POST /users` (Operator role token) | Non-admin registration attempt | HTTP 403 | exact (status) | N/A | N/A |
|
||||
|
||||
### User Retrieval
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 11 | `GET /users/current` (valid JWT) | Get current user | HTTP 200, body contains email matching JWT claims | exact (status), exact (email matches claim) | N/A | N/A |
|
||||
| 12 | `GET /users` (ApiAdmin JWT) | List all users | HTTP 200, body is array of User objects | exact (status), schema (array of users) | N/A | N/A |
|
||||
| 13 | `GET /users?searchEmail=admin` (ApiAdmin JWT) | Filter by email substring | HTTP 200, all returned users have "admin" in email | exact (status), substring (filter applied) | N/A | N/A |
|
||||
| 14 | `GET /users?searchRole=10` (ApiAdmin JWT) | Filter by Operator role | HTTP 200, all returned users have Role=Operator | exact (status, role filter) | N/A | N/A |
|
||||
|
||||
### Hardware Binding
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 15 | `POST /resources/check { "Hardware": "CPU: Test..." }` (user with no hardware) | First-time hardware binding | HTTP 200, body: `true`, user's hardware column updated in DB | exact (status, body) | N/A | N/A |
|
||||
| 16 | `POST /resources/check { "Hardware": "CPU: Test..." }` (same user, same hardware) | Repeat check with matching hardware | HTTP 200, body: `true` | exact (status, body) | N/A | N/A |
|
||||
| 17 | `POST /resources/check { "Hardware": "DIFFERENT_HW" }` (user with different stored hardware) | Hardware mismatch | HTTP 409, body: `{ "ErrorCode": 40, "Message": "Hardware mismatch!..." }` | exact (status, ErrorCode), substring (Message) | N/A | N/A |
|
||||
|
||||
### Resource Management
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 18 | `POST /resources (multipart: testfile.txt, 100 bytes)` | Upload small file | HTTP 200, file exists at ResourcesFolder/testfile.txt | exact (status), file exists | N/A | N/A |
|
||||
| 19 | `POST /resources/subfolder (multipart: testfile.txt)` | Upload to subfolder | HTTP 200, file exists at ResourcesFolder/subfolder/testfile.txt | exact (status), file exists | N/A | N/A |
|
||||
| 20 | `GET /resources/list` (authenticated) | List resources in root folder | HTTP 200, body is array of filenames including "testfile.txt" | exact (status), set_contains (filename) | N/A | N/A |
|
||||
| 21 | `POST /resources/get { "Password": "validpass123", "Hardware": "<matching>", "FileName": "testfile.txt" }` | Download encrypted resource | HTTP 200, content-type: `application/octet-stream`, body decrypts to original file content | exact (status, content-type), exact (decrypted content matches original) | N/A | N/A |
|
||||
| 22 | `POST /resources (no file)` | Upload with no file | HTTP 409, body: `{ "ErrorCode": 60 }` | exact (status, ErrorCode) | N/A | N/A |
|
||||
|
||||
### Encryption Round-Trip
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 23 | Plaintext string "Hello World..." + key derived from (email, password, hwHash) | Encrypt then decrypt | Decrypted output == original plaintext | exact (string equality) | N/A | N/A |
|
||||
| 24 | Large file (~400 MB) + key | Encrypt then decrypt large file | SHA-256 of decrypted file == SHA-256 of original | exact (hash equality) | N/A | N/A |
|
||||
|
||||
### User Lifecycle
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 25 | `PUT /users/test@azaion.com/set-role/50` (ApiAdmin JWT) | Change role to ResourceUploader | HTTP 200, user's role updated in DB to `ResourceUploader` | exact (status, DB role) | N/A | N/A |
|
||||
| 26 | `PUT /users/test@azaion.com/disable` (ApiAdmin JWT) | Disable user | HTTP 200, user's is_enabled=false in DB | exact (status, DB flag) | N/A | N/A |
|
||||
| 27 | `PUT /users/test@azaion.com/enable` (ApiAdmin JWT) | Enable user | HTTP 200, user's is_enabled=true in DB | exact (status, DB flag) | N/A | N/A |
|
||||
| 28 | `DELETE /users/test@azaion.com` (ApiAdmin JWT) | Delete user | HTTP 200, user no longer exists in DB | exact (status), exact (user gone) | N/A | N/A |
|
||||
|
||||
### API Behavior
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 29 | `GET /` | Root URL | HTTP 302 redirect to `/swagger` | exact (status 302, Location header contains "swagger") | N/A | N/A |
|
||||
| 30 | Any endpoint from non-allowed origin | CORS rejection | No `Access-Control-Allow-Origin` header in response | exact (header absent) | N/A | N/A |
|
||||
| 31 | `OPTIONS /login` from `https://admin.azaion.com` | CORS preflight from allowed origin | `Access-Control-Allow-Origin: https://admin.azaion.com` | exact (header value) | N/A | N/A |
|
||||
Reference in New Issue
Block a user