[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:
Oleksandr Bezdieniezhnykh
2026-04-16 06:25:36 +03:00
parent 1b38e888e1
commit d320d6dd59
98 changed files with 6883 additions and 1 deletions
@@ -0,0 +1,173 @@
# Data Layer
## 1. High-Level Overview
**Purpose**: Provides database access, ORM mapping, entity definitions, configuration binding, and in-memory caching for the entire application.
**Architectural Pattern**: Repository/Factory — `DbFactory` creates short-lived `AzaionDb` connections with a read/write separation pattern.
**Upstream dependencies**: None (leaf component).
**Downstream consumers**: User Management, Authentication & Security, Resource Management.
## 2. Internal Interfaces
### Interface: IDbFactory
| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `Run<T>` | `Func<AzaionDb, Task<T>>` | `T` | Yes | `ArgumentException` (empty conn string) |
| `Run` | `Func<AzaionDb, Task>` | void | Yes | `ArgumentException` |
| `RunAdmin` | `Func<AzaionDb, Task>` | void | Yes | `ArgumentException` |
### Interface: ICache
| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `GetFromCacheAsync<T>` | `string key, Func<Task<T>>, TimeSpan?` | `T` | Yes | None |
| `Invalidate` | `string key` | void | No | None |
### Entities
```
User:
Id: Guid (PK)
Email: string (required)
PasswordHash: string (required)
Hardware: string? (optional)
Role: RoleEnum (required)
CreatedAt: DateTime (required)
LastLogin: DateTime? (optional)
UserConfig: UserConfig? (optional, JSON-serialized)
IsEnabled: bool (required)
UserConfig:
QueueOffsets: UserQueueOffsets? (optional)
UserQueueOffsets:
AnnotationsOffset: ulong
AnnotationsConfirmOffset: ulong
AnnotationsCommandsOffset: ulong
RoleEnum: None=0, Operator=10, Validator=20, CompanionPC=30, Admin=40, ResourceUploader=50, ApiAdmin=1000
```
### Configuration POCOs
```
ConnectionStrings:
AzaionDb: string — read-only connection string
AzaionDbAdmin: string — admin (read/write) connection string
JwtConfig:
Issuer: string
Audience: string
Secret: string
TokenLifetimeHours: double
ResourcesConfig:
ResourcesFolder: string
SuiteInstallerFolder: string
SuiteStageInstallerFolder: string
```
## 3. External API Specification
N/A — internal component.
## 4. Data Access Patterns
### Queries
| Query | Frequency | Hot Path | Index Needed |
|-------|-----------|----------|--------------|
| `SELECT * FROM users WHERE email = ?` | High | Yes | Yes (email) |
| `SELECT * FROM users` with optional filters | Medium | No | No |
| `UPDATE users SET ... WHERE email = ?` | Medium | No | No |
| `INSERT INTO users` | Low | No | No |
| `DELETE FROM users WHERE email = ?` | Low | No | No |
### Caching Strategy
| Data | Cache Type | TTL | Invalidation |
|------|-----------|-----|-------------|
| User by email | In-memory (LazyCache) | 4 hours | On hardware update, queue offset update, hardware check |
### Storage Estimates
| Table | Est. Row Count (1yr) | Row Size | Total Size | Growth Rate |
|-------|---------------------|----------|------------|-------------|
| `users` | 1001000 | ~500 bytes | ~500 KB | Low |
### Data Management
**Seed data**: Default admin user (`admin@azaion.com`, `ApiAdmin` role) and uploader user (`uploader@azaion.com`, `ResourceUploader` role) — see `env/db/02_structure.sql`.
**Rollback**: Standard PostgreSQL transactions; linq2db creates a new connection per `Run`/`RunAdmin` call.
## 5. Implementation Details
**State Management**: Stateless factory pattern. `DbFactory` is a singleton holding pre-built `DataOptions`. Cache state is in-memory per process.
**Key Dependencies**:
| Library | Version | Purpose |
|---------|---------|---------|
| linq2db | 5.4.1 | ORM for PostgreSQL access |
| Npgsql | 10.0.1 | PostgreSQL ADO.NET provider |
| LazyCache | 2.4.0 | In-memory cache with async support |
| Newtonsoft.Json | 13.0.1 | JSON serialization for UserConfig |
**Error Handling Strategy**:
- `DbFactory.LoadOptions` throws `ArgumentException` on empty connection strings (fail-fast at startup).
- Database exceptions from linq2db/Npgsql propagate unhandled to callers.
## 6. Extensions and Helpers
| Helper | Purpose | Used By |
|--------|---------|---------|
| `StringExtensions.ToSnakeCase` | PascalCase → snake_case column mapping | AzaionDbSchemaHolder |
| `EnumExtensions.GetDescriptions` | Enum → description dictionary | BusinessException |
| `QueryableExtensions.WhereIf` | Conditional LINQ filters | UserService |
## 7. Caveats & Edge Cases
**Known limitations**:
- No connection pooling configuration exposed; relies on Npgsql defaults.
- `AzaionDbSchemaHolder` mapping schema is static — adding new entities requires code changes.
- Cache TTL (4 hours) is hardcoded, not configurable.
**Potential race conditions**:
- Cache invalidation after write: there's a small window where stale data could be served between the DB write and cache invalidation.
**Performance bottlenecks**:
- `DbFactory` creates a new connection per operation. For high-throughput scenarios, connection reuse or batching would be needed.
## 8. Dependency Graph
**Must be implemented after**: None (leaf component).
**Can be implemented in parallel with**: Security & Cryptography (no dependency).
**Blocks**: User Management, Authentication, Resource Management, Admin API.
## 9. Logging Strategy
| Log Level | When | Example |
|-----------|------|---------|
| INFO | SQL trace | `SELECT * FROM users WHERE email = @p1` (via linq2db TraceLevel.Info) |
**Log format**: Plaintext SQL output to console.
**Log storage**: Console (via `Console.WriteLine` in `DbFactory.LoadOptions` trace callback).
## Modules Covered
- `Common/Configs/ConnectionStrings`
- `Common/Configs/JwtConfig`
- `Common/Configs/ResourcesConfig`
- `Common/Entities/User`
- `Common/Entities/RoleEnum`
- `Common/Database/AzaionDb`
- `Common/Database/AzaionDbSchemaHolder`
- `Common/Database/DbFactory`
- `Services/Cache`
@@ -0,0 +1,127 @@
# User Management
## 1. High-Level Overview
**Purpose**: Full user lifecycle management — registration, credential validation, hardware binding, role changes, account enable/disable, and deletion.
**Architectural Pattern**: Service layer — stateless business logic operating on the Data Layer through `IDbFactory`.
**Upstream dependencies**: Data Layer (IDbFactory, ICache, User entity), Security & Cryptography (hashing).
**Downstream consumers**: Admin API (endpoint handlers), Authentication (GetByEmail).
## 2. Internal Interfaces
### Interface: IUserService
| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `RegisterUser` | `RegisterUserRequest, CancellationToken` | void | Yes | `BusinessException(EmailExists)` |
| `ValidateUser` | `LoginRequest, CancellationToken` | `User` | Yes | `BusinessException(NoEmailFound, WrongPassword)` |
| `GetByEmail` | `string? email, CancellationToken` | `User?` | Yes | `ArgumentNullException` |
| `UpdateHardware` | `string email, string? hardware, CancellationToken` | void | Yes | None |
| `UpdateQueueOffsets` | `string email, UserQueueOffsets, CancellationToken` | void | Yes | None |
| `GetUsers` | `string? searchEmail, RoleEnum? searchRole, CancellationToken` | `IEnumerable<User>` | Yes | None |
| `CheckHardwareHash` | `User, string hardware, CancellationToken` | `string` (hash) | Yes | `BusinessException(HardwareIdMismatch)` |
| `ChangeRole` | `string email, RoleEnum, CancellationToken` | void | Yes | None |
| `SetEnableStatus` | `string email, bool, CancellationToken` | void | Yes | None |
| `RemoveUser` | `string email, CancellationToken` | void | Yes | None |
**Input DTOs**:
```
RegisterUserRequest:
Email: string (required) — validated: min 8 chars, valid email format
Password: string (required) — validated: min 8 chars
Role: RoleEnum (required)
LoginRequest:
Email: string (required)
Password: string (required)
SetHWRequest:
Email: string (required, validated: not empty)
Hardware: string? (optional — null clears hardware)
SetUserQueueOffsetsRequest:
Email: string (required)
Offsets: UserQueueOffsets (required)
```
## 3. External API Specification
N/A — exposed through Admin API component.
## 4. Data Access Patterns
### Queries
| Query | Frequency | Hot Path | Index Needed |
|-------|-----------|----------|--------------|
| User by email (cached) | High | Yes | Yes |
| User list with filters | Medium | No | No |
| User insert (registration) | Low | No | No |
| User update (hardware, role, config, status) | Medium | No | No |
| User delete | Low | No | No |
### Caching Strategy
| Data | Cache Type | TTL | Invalidation |
|------|-----------|-----|-------------|
| User by email | In-memory (via ICache) | 4 hours | After UpdateHardware, UpdateQueueOffsets, CheckHardwareHash (first login) |
## 5. Implementation Details
**State Management**: Stateless — all state in PostgreSQL + in-memory cache.
**Key Dependencies**:
| Library | Version | Purpose |
|---------|---------|---------|
| FluentValidation | 11.10.0 | Request validation (auto-discovered) |
**Error Handling Strategy**:
- Domain errors thrown as `BusinessException` with specific `ExceptionEnum` codes.
- `GetByEmail` throws `ArgumentNullException` for null/whitespace email.
- Database errors propagate from `IDbFactory`.
- Write operations use `RunAdmin` (admin connection); reads use `Run` (reader connection).
## 6. Extensions and Helpers
| Helper | Purpose | Used By |
|--------|---------|---------|
| `Security.ToHash` | Password hashing (SHA-384) | RegisterUser, ValidateUser |
| `Security.GetHWHash` | Hardware fingerprint hashing | CheckHardwareHash |
| `QueryableExtensions.WhereIf` | Conditional LINQ filters | GetUsers |
## 7. Caveats & Edge Cases
**Known limitations**:
- No pagination on `GetUsers` — returns all matching users.
- `CheckHardwareHash` auto-stores hardware on first access (no explicit admin approval step).
- `RemoveUser` is a hard delete, not soft delete.
**Potential race conditions**:
- Concurrent `RegisterUser` calls with the same email: both could pass the existence check before insert. Mitigated by database unique constraint on email (if one exists).
- `CheckHardwareHash` first-login path: concurrent requests could trigger multiple hardware updates.
**Performance bottlenecks**:
- `GetUsers` loads full user objects including `UserConfig` JSON; for large user bases, projection would be more efficient.
## 8. Dependency Graph
**Must be implemented after**: Data Layer, Security & Cryptography.
**Can be implemented in parallel with**: Resource Management.
**Blocks**: Authentication (uses `GetByEmail`), Admin API.
## 9. Logging Strategy
No explicit logging in UserService.
## Modules Covered
- `Services/UserService`
- `Common/Requests/LoginRequest`
- `Common/Requests/RegisterUserRequest`
- `Common/Requests/SetHWRequest`
- `Common/Requests/SetUserQueueOffsetsRequest`
@@ -0,0 +1,87 @@
# Authentication & Security
## 1. High-Level Overview
**Purpose**: JWT token creation/validation and cryptographic utilities (password hashing, hardware fingerprint hashing, AES file encryption/decryption).
**Architectural Pattern**: Service + static utility — `AuthService` is a DI-managed service for JWT operations; `Security` is a static class for cryptographic primitives.
**Upstream dependencies**: Data Layer (JwtConfig, IUserService for GetByEmail), ASP.NET Core (IHttpContextAccessor).
**Downstream consumers**: Admin API (token creation on login, current user resolution), User Management (password hashing, hardware hashing), Resource Management (encryption key derivation, stream encryption).
## 2. Internal Interfaces
### Interface: IAuthService
| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `GetCurrentUser` | (none — reads from HttpContext) | `User?` | Yes | None |
| `CreateToken` | `User` | `string` (JWT) | No | None |
### Static: Security
| Method | Input | Output | Description |
|--------|-------|--------|-------------|
| `ToHash` | `string` | `string` (Base64) | SHA-384 hash |
| `GetHWHash` | `string hardware` | `string` (Base64) | Salted hardware hash |
| `GetApiEncryptionKey` | `string email, string password, string? hwHash` | `string` (Base64) | Derives AES encryption key |
| `EncryptTo` | `Stream input, Stream output, string key, CancellationToken` | void | AES-256-CBC encrypt stream |
| `DecryptTo` | `Stream encrypted, Stream output, string key, CancellationToken` | void | AES-256-CBC decrypt stream |
## 3. External API Specification
N/A — exposed through Admin API.
## 4. Data Access Patterns
No direct database access. `AuthService.GetCurrentUser` delegates to `IUserService.GetByEmail`.
## 5. Implementation Details
**Algorithmic Complexity**: Encryption/decryption is O(n) where n is file size, streaming in 512 KB buffers.
**State Management**: `AuthService` is stateless (reads claims from HTTP context per request). `Security` is purely static.
**Key Dependencies**:
| Library | Version | Purpose |
|---------|---------|---------|
| System.IdentityModel.Tokens.Jwt | 7.1.2 | JWT token generation |
| Microsoft.AspNetCore.Authentication.JwtBearer | 10.0.3 | JWT middleware integration |
**Error Handling Strategy**:
- `EncryptTo` throws `ArgumentNullException` for unreadable streams or empty keys.
- JWT token creation does not throw (malformed config would cause runtime errors at middleware level).
- `GetCurrentUser` returns null if claims are missing or user not found.
## 6. Extensions and Helpers
None — `Security` itself is a utility consumed by other components.
## 7. Caveats & Edge Cases
**Known limitations**:
- Password hashing uses SHA-384 without per-user salt or key stretching. Not resistant to rainbow table attacks.
- Hardware and encryption key salts are hardcoded constants.
- `GetCurrentUserEmail` assumes `ClaimTypes.Name` is always present; accessing a missing key would throw `KeyNotFoundException`.
- AES encryption prepends IV as first 16 bytes — consumers must know this format.
**Performance bottlenecks**:
- Large file encryption loads encrypted output into `MemoryStream` before sending — high memory usage for large files.
## 8. Dependency Graph
**Must be implemented after**: Data Layer (for JwtConfig, IUserService).
**Can be implemented in parallel with**: User Management (shared dependency on Data Layer).
**Blocks**: Admin API, Resource Management (uses encryption).
## 9. Logging Strategy
No explicit logging in AuthService or Security.
## Modules Covered
- `Services/AuthService`
- `Services/Security`
@@ -0,0 +1,100 @@
# Resource Management
## 1. High-Level Overview
**Purpose**: Server-side file storage management — upload, list, download (with per-user AES encryption), folder clearing, and installer distribution.
**Architectural Pattern**: Service layer — filesystem operations with encryption applied at the service boundary.
**Upstream dependencies**: Data Layer (ResourcesConfig), Authentication & Security (encryption via Security.EncryptTo).
**Downstream consumers**: Admin API (resource endpoints).
## 2. Internal Interfaces
### Interface: IResourcesService
| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `GetInstaller` | `bool isStage` | `(string?, Stream?)` | No | None (returns nulls if not found) |
| `GetEncryptedResource` | `string? dataFolder, string fileName, string key, CancellationToken` | `Stream` | Yes | `FileNotFoundException` |
| `SaveResource` | `string? dataFolder, IFormFile data, CancellationToken` | void | Yes | `BusinessException(NoFileProvided)` |
| `ListResources` | `string? dataFolder, string? search, CancellationToken` | `IEnumerable<string>` | Yes | `DirectoryNotFoundException` |
| `ClearFolder` | `string? dataFolder` | void | No | None |
**Input DTOs**:
```
GetResourceRequest:
Password: string (required, min 8 chars)
Hardware: string (required, not empty)
FileName: string (required, not empty)
CheckResourceRequest:
Hardware: string (required)
```
## 3. External API Specification
N/A — exposed through Admin API.
## 4. Data Access Patterns
No database access. All operations are filesystem-based.
### Storage Estimates
Resources are stored as flat files in configured directories. Size depends on uploaded content (AI models, DLLs, installers — potentially hundreds of MB per file).
## 5. Implementation Details
**State Management**: Stateless — reads/writes directly to filesystem.
**Key Dependencies**: None beyond BCL (System.IO).
**Error Handling Strategy**:
- `SaveResource` throws `BusinessException(NoFileProvided)` for null uploads.
- Missing files/directories throw standard .NET I/O exceptions.
- `ClearFolder` silently returns if directory doesn't exist.
- `GetInstaller` returns `(null, null)` tuple if installer file is not found.
## 6. Extensions and Helpers
| Helper | Purpose | Used By |
|--------|---------|---------|
| `Security.EncryptTo` | AES stream encryption | GetEncryptedResource |
| `Security.GetApiEncryptionKey` | Key derivation | Admin API (before calling GetEncryptedResource) |
## 7. Caveats & Edge Cases
**Known limitations**:
- No path traversal protection: `dataFolder` parameter is concatenated directly with `ResourcesFolder`. A malicious `dataFolder` like `../../etc` could access arbitrary filesystem paths.
- `SaveResource` deletes existing file before writing — no versioning or backup.
- `GetEncryptedResource` loads the entire encrypted file into a `MemoryStream` — memory-intensive for large files.
- `ListResources` wraps a synchronous `DirectoryInfo.GetFiles` in `Task.FromResult` — not truly async.
**Performance bottlenecks**:
- Full file encryption to memory before streaming response: memory usage scales with file size.
- `ClearFolder` iterates and deletes files synchronously.
## 8. Dependency Graph
**Must be implemented after**: Data Layer (ResourcesConfig), Authentication & Security (encryption).
**Can be implemented in parallel with**: User Management.
**Blocks**: Admin API.
## 9. Logging Strategy
| Log Level | When | Example |
|-----------|------|---------|
| INFO | Successful file save | `Resource {data.FileName} Saved Successfully` |
**Log format**: String interpolation via Serilog.
**Log storage**: Console + rolling file (via Serilog configured in Program.cs).
## Modules Covered
- `Services/ResourcesService`
- `Common/Requests/GetResourceRequest` (includes CheckResourceRequest)
- `Common/Configs/ResourcesConfig`
@@ -0,0 +1,124 @@
# Admin API
## 1. High-Level Overview
**Purpose**: HTTP API entry point — configures DI, middleware pipeline, authentication, authorization, CORS, Swagger, and defines all REST endpoints using ASP.NET Core Minimal API.
**Architectural Pattern**: Composition root + Minimal API endpoints — top-level statements configure the application and map HTTP routes to service methods.
**Upstream dependencies**: User Management (IUserService), Authentication & Security (IAuthService, Security), Resource Management (IResourcesService), Data Layer (IDbFactory, ICache, configs).
**Downstream consumers**: None (top-level entry point, consumed by HTTP clients).
## 2. Internal Interfaces
### BusinessExceptionHandler
| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `TryHandleAsync` | `HttpContext, Exception, CancellationToken` | `bool` | Yes | None |
Converts `BusinessException` to HTTP 409 JSON response: `{ ErrorCode: int, Message: string }`.
## 3. External API Specification
### Authentication
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `/login` | POST | Anonymous | Validates credentials, returns JWT |
### User Management
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `/users` | POST | ApiAdmin | Creates a new user |
| `/users/current` | GET | Authenticated | Returns current user |
| `/users` | GET | ApiAdmin | Lists users (optional email/role filters) |
| `/users/hardware/set` | PUT | ApiAdmin | Sets user hardware |
| `/users/queue-offsets/set` | PUT | Authenticated | Updates queue offsets |
| `/users/{email}/set-role/{role}` | PUT | ApiAdmin | Changes user role |
| `/users/{email}/enable` | PUT | ApiAdmin | Enables user |
| `/users/{email}/disable` | PUT | ApiAdmin | Disables user |
| `/users/{email}` | DELETE | ApiAdmin | Removes user |
### Resource Management
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `/resources/{dataFolder?}` | POST | Authenticated | Uploads a file (up to 200 MB) |
| `/resources/list/{dataFolder?}` | GET | Authenticated | Lists files |
| `/resources/clear/{dataFolder?}` | POST | ApiAdmin | Clears folder |
| `/resources/get/{dataFolder?}` | POST | Authenticated | Downloads encrypted resource |
| `/resources/get-installer` | GET | Authenticated | Downloads production installer |
| `/resources/get-installer/stage` | GET | Authenticated | Downloads staging installer |
| `/resources/check` | POST | Authenticated | Validates hardware |
### Authorization Policies
- **apiAdminPolicy**: requires `ApiAdmin` role (used on most admin endpoints)
- **apiUploaderPolicy**: requires `ResourceUploader` or `ApiAdmin` role (**defined but never applied to any endpoint — dead code**)
### CORS
- Allowed origins: `https://admin.azaion.com`, `http://admin.azaion.com`
- All methods/headers, credentials allowed
## 4. Data Access Patterns
No direct data access — delegates to service components.
## 5. Implementation Details
**State Management**: Stateless — ASP.NET Core request pipeline.
**Key Dependencies**:
| Library | Version | Purpose |
|---------|---------|---------|
| Swashbuckle.AspNetCore | 10.1.4 | Swagger/OpenAPI documentation |
| FluentValidation.AspNetCore | 11.3.0 | Request validation pipeline |
| Serilog | 4.1.0 | Structured logging |
| Serilog.Sinks.Console | 6.0.0 | Console log output |
| Serilog.Sinks.File | 6.0.0 | Rolling file log output |
**Error Handling Strategy**:
- `BusinessException``BusinessExceptionHandler` → HTTP 409 with JSON body.
- `UnauthorizedAccessException` → thrown in resource endpoints when current user is null.
- `FileNotFoundException` → thrown when installer not found.
- FluentValidation errors → automatic 400 Bad Request via middleware.
- Unhandled exceptions → default ASP.NET Core exception handling.
## 6. Extensions and Helpers
None.
## 7. Caveats & Edge Cases
**Known limitations**:
- All endpoints are defined in a single `Program.cs` file — no route grouping or controller separation.
- Swagger UI only available in Development environment.
- CORS origins are hardcoded (not configurable).
- Antiforgery disabled for resource upload endpoint.
- Root URL (`/`) redirects to `/swagger`.
**Performance bottlenecks**:
- Kestrel max request body: 200 MB — allows large file uploads but could be a memory concern.
## 8. Dependency Graph
**Must be implemented after**: All other components (composition root).
**Can be implemented in parallel with**: Nothing — depends on all services.
**Blocks**: Nothing.
## 9. Logging Strategy
| Log Level | When | Example |
|-----------|------|---------|
| WARN | Business exception caught | `BusinessExceptionHandler` logs the exception |
| INFO | Serilog minimum level | General application events |
**Log format**: Serilog structured logging with context enrichment.
**Log storage**: Console + rolling file (`logs/log.txt`, daily rotation).
## Modules Covered
- `AdminApi/Program`
- `AdminApi/BusinessExceptionHandler`