mirror of
https://github.com/azaion/loader.git
synced 2026-04-22 10:06:32 +00:00
Add E2E tests, fix bugs
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
# Input Data Parameters
|
||||
|
||||
## API Request Schemas
|
||||
|
||||
### Login
|
||||
- `email`: string — user email address
|
||||
- `password`: string — user password (plaintext)
|
||||
|
||||
### Load Resource
|
||||
- `filename`: string — resource name (without `.big`/`.small` suffix)
|
||||
- `folder`: string — resource folder/bucket name
|
||||
|
||||
### Upload Resource
|
||||
- `data`: binary file (multipart upload)
|
||||
- `filename`: string — resource name (path parameter)
|
||||
- `folder`: string — destination folder (form field, defaults to `"models"`)
|
||||
|
||||
### Unlock
|
||||
- `email`: string — user email
|
||||
- `password`: string — user password
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### cdn.yaml (downloaded encrypted from API)
|
||||
- `host`: string — S3 endpoint URL
|
||||
- `downloader_access_key`: string — read-only S3 access key
|
||||
- `downloader_access_secret`: string — read-only S3 secret key
|
||||
- `uploader_access_key`: string — write S3 access key
|
||||
- `uploader_access_secret`: string — write S3 secret key
|
||||
|
||||
## JWT Token Claims
|
||||
- `nameid`: string — user GUID
|
||||
- `unique_name`: string — user email
|
||||
- `role`: string — one of: ApiAdmin, Admin, ResourceUploader, Validator, Operator
|
||||
|
||||
## External Data Sources
|
||||
|
||||
| Source | Data | Format | Direction |
|
||||
|--------|------|--------|-----------|
|
||||
| Azaion Resource API | JWT tokens, encrypted resources (small parts), CDN config, key fragments | JSON / binary | Download |
|
||||
| S3 CDN | Large resource parts (.big files) | Binary | Upload / Download |
|
||||
| Local filesystem | Encrypted Docker archive (`images.enc`), cached `.big` files | Binary | Read / Write |
|
||||
| Docker daemon | Image loading, image inspection | CLI stdout | Read |
|
||||
| Host OS | Hardware fingerprint (CPU, GPU, RAM, drive serial) | Text (subprocess) | Read |
|
||||
@@ -0,0 +1,80 @@
|
||||
# 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`, `key: "healthy"` |
|
||||
| Threshold | Output must exceed or stay below a limit | `latency < 2000ms` |
|
||||
| Pattern match | Output must match a string/regex pattern | `error contains "invalid"` |
|
||||
| Schema match | Output structure must conform to a schema | `response has keys: status, authenticated, modelCacheDir` |
|
||||
|
||||
## Input → Expected Result Mapping
|
||||
|
||||
### Health & Status Endpoints
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 1 | `GET /health` | Liveness probe, no auth needed | HTTP 200, body: `{"status": "healthy"}` | exact | N/A | N/A |
|
||||
| 2 | `GET /status` (no prior login) | Status before authentication | HTTP 200, body: `{"status": "healthy", "authenticated": false, "modelCacheDir": "models"}` | exact | N/A | N/A |
|
||||
| 3 | `GET /status` (after login) | Status after valid authentication | HTTP 200, body has `"authenticated": true` | exact (status), exact (authenticated field) | N/A | N/A |
|
||||
|
||||
### Authentication
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 4 | `POST /login {"email": "valid@test.com", "password": "validpass"}` | Valid credentials | HTTP 200, body: `{"status": "ok"}` | exact | N/A | N/A |
|
||||
| 5 | `POST /login {"email": "bad@test.com", "password": "wrongpass"}` | Invalid credentials | HTTP 401, body has `"detail"` key with error string | exact (status), schema (body has detail) | N/A | N/A |
|
||||
| 6 | `POST /login {}` | Missing fields | HTTP 422 (validation error) | exact (status) | N/A | N/A |
|
||||
|
||||
### Resource Download
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 7 | `POST /load/testfile {"filename": "testfile", "folder": "models"}` (after valid login) | Download existing resource | HTTP 200, Content-Type: `application/octet-stream`, body is non-empty bytes | exact (status), exact (content-type), threshold_min (body length > 0) | N/A | N/A |
|
||||
| 8 | `POST /load/nonexistent {"filename": "nonexistent", "folder": "models"}` (after valid login) | Download missing resource | HTTP 500, body has `"detail"` key | exact (status), schema (body has detail) | N/A | N/A |
|
||||
| 9 | `POST /load/testfile {"filename": "testfile", "folder": "models"}` (no login) | Download without authentication | HTTP 500, body has `"detail"` key (ApiClient has no credentials) | exact (status), schema (body has detail) | N/A | N/A |
|
||||
|
||||
### Resource Upload
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 10 | `POST /upload/testfile` multipart: file=binary, folder="models" (after valid login) | Upload resource | HTTP 200, body: `{"status": "ok"}` | exact | N/A | N/A |
|
||||
| 11 | `POST /upload/testfile` no file attached | Upload without file | HTTP 422 (validation error) | exact (status) | N/A | N/A |
|
||||
|
||||
### Unlock Workflow
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 12 | `POST /unlock {"email": "valid@test.com", "password": "validpass"}` (archive exists, images not loaded) | Start unlock workflow | HTTP 200, body: `{"state": "authenticating"}` | exact | N/A | N/A |
|
||||
| 13 | `POST /unlock {"email": "valid@test.com", "password": "validpass"}` (images already loaded) | Unlock when already ready | HTTP 200, body: `{"state": "ready"}` | exact | N/A | N/A |
|
||||
| 14 | `POST /unlock {"email": "valid@test.com", "password": "validpass"}` (no archive, images not loaded) | Unlock without archive | HTTP 404, body has `"detail"` containing "Encrypted archive not found" | exact (status), substring (detail) | N/A | N/A |
|
||||
| 15 | `POST /unlock {"email": "valid@test.com", "password": "validpass"}` (unlock already in progress) | Duplicate unlock request | HTTP 200, body has `"state"` field with current in-progress state | exact (status), schema (body has state) | N/A | N/A |
|
||||
| 16 | `GET /unlock/status` (unlock in progress) | Poll unlock status | HTTP 200, body: `{"state": "<current_state>", "error": null}` | exact (status), schema (body has state + error) | N/A | N/A |
|
||||
| 17 | `GET /unlock/status` (unlock failed) | Poll after failure | HTTP 200, body has `"state": "error"` and `"error"` is non-null string | exact (state), threshold_min (error string length > 0) | N/A | N/A |
|
||||
| 18 | `GET /unlock/status` (idle, no unlock started) | Poll before any unlock | HTTP 200, body: `{"state": "idle", "error": null}` | exact | N/A | N/A |
|
||||
|
||||
### Security — Encryption Round-Trip
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 19 | encrypt_to(b"hello world", "testkey") then decrypt_to(result, "testkey") | Encrypt/decrypt round-trip | Decrypted output equals original: `b"hello world"` | exact | N/A | N/A |
|
||||
| 20 | decrypt_to(encrypted_bytes, "wrong_key") | Decrypt with wrong key | Raises exception or returns garbled data ≠ original | pattern (exception raised or output ≠ input) | N/A | N/A |
|
||||
|
||||
### Security — Key Derivation
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 21 | get_resource_encryption_key() called twice | Deterministic shared key | Both calls return identical string | exact | N/A | N/A |
|
||||
| 22 | get_hw_hash("CPU: test") | Hardware hash derivation | Returns non-empty base64 string | threshold_min (length > 0), pattern (base64 charset) | N/A | N/A |
|
||||
| 23 | get_api_encryption_key(creds1, hw_hash) vs get_api_encryption_key(creds2, hw_hash) | Different credentials produce different keys | key1 ≠ key2 | exact (inequality) | N/A | N/A |
|
||||
|
||||
### Binary Split — Archive Decryption
|
||||
|
||||
| # | Input | Input Description | Expected Result | Comparison | Tolerance | Reference File |
|
||||
|---|-------|-------------------|-----------------|------------|-----------|---------------|
|
||||
| 24 | decrypt_archive(test_encrypted_file, known_key, output_path) | Decrypt test archive | Output file matches original plaintext content | exact (file content) | N/A | N/A |
|
||||
| 25 | check_images_loaded("nonexistent-version") | Check for missing Docker images | Returns `False` | exact | N/A | N/A |
|
||||
Reference in New Issue
Block a user