Compare commits
153 Commits
023-clean-
...
09e59ba88b
| Author | SHA1 | Date | |
|---|---|---|---|
| 09e59ba88b | |||
| 638597f182 | |||
| bb921ce5dd | |||
| fa380ff9a5 | |||
| ce3955ed2e | |||
| 19898b1570 | |||
| da24fb9253 | |||
| 80b28ac371 | |||
| f24200d52a | |||
| 5d45b4adb0 | |||
| daa9f7be3a | |||
| 7e43830144 | |||
| 066747de59 | |||
| 442d0e0ac2 | |||
| 8fa951fc93 | |||
| 149d230426 | |||
| 4c601fbe06 | |||
| 36173c0880 | |||
| 81d62c1345 | |||
| a8f7147500 | |||
| ce684bc5d1 | |||
| 484019e750 | |||
| 4ff6d307f8 | |||
| f4612c0737 | |||
| 5ec1254336 | |||
| b7d1ee2b71 | |||
| 87285d8f0a | |||
| 04b01eadb5 | |||
| 4d5b9e88dd | |||
| 4bad4ab4e2 | |||
| 3801ca13d9 | |||
| 999c0c54df | |||
| f9ac282596 | |||
| 5d42a6b930 | |||
| 99f19ac305 | |||
| 590ba49ddb | |||
| 2a5b225800 | |||
| 33433c3173 | |||
| 21e969a769 | |||
| 783644c6ad | |||
| d32d85556f | |||
| bc0367ab72 | |||
| 1c362f4092 | |||
| 95ae9c6af1 | |||
| 7a12ed0931 | |||
| e0c0dd3221 | |||
| 5f6e9c0cc0 | |||
| 4fd9d6b6d5 | |||
| 7e6bd56488 | |||
| 5e3c213b92 | |||
| 37b75b5a5c | |||
| 3d42a487f7 | |||
| 2e93f5ca63 | |||
| 286167b1d5 | |||
| 7df7b4f98c | |||
| ab1c87ffba | |||
| 40e6d8cd4c | |||
| 18e96a58bc | |||
| 83e4875097 | |||
| e635bd7e5f | |||
| 43dd97ecbf | |||
| 0685f50ae7 | |||
| d0ffc2f1df | |||
| 26880d2e09 | |||
| 008b6d72c9 | |||
| f0c85e4c03 | |||
| 6ffdf5f8a4 | |||
| 0cf0ef25f1 | |||
| af74841765 | |||
| d7e4919d54 | |||
| fdcbe32dfa | |||
| 4de5b22d57 | |||
| c8029ed309 | |||
| c2a4c8062a | |||
| 2c820e103a | |||
| c8b84b7bd7 | |||
| fdb944f123 | |||
| d29bc511a2 | |||
| a3a9f0788d | |||
| 77147dc95b | |||
| 026239e3bf | |||
| 4a0273a604 | |||
| edb2dd5263 | |||
| 76b98fcf8f | |||
| 794cc55fe7 | |||
| 235b0e3c9f | |||
| e6087bd3c1 | |||
| 0f16bab2b8 | |||
| 7de96c17c4 | |||
| f018b97ed2 | |||
| 72846aa835 | |||
| 994c0c3e5d | |||
| 252a8601a9 | |||
| 8044f85ea4 | |||
| d4109e5a03 | |||
| b2bbd73439 | |||
| 0e0e26e2f7 | |||
| 18b42f8dd0 | |||
| e7b31accd6 | |||
| d3c3a80ed2 | |||
| cc244c2d86 | |||
| d10c23e658 | |||
| 1042b35d1b | |||
| 16ffeb1ed6 | |||
| da34deac02 | |||
| 51e9ee3fcc | |||
| edf9286071 | |||
| a542e7d2df | |||
| a863807cf2 | |||
| e2bc68683f | |||
| 43cb82697b | |||
| 4ba28cf93e | |||
| 343f2e29f5 | |||
| c9a53578fd | |||
| 07ec2d9797 | |||
| e9d3f3c827 | |||
| 26ba015b75 | |||
| 49129d3e86 | |||
| d99a13d91f | |||
| 203ce446f4 | |||
| c96d50a3f4 | |||
| 3bbe320949 | |||
| 2d2435642d | |||
| ec8d67c956 | |||
| 76baeb1038 | |||
| 11c59fb420 | |||
| b2529973eb | |||
| ae1d630ad6 | |||
| 9a9c5879e6 | |||
| 696aac32e7 | |||
| 7a9b1a190a | |||
| a3dc1fb2b9 | |||
| 297b29986d | |||
| 4c6fc8256d | |||
| a747a163c8 | |||
| fce0941e98 | |||
| 45c077b928 | |||
| 9ed3a5992d | |||
| a032fe8457 | |||
| 4c9d554432 | |||
| 6962a78112 | |||
| 3d75a21127 | |||
| 07914c8728 | |||
| cddc259b76 | |||
| dcbf0a7d7f | |||
| 65f61c1f80 | |||
| cb7386f274 | |||
| 83e34e1799 | |||
| d197303b9f | |||
| a43f8fb021 | |||
| 4aa01b6470 | |||
| 35b423979d | |||
| 2ffc3cc68f |
@@ -2,12 +2,12 @@
|
||||
|
||||
> High-level module structure for AI Context. Generated automatically.
|
||||
|
||||
**Generated:** 2026-03-04T13:18:11.370535
|
||||
**Generated:** 2026-03-01T12:09:39.463912
|
||||
|
||||
## Summary
|
||||
|
||||
- **Total Modules:** 83
|
||||
- **Total Entities:** 2349
|
||||
- **Total Modules:** 80
|
||||
- **Total Entities:** 2080
|
||||
|
||||
## Module Hierarchy
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
### 📁 `src/`
|
||||
|
||||
- 🏗️ **Layers:** API, Core, UI (API)
|
||||
- 📊 **Tiers:** CRITICAL: 2, STANDARD: 20, TRIVIAL: 2
|
||||
- 📊 **Tiers:** CRITICAL: 2, STANDARD: 19, TRIVIAL: 2
|
||||
- 📄 **Files:** 2
|
||||
- 📦 **Entities:** 24
|
||||
- 📦 **Entities:** 23
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -42,21 +42,21 @@
|
||||
### 📁 `api/`
|
||||
|
||||
- 🏗️ **Layers:** API
|
||||
- 📊 **Tiers:** CRITICAL: 7
|
||||
- 📊 **Tiers:** STANDARD: 7
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 7
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 📦 **backend.src.api.auth** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.api.auth** (Module)
|
||||
- Authentication API endpoints.
|
||||
|
||||
### 📁 `routes/`
|
||||
|
||||
- 🏗️ **Layers:** API, UI (API)
|
||||
- 📊 **Tiers:** CRITICAL: 11, STANDARD: 226, TRIVIAL: 8
|
||||
- 📄 **Files:** 18
|
||||
- 📦 **Entities:** 245
|
||||
- 📊 **Tiers:** CRITICAL: 3, STANDARD: 205, TRIVIAL: 7
|
||||
- 📄 **Files:** 17
|
||||
- 📦 **Entities:** 215
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -91,10 +91,10 @@
|
||||
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** API, Domain, Domain (Tests), UI (API Tests), Unknown
|
||||
- 📊 **Tiers:** STANDARD: 63, TRIVIAL: 134
|
||||
- 📄 **Files:** 12
|
||||
- 📦 **Entities:** 197
|
||||
- 🏗️ **Layers:** API, Domain (Tests), UI (API Tests)
|
||||
- 📊 **Tiers:** STANDARD: 61, TRIVIAL: 121
|
||||
- 📄 **Files:** 9
|
||||
- 📦 **Entities:** 182
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
### 📁 `core/`
|
||||
|
||||
- 🏗️ **Layers:** Core
|
||||
- 📊 **Tiers:** CRITICAL: 45, STANDARD: 88, TRIVIAL: 8
|
||||
- 📊 **Tiers:** CRITICAL: 2, STANDARD: 131, TRIVIAL: 8
|
||||
- 📄 **Files:** 10
|
||||
- 📦 **Entities:** 141
|
||||
|
||||
@@ -136,13 +136,13 @@
|
||||
- A session factory for the authentication database.
|
||||
- ℂ **BeliefFormatter** (Class)
|
||||
- Custom logging formatter that adds belief state prefixes to ...
|
||||
- ℂ **ConfigManager** (Class) `[CRITICAL]`
|
||||
- ℂ **ConfigManager** (Class)
|
||||
- A class to handle application configuration persistence and ...
|
||||
- ℂ **IdMappingService** (Class) `[CRITICAL]`
|
||||
- Service handling the cataloging and retrieval of remote Supe...
|
||||
- ℂ **LogEntry** (Class)
|
||||
- A Pydantic model representing a single, structured log entry...
|
||||
- ℂ **MigrationEngine** (Class) `[CRITICAL]`
|
||||
- ℂ **MigrationEngine** (Class)
|
||||
- Engine for transforming Superset export ZIPs.
|
||||
- ℂ **PluginBase** (Class)
|
||||
- Defines the abstract base class that all plugins must implem...
|
||||
@@ -164,27 +164,27 @@
|
||||
### 📁 `auth/`
|
||||
|
||||
- 🏗️ **Layers:** Core
|
||||
- 📊 **Tiers:** CRITICAL: 26
|
||||
- 📊 **Tiers:** STANDARD: 26
|
||||
- 📄 **Files:** 6
|
||||
- 📦 **Entities:** 26
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **AuthConfig** (Class) `[CRITICAL]`
|
||||
- ℂ **AuthConfig** (Class)
|
||||
- Holds authentication-related settings.
|
||||
- ℂ **AuthRepository** (Class) `[CRITICAL]`
|
||||
- ℂ **AuthRepository** (Class)
|
||||
- Encapsulates database operations for authentication.
|
||||
- 📦 **backend.src.core.auth.config** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.auth.config** (Module)
|
||||
- Centralized configuration for authentication and authorizati...
|
||||
- 📦 **backend.src.core.auth.jwt** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.auth.jwt** (Module)
|
||||
- JWT token generation and validation logic.
|
||||
- 📦 **backend.src.core.auth.logger** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.auth.logger** (Module)
|
||||
- Audit logging for security-related events.
|
||||
- 📦 **backend.src.core.auth.oauth** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.auth.oauth** (Module)
|
||||
- ADFS OIDC configuration and client using Authlib.
|
||||
- 📦 **backend.src.core.auth.repository** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.auth.repository** (Module)
|
||||
- Data access layer for authentication-related entities.
|
||||
- 📦 **backend.src.core.auth.security** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.auth.security** (Module)
|
||||
- Utility for password hashing and verification using Passlib.
|
||||
|
||||
**Dependencies:**
|
||||
@@ -222,23 +222,23 @@
|
||||
### 📁 `migration/`
|
||||
|
||||
- 🏗️ **Layers:** Core
|
||||
- 📊 **Tiers:** CRITICAL: 20, TRIVIAL: 1
|
||||
- 📊 **Tiers:** STANDARD: 20, TRIVIAL: 1
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 21
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **MigrationArchiveParser** (Class) `[CRITICAL]`
|
||||
- ℂ **MigrationArchiveParser** (Class)
|
||||
- Extract normalized dashboards/charts/datasets metadata from ...
|
||||
- ℂ **MigrationDryRunService** (Class) `[CRITICAL]`
|
||||
- ℂ **MigrationDryRunService** (Class)
|
||||
- Build deterministic diff/risk payload for migration pre-flig...
|
||||
- 📦 **backend.src.core.migration.__init__** (Module) `[TRIVIAL]`
|
||||
- Namespace package for migration pre-flight orchestration com...
|
||||
- 📦 **backend.src.core.migration.archive_parser** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.migration.archive_parser** (Module)
|
||||
- Parse Superset export ZIP archives into normalized object ca...
|
||||
- 📦 **backend.src.core.migration.dry_run_orchestrator** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.migration.dry_run_orchestrator** (Module)
|
||||
- Compute pre-flight migration diff and risk scoring without a...
|
||||
- 📦 **backend.src.core.migration.risk_assessor** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.core.migration.risk_assessor** (Module)
|
||||
- Risk evaluation helpers for migration pre-flight reporting.
|
||||
|
||||
**Dependencies:**
|
||||
@@ -285,24 +285,12 @@
|
||||
- 🔗 DEPENDS_ON -> TaskLogger, USED_BY -> plugins
|
||||
- 🔗 DEPENDS_ON -> TaskManager, CALLS -> TaskManager._add_log
|
||||
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** Unknown
|
||||
- 📊 **Tiers:** TRIVIAL: 9
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 9
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 📦 **test_task_logger** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/src/core/task_manager/__te...
|
||||
|
||||
### 📁 `utils/`
|
||||
|
||||
- 🏗️ **Layers:** Core, Domain, Infra
|
||||
- 📊 **Tiers:** STANDARD: 50, TRIVIAL: 1
|
||||
- 📊 **Tiers:** STANDARD: 48, TRIVIAL: 1
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 51
|
||||
- 📦 **Entities:** 49
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -338,15 +326,15 @@
|
||||
### 📁 `models/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Model
|
||||
- 📊 **Tiers:** CRITICAL: 20, STANDARD: 33, TRIVIAL: 29
|
||||
- 📄 **Files:** 12
|
||||
- 📦 **Entities:** 82
|
||||
- 📊 **Tiers:** CRITICAL: 9, STANDARD: 22, TRIVIAL: 22
|
||||
- 📄 **Files:** 11
|
||||
- 📦 **Entities:** 53
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **ADGroupMapping** (Class) `[CRITICAL]`
|
||||
- ℂ **ADGroupMapping** (Class)
|
||||
- Maps an Active Directory group to a local System Role.
|
||||
- ℂ **AppConfigRecord** (Class) `[CRITICAL]`
|
||||
- ℂ **AppConfigRecord** (Class)
|
||||
- Stores the single source of truth for application configurat...
|
||||
- ℂ **AssistantAuditRecord** (Class)
|
||||
- Store audit decisions and outcomes produced by assistant com...
|
||||
@@ -354,16 +342,16 @@
|
||||
- Persist risky operation confirmation tokens with lifecycle s...
|
||||
- ℂ **AssistantMessageRecord** (Class)
|
||||
- Persist chat history entries for assistant conversations.
|
||||
- ℂ **CheckFinalStatus** (Class)
|
||||
- Final status for compliance check run.
|
||||
- ℂ **CheckStageName** (Class)
|
||||
- Mandatory check stages.
|
||||
- ℂ **CheckStageResult** (Class)
|
||||
- Per-stage compliance result.
|
||||
- ℂ **CheckStageStatus** (Class)
|
||||
- Stage-level execution status.
|
||||
- ℂ **ClassificationType** (Class)
|
||||
- Manifest classification outcomes for artifacts.
|
||||
- ℂ **ConnectionConfig** (Class) `[TRIVIAL]`
|
||||
- Stores credentials for external databases used for column ma...
|
||||
- ℂ **DashboardMetadata** (Class) `[TRIVIAL]`
|
||||
- Represents a dashboard available for migration.
|
||||
- ℂ **DashboardSelection** (Class) `[TRIVIAL]`
|
||||
- Represents the user's selection of dashboards to migrate.
|
||||
- ℂ **DatabaseMapping** (Class)
|
||||
- Represents a mapping between source and target databases.
|
||||
- ℂ **DeploymentEnvironment** (Class) `[TRIVIAL]`
|
||||
- Target Superset environments for dashboard deployment.
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
@@ -375,15 +363,13 @@
|
||||
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 2, TRIVIAL: 38
|
||||
- 📄 **Files:** 3
|
||||
- 📦 **Entities:** 40
|
||||
- 🏗️ **Layers:** Domain
|
||||
- 📊 **Tiers:** STANDARD: 2, TRIVIAL: 27
|
||||
- 📄 **Files:** 2
|
||||
- 📦 **Entities:** 29
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 📦 **test_clean_release** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/src/models/__tests__/test_...
|
||||
- 📦 **test_models** (Module) `[TRIVIAL]`
|
||||
- Unit tests for data models
|
||||
- 📦 **test_report_models** (Module)
|
||||
@@ -392,7 +378,7 @@
|
||||
### 📁 `plugins/`
|
||||
|
||||
- 🏗️ **Layers:** App, Plugin, Plugins
|
||||
- 📊 **Tiers:** CRITICAL: 10, STANDARD: 53
|
||||
- 📊 **Tiers:** STANDARD: 63
|
||||
- 📄 **Files:** 6
|
||||
- 📦 **Entities:** 63
|
||||
|
||||
@@ -406,7 +392,7 @@
|
||||
- Реализация плагина Git Integration для управления версиями д...
|
||||
- ℂ **MapperPlugin** (Class)
|
||||
- Plugin for mapping dataset columns verbose names.
|
||||
- ℂ **MigrationPlugin** (Class) `[CRITICAL]`
|
||||
- ℂ **MigrationPlugin** (Class)
|
||||
- Implementation of the migration plugin logic.
|
||||
- ℂ **SearchPlugin** (Class)
|
||||
- Plugin for searching text patterns in Superset datasets.
|
||||
@@ -416,7 +402,7 @@
|
||||
- Implements a plugin for system diagnostics and debugging Sup...
|
||||
- 📦 **MapperPluginModule** (Module)
|
||||
- Implements a plugin for mapping dataset columns using extern...
|
||||
- 📦 **MigrationPlugin** (Module) `[CRITICAL]`
|
||||
- 📦 **MigrationPlugin** (Module)
|
||||
- A plugin that provides functionality to migrate Superset das...
|
||||
|
||||
**Dependencies:**
|
||||
@@ -495,31 +481,31 @@
|
||||
### 📁 `schemas/`
|
||||
|
||||
- 🏗️ **Layers:** API
|
||||
- 📊 **Tiers:** CRITICAL: 10, TRIVIAL: 3
|
||||
- 📊 **Tiers:** STANDARD: 10, TRIVIAL: 3
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 13
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **ADGroupMappingCreate** (Class) `[CRITICAL]`
|
||||
- ℂ **ADGroupMappingCreate** (Class)
|
||||
- Schema for creating an AD Group mapping.
|
||||
- ℂ **ADGroupMappingSchema** (Class) `[CRITICAL]`
|
||||
- ℂ **ADGroupMappingSchema** (Class)
|
||||
- Represents an AD Group to Role mapping in API responses.
|
||||
- ℂ **PermissionSchema** (Class) `[TRIVIAL]`
|
||||
- Represents a permission in API responses.
|
||||
- ℂ **RoleCreate** (Class) `[CRITICAL]`
|
||||
- ℂ **RoleCreate** (Class)
|
||||
- Schema for creating a new role.
|
||||
- ℂ **RoleSchema** (Class) `[CRITICAL]`
|
||||
- ℂ **RoleSchema** (Class)
|
||||
- Represents a role in API responses.
|
||||
- ℂ **RoleUpdate** (Class) `[CRITICAL]`
|
||||
- ℂ **RoleUpdate** (Class)
|
||||
- Schema for updating an existing role.
|
||||
- ℂ **Token** (Class) `[TRIVIAL]`
|
||||
- Represents a JWT access token response.
|
||||
- ℂ **TokenData** (Class) `[TRIVIAL]`
|
||||
- Represents the data encoded in a JWT token.
|
||||
- ℂ **User** (Class) `[CRITICAL]`
|
||||
- ℂ **User** (Class)
|
||||
- Schema for user data in API responses.
|
||||
- ℂ **UserBase** (Class) `[CRITICAL]`
|
||||
- ℂ **UserBase** (Class)
|
||||
- Base schema for user data.
|
||||
|
||||
**Dependencies:**
|
||||
@@ -528,18 +514,16 @@
|
||||
|
||||
### 📁 `scripts/`
|
||||
|
||||
- 🏗️ **Layers:** Scripts, UI, Unknown
|
||||
- 📊 **Tiers:** CRITICAL: 2, STANDARD: 25, TRIVIAL: 3
|
||||
- 📄 **Files:** 7
|
||||
- 📦 **Entities:** 30
|
||||
- 🏗️ **Layers:** Scripts, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 26, TRIVIAL: 2
|
||||
- 📄 **Files:** 6
|
||||
- 📦 **Entities:** 28
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 📦 **backend.src.scripts.clean_release_tui** (Module)
|
||||
- Provide clean release TUI entrypoint placeholder for phased ...
|
||||
- 📦 **backend.src.scripts.create_admin** (Module)
|
||||
- CLI tool for creating the initial admin user.
|
||||
- 📦 **backend.src.scripts.init_auth_db** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.scripts.init_auth_db** (Module)
|
||||
- Initializes the auth database and creates the necessary tabl...
|
||||
- 📦 **backend.src.scripts.migrate_sqlite_to_postgres** (Module)
|
||||
- Migrates legacy config and task history from SQLite/file sto...
|
||||
@@ -553,13 +537,13 @@
|
||||
### 📁 `services/`
|
||||
|
||||
- 🏗️ **Layers:** Core, Domain, Service
|
||||
- 📊 **Tiers:** CRITICAL: 7, STANDARD: 76, TRIVIAL: 6
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 62, TRIVIAL: 6
|
||||
- 📄 **Files:** 7
|
||||
- 📦 **Entities:** 89
|
||||
- 📦 **Entities:** 69
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **AuthService** (Class) `[CRITICAL]`
|
||||
- ℂ **AuthService** (Class)
|
||||
- Provides high-level authentication services.
|
||||
- ℂ **EncryptionManager** (Class) `[CRITICAL]`
|
||||
- Handles encryption and decryption of sensitive data like API...
|
||||
@@ -573,7 +557,7 @@
|
||||
- Provides centralized access to resource data with enhanced m...
|
||||
- 📦 **backend.src.services** (Module)
|
||||
- Package initialization for services module
|
||||
- 📦 **backend.src.services.auth_service** (Module) `[CRITICAL]`
|
||||
- 📦 **backend.src.services.auth_service** (Module)
|
||||
- Orchestrates authentication business logic.
|
||||
- 📦 **backend.src.services.git_service** (Module)
|
||||
- Core Git logic using GitPython to manage dashboard repositor...
|
||||
@@ -590,10 +574,10 @@
|
||||
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Domain Tests, Service, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 24, TRIVIAL: 17
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 41
|
||||
- 🏗️ **Layers:** Domain, Domain Tests, Service
|
||||
- 📊 **Tiers:** STANDARD: 24, TRIVIAL: 7
|
||||
- 📄 **Files:** 3
|
||||
- 📦 **Entities:** 31
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -605,76 +589,11 @@
|
||||
- Unit tests for ResourceService
|
||||
- 📦 **test_encryption_manager** (Module)
|
||||
- Unit tests for EncryptionManager encrypt/decrypt functionali...
|
||||
- 📦 **test_llm_provider** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/src/services/__tests__/tes...
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- 🔗 DEPENDS_ON -> backend.src.services.llm_prompt_templates
|
||||
|
||||
### 📁 `clean_release/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Infra
|
||||
- 📊 **Tiers:** CRITICAL: 3, STANDARD: 12, TRIVIAL: 33
|
||||
- 📄 **Files:** 10
|
||||
- 📦 **Entities:** 48
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **CleanPolicyEngine** (Class)
|
||||
- 📦 **backend.src.services.clean_release** (Module)
|
||||
- Initialize clean release service package and provide explici...
|
||||
- 📦 **backend.src.services.clean_release.audit_service** (Module)
|
||||
- Provide lightweight audit hooks for clean release preparatio...
|
||||
- 📦 **backend.src.services.clean_release.compliance_orchestrator** (Module) `[CRITICAL]`
|
||||
- Execute mandatory clean compliance stages and produce final ...
|
||||
- 📦 **backend.src.services.clean_release.manifest_builder** (Module)
|
||||
- Build deterministic distribution manifest from classified ar...
|
||||
- 📦 **backend.src.services.clean_release.policy_engine** (Module) `[CRITICAL]`
|
||||
- Evaluate artifact/source policies for enterprise clean profi...
|
||||
- 📦 **backend.src.services.clean_release.preparation_service** (Module)
|
||||
- Prepare release candidate by policy evaluation and determini...
|
||||
- 📦 **backend.src.services.clean_release.report_builder** (Module) `[CRITICAL]`
|
||||
- Build and persist compliance reports with consistent counter...
|
||||
- 📦 **backend.src.services.clean_release.repository** (Module)
|
||||
- Provide repository adapter for clean release entities with d...
|
||||
- 📦 **backend.src.services.clean_release.source_isolation** (Module)
|
||||
- Validate that all resource endpoints belong to the approved ...
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- 🔗 DEPENDS_ON -> backend.src.core.logger
|
||||
- 🔗 DEPENDS_ON -> backend.src.models.clean_release
|
||||
- 🔗 DEPENDS_ON -> backend.src.models.clean_release.CleanProfilePolicy
|
||||
- 🔗 DEPENDS_ON -> backend.src.models.clean_release.ResourceSourceRegistry
|
||||
- 🔗 DEPENDS_ON -> backend.src.services.clean_release.manifest_builder
|
||||
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Infra, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 18, TRIVIAL: 25
|
||||
- 📄 **Files:** 8
|
||||
- 📦 **Entities:** 43
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 📦 **backend.tests.services.clean_release.test_audit_service** (Module)
|
||||
- Validate audit hooks emit expected log patterns for clean re...
|
||||
- 📦 **backend.tests.services.clean_release.test_compliance_orchestrator** (Module)
|
||||
- Validate compliance orchestrator stage transitions and final...
|
||||
- 📦 **backend.tests.services.clean_release.test_manifest_builder** (Module)
|
||||
- Validate deterministic manifest generation behavior for US1.
|
||||
- 📦 **backend.tests.services.clean_release.test_preparation_service** (Module)
|
||||
- Validate release candidate preparation flow, including polic...
|
||||
- 📦 **backend.tests.services.clean_release.test_report_builder** (Module)
|
||||
- Validate compliance report builder counter integrity and blo...
|
||||
- 📦 **backend.tests.services.clean_release.test_source_isolation** (Module)
|
||||
- Verify internal source registry validation behavior.
|
||||
- 📦 **backend.tests.services.clean_release.test_stages** (Module)
|
||||
- Validate final status derivation logic from stage results.
|
||||
- 📦 **test_policy_engine** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/src/services/clean_release...
|
||||
|
||||
### 📁 `reports/`
|
||||
|
||||
- 🏗️ **Layers:** Domain
|
||||
@@ -703,10 +622,10 @@
|
||||
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Domain (Tests), Unknown
|
||||
- 📊 **Tiers:** STANDARD: 2, TRIVIAL: 24
|
||||
- 📄 **Files:** 3
|
||||
- 📦 **Entities:** 26
|
||||
- 🏗️ **Layers:** Domain, Domain (Tests)
|
||||
- 📊 **Tiers:** STANDARD: 2, TRIVIAL: 19
|
||||
- 📄 **Files:** 2
|
||||
- 📦 **Entities:** 21
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -714,33 +633,31 @@
|
||||
- Validate unknown task type fallback and partial payload norm...
|
||||
- 📦 **test_report_service** (Module)
|
||||
- Unit tests for ReportsService list/detail operations
|
||||
- 📦 **test_type_profiles** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/src/services/reports/__tes...
|
||||
|
||||
### 📁 `tests/`
|
||||
|
||||
- 🏗️ **Layers:** Core, Domain (Tests), Logging (Tests), Test, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 86, TRIVIAL: 85
|
||||
- 🏗️ **Layers:** Core, Domain (Tests), Test, Unknown
|
||||
- 📊 **Tiers:** CRITICAL: 6, STANDARD: 79, TRIVIAL: 85
|
||||
- 📄 **Files:** 10
|
||||
- 📦 **Entities:** 171
|
||||
- 📦 **Entities:** 170
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- ℂ **TestLogPersistence** (Class)
|
||||
- ℂ **TestLogPersistence** (Class) `[CRITICAL]`
|
||||
- Test suite for TaskLogPersistenceService.
|
||||
- ℂ **TestTaskContext** (Class)
|
||||
- Test suite for TaskContext.
|
||||
- ℂ **TestTaskLogger** (Class)
|
||||
- Test suite for TaskLogger.
|
||||
- ℂ **TestTaskPersistenceHelpers** (Class)
|
||||
- ℂ **TestTaskPersistenceHelpers** (Class) `[CRITICAL]`
|
||||
- Test suite for TaskPersistenceService static helper methods.
|
||||
- ℂ **TestTaskPersistenceService** (Class)
|
||||
- ℂ **TestTaskPersistenceService** (Class) `[CRITICAL]`
|
||||
- Test suite for TaskPersistenceService CRUD operations.
|
||||
- 📦 **backend.tests.test_dashboards_api** (Module)
|
||||
- Comprehensive contract-driven tests for Dashboard Hub API
|
||||
- 📦 **test_auth** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/tests/test_auth.py
|
||||
- 📦 **test_log_persistence** (Module)
|
||||
- 📦 **test_log_persistence** (Module) `[CRITICAL]`
|
||||
- Unit tests for TaskLogPersistenceService.
|
||||
- 📦 **test_resource_hubs** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for backend/tests/test_resource_hubs.p...
|
||||
@@ -750,14 +667,12 @@
|
||||
### 📁 `core/`
|
||||
|
||||
- 🏗️ **Layers:** Domain, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 5, TRIVIAL: 33
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 38
|
||||
- 📊 **Tiers:** STANDARD: 2, TRIVIAL: 31
|
||||
- 📄 **Files:** 3
|
||||
- 📦 **Entities:** 33
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 📦 **backend.tests.core.test_git_service_gitea_pr** (Module)
|
||||
- Validate Gitea PR creation fallback behavior when configured...
|
||||
- 📦 **backend.tests.core.test_mapping_service** (Module)
|
||||
- Unit tests for the IdMappingService matching UUIDs to intege...
|
||||
- 📦 **backend.tests.core.test_migration_engine** (Module)
|
||||
@@ -782,7 +697,7 @@
|
||||
### 📁 `components/`
|
||||
|
||||
- 🏗️ **Layers:** Component, Feature, UI, UI -->, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 69, TRIVIAL: 4
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 68, TRIVIAL: 4
|
||||
- 📄 **Files:** 14
|
||||
- 📦 **Entities:** 73
|
||||
|
||||
@@ -836,9 +751,9 @@
|
||||
### 📁 `git/`
|
||||
|
||||
- 🏗️ **Layers:** Component
|
||||
- 📊 **Tiers:** STANDARD: 45
|
||||
- 📊 **Tiers:** STANDARD: 28
|
||||
- 📄 **Files:** 6
|
||||
- 📦 **Entities:** 45
|
||||
- 📦 **Entities:** 28
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -853,12 +768,12 @@
|
||||
- 🧩 **DeploymentModal** (Component)
|
||||
- Modal for deploying a dashboard to a target environment.
|
||||
- 🧩 **GitManager** (Component)
|
||||
- Центральный UI управления Git с фокусом на рабочий поток ана...
|
||||
- Центральный компонент для управления Git-операциями конкретн...
|
||||
|
||||
### 📁 `llm/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 1, TRIVIAL: 11
|
||||
- 📊 **Tiers:** STANDARD: 2, TRIVIAL: 11
|
||||
- 📄 **Files:** 3
|
||||
- 📦 **Entities:** 13
|
||||
|
||||
@@ -866,7 +781,7 @@
|
||||
|
||||
- 🧩 **DocPreview** (Component)
|
||||
- UI component for previewing generated dataset documentation ...
|
||||
- 🧩 **ProviderConfig** (Component) `[CRITICAL]`
|
||||
- 🧩 **ProviderConfig** (Component)
|
||||
- UI form for managing LLM provider configurations.
|
||||
- 📦 **DocPreview** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for frontend/src/components/llm/DocPre...
|
||||
@@ -946,9 +861,9 @@
|
||||
### 📁 `lib/`
|
||||
|
||||
- 🏗️ **Layers:** Infra, Infra-API, UI, UI-State
|
||||
- 📊 **Tiers:** STANDARD: 24, TRIVIAL: 5
|
||||
- 📊 **Tiers:** STANDARD: 24, TRIVIAL: 3
|
||||
- 📄 **Files:** 5
|
||||
- 📦 **Entities:** 29
|
||||
- 📦 **Entities:** 27
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1003,25 +918,25 @@
|
||||
### 📁 `auth/`
|
||||
|
||||
- 🏗️ **Layers:** Feature
|
||||
- 📊 **Tiers:** CRITICAL: 7
|
||||
- 📊 **Tiers:** STANDARD: 7
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 7
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 🗄️ **authStore** (Store) `[CRITICAL]`
|
||||
- 🗄️ **authStore** (Store)
|
||||
- Manages the global authentication state on the frontend.
|
||||
|
||||
### 📁 `assistant/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 14, TRIVIAL: 5
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 13, TRIVIAL: 5
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 19
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 🧩 **AssistantChatPanel** (Component)
|
||||
- 🧩 **AssistantChatPanel** (Component) `[CRITICAL]`
|
||||
- Slide-out assistant chat panel for natural language command ...
|
||||
- 📦 **AssistantChatPanel** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for frontend/src/lib/components/assist...
|
||||
@@ -1041,7 +956,7 @@
|
||||
### 📁 `layout/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 8, TRIVIAL: 48
|
||||
- 📊 **Tiers:** CRITICAL: 3, STANDARD: 5, TRIVIAL: 48
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 56
|
||||
|
||||
@@ -1049,11 +964,11 @@
|
||||
|
||||
- 🧩 **Breadcrumbs** (Component)
|
||||
- Display page hierarchy navigation
|
||||
- 🧩 **Sidebar** (Component)
|
||||
- 🧩 **Sidebar** (Component) `[CRITICAL]`
|
||||
- Persistent left sidebar with resource categories navigation
|
||||
- 🧩 **TaskDrawer** (Component)
|
||||
- 🧩 **TaskDrawer** (Component) `[CRITICAL]`
|
||||
- Global task drawer for monitoring background operations
|
||||
- 🧩 **TopNavbar** (Component)
|
||||
- 🧩 **TopNavbar** (Component) `[CRITICAL]`
|
||||
- Unified top navigation bar with Logo, Search, Activity, and ...
|
||||
- 📦 **Breadcrumbs** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for frontend/src/lib/components/layout...
|
||||
@@ -1079,17 +994,17 @@
|
||||
### 📁 `reports/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 4, TRIVIAL: 10
|
||||
- 📊 **Tiers:** CRITICAL: 4, STANDARD: 1, TRIVIAL: 10
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 15
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 🧩 **ReportCard** (Component)
|
||||
- 🧩 **ReportCard** (Component) `[CRITICAL]`
|
||||
- Render one report with explicit textual type label and profi...
|
||||
- 🧩 **ReportDetailPanel** (Component)
|
||||
- 🧩 **ReportDetailPanel** (Component) `[CRITICAL]`
|
||||
- Display detailed report context with diagnostics and actiona...
|
||||
- 🧩 **ReportsList** (Component)
|
||||
- 🧩 **ReportsList** (Component) `[CRITICAL]`
|
||||
- Render unified list of normalized reports with canonical min...
|
||||
- 📦 **ReportCard** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for frontend/src/lib/components/report...
|
||||
@@ -1107,9 +1022,9 @@
|
||||
### 📁 `__tests__/`
|
||||
|
||||
- 🏗️ **Layers:** UI, UI (Tests)
|
||||
- 📊 **Tiers:** STANDARD: 7, TRIVIAL: 4
|
||||
- 📄 **Files:** 7
|
||||
- 📦 **Entities:** 11
|
||||
- 📊 **Tiers:** STANDARD: 6, TRIVIAL: 4
|
||||
- 📄 **Files:** 6
|
||||
- 📦 **Entities:** 10
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1123,8 +1038,6 @@
|
||||
- Validate report type profile mapping and unknown fallback be...
|
||||
- 📦 **frontend.src.lib.components.reports.__tests__.reports_filter_performance** (Module)
|
||||
- Guard test for report filter responsiveness on moderate in-m...
|
||||
- 📦 **frontend.src.lib.components.reports.__tests__.reports_list.ux** (Module)
|
||||
- Test ReportsList component iteration and event forwarding.
|
||||
- 📦 **frontend.src.lib.components.reports.__tests__.reports_page.integration** (Module)
|
||||
- Integration-style checks for unified mixed-type reports rend...
|
||||
|
||||
@@ -1347,9 +1260,9 @@
|
||||
### 📁 `dashboards/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 24, TRIVIAL: 61
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 23, TRIVIAL: 60
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 85
|
||||
- 📦 **Entities:** 84
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1359,9 +1272,9 @@
|
||||
### 📁 `[id]/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 1, TRIVIAL: 28
|
||||
- 📊 **Tiers:** CRITICAL: 1, TRIVIAL: 17
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 29
|
||||
- 📦 **Entities:** 18
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1371,7 +1284,7 @@
|
||||
### 📁 `datasets/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 1, TRIVIAL: 15
|
||||
- 📊 **Tiers:** CRITICAL: 1, TRIVIAL: 15
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 16
|
||||
|
||||
@@ -1383,7 +1296,7 @@
|
||||
### 📁 `[id]/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** STANDARD: 1, TRIVIAL: 6
|
||||
- 📊 **Tiers:** CRITICAL: 1, TRIVIAL: 6
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 7
|
||||
|
||||
@@ -1419,26 +1332,26 @@
|
||||
### 📁 `migration/`
|
||||
|
||||
- 🏗️ **Layers:** Page
|
||||
- 📊 **Tiers:** CRITICAL: 11
|
||||
- 📊 **Tiers:** STANDARD: 11
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 11
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 🧩 **DashboardSelectionSection** (Component) `[CRITICAL]`
|
||||
- 🧩 **MigrationDashboard** (Component) `[CRITICAL]`
|
||||
- 🧩 **DashboardSelectionSection** (Component)
|
||||
- 🧩 **MigrationDashboard** (Component)
|
||||
- Main dashboard for configuring and starting migrations.
|
||||
|
||||
### 📁 `mappings/`
|
||||
|
||||
- 🏗️ **Layers:** Page
|
||||
- 📊 **Tiers:** CRITICAL: 4
|
||||
- 📊 **Tiers:** STANDARD: 4
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 4
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
- 🧩 **MappingManagement** (Component) `[CRITICAL]`
|
||||
- 🧩 **MappingManagement** (Component)
|
||||
- Page for managing database mappings between environments.
|
||||
|
||||
### 📁 `reports/`
|
||||
@@ -1470,9 +1383,9 @@
|
||||
### 📁 `settings/`
|
||||
|
||||
- 🏗️ **Layers:** UI, Unknown
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 1, TRIVIAL: 25
|
||||
- 📊 **Tiers:** CRITICAL: 1, STANDARD: 1, TRIVIAL: 23
|
||||
- 📄 **Files:** 2
|
||||
- 📦 **Entities:** 27
|
||||
- 📦 **Entities:** 25
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1494,9 +1407,9 @@
|
||||
### 📁 `git/`
|
||||
|
||||
- 🏗️ **Layers:** Page
|
||||
- 📊 **Tiers:** STANDARD: 8
|
||||
- 📊 **Tiers:** STANDARD: 5
|
||||
- 📄 **Files:** 1
|
||||
- 📦 **Entities:** 8
|
||||
- 📦 **Entities:** 5
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1555,9 +1468,9 @@
|
||||
### 📁 `services/`
|
||||
|
||||
- 🏗️ **Layers:** Service
|
||||
- 📊 **Tiers:** STANDARD: 33, TRIVIAL: 1
|
||||
- 📊 **Tiers:** STANDARD: 33
|
||||
- 📄 **Files:** 6
|
||||
- 📦 **Entities:** 34
|
||||
- 📦 **Entities:** 33
|
||||
|
||||
**Key Entities:**
|
||||
|
||||
@@ -1587,7 +1500,7 @@
|
||||
### 📁 `root/`
|
||||
|
||||
- 🏗️ **Layers:** DevOps/Tooling, Domain, Unknown
|
||||
- 📊 **Tiers:** CRITICAL: 11, STANDARD: 27, TRIVIAL: 12
|
||||
- 📊 **Tiers:** CRITICAL: 14, STANDARD: 24, TRIVIAL: 12
|
||||
- 📄 **Files:** 4
|
||||
- 📦 **Entities:** 50
|
||||
|
||||
@@ -1595,7 +1508,7 @@
|
||||
|
||||
- ℂ **ComplianceIssue** (Class) `[TRIVIAL]`
|
||||
- Represents a single compliance issue with severity.
|
||||
- ℂ **ReportsService** (Class)
|
||||
- ℂ **ReportsService** (Class) `[CRITICAL]`
|
||||
- Service layer for list/detail report retrieval and normaliza...
|
||||
- ℂ **SemanticEntity** (Class) `[CRITICAL]`
|
||||
- Represents a code entity (Module, Function, Component) found...
|
||||
@@ -1605,7 +1518,7 @@
|
||||
- Severity levels for compliance issues.
|
||||
- ℂ **Tier** (Class) `[TRIVIAL]`
|
||||
- Enumeration of semantic tiers defining validation strictness...
|
||||
- 📦 **backend.src.services.reports.report_service** (Module)
|
||||
- 📦 **backend.src.services.reports.report_service** (Module) `[CRITICAL]`
|
||||
- Aggregate, normalize, filter, and paginate task reports for ...
|
||||
- 📦 **check_test_data** (Module) `[TRIVIAL]`
|
||||
- Auto-generated module for check_test_data.py
|
||||
@@ -1653,10 +1566,6 @@ graph TD
|
||||
routes-->|DEPENDS_ON|backend
|
||||
routes-->|DEPENDS_ON|backend
|
||||
routes-->|DEPENDS_ON|backend
|
||||
routes-->|DEPENDS_ON|backend
|
||||
routes-->|DEPENDS_ON|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
@@ -1716,28 +1625,6 @@ graph TD
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|DEPENDS_ON|backend
|
||||
__tests__-->|TESTS|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
clean_release-->|DEPENDS_ON|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|VERIFIES|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
reports-->|DEPENDS_ON|backend
|
||||
reports-->|DEPENDS_ON|backend
|
||||
reports-->|DEPENDS_ON|backend
|
||||
@@ -1748,13 +1635,11 @@ graph TD
|
||||
__tests__-->|TESTS|backend
|
||||
__tests__-->|TESTS|backend
|
||||
tests-->|TESTS|backend
|
||||
core-->|TESTS|backend
|
||||
core-->|VERIFIES|backend
|
||||
core-->|VERIFIES|backend
|
||||
migration-->|VERIFIES|backend
|
||||
migration-->|VERIFIES|backend
|
||||
__tests__-->|VERIFIES|components
|
||||
__tests__-->|VERIFIES|components
|
||||
__tests__-->|VERIFIES|lib
|
||||
reports-->|DEPENDS_ON|lib
|
||||
__tests__-->|TESTS|routes
|
||||
|
||||
1047
.ai/PROJECT_MAP.md
1047
.ai/PROJECT_MAP.md
File diff suppressed because it is too large
Load Diff
BIN
backend/mappings.db
Normal file
BIN
backend/mappings.db
Normal file
Binary file not shown.
@@ -97,17 +97,17 @@ def test_get_dashboards_with_search(mock_deps):
|
||||
mock_deps["config"].get_environments.return_value = [mock_env]
|
||||
mock_deps["task"].get_all_tasks.return_value = []
|
||||
|
||||
async def mock_get_dashboards(env, tasks, include_git_status=False):
|
||||
async def mock_get_dashboards(env, tasks):
|
||||
return [
|
||||
{"id": 1, "title": "Sales Report", "slug": "sales", "git_status": {"branch": "main", "sync_status": "OK"}, "last_task": None},
|
||||
{"id": 2, "title": "Marketing Dashboard", "slug": "marketing", "git_status": {"branch": "main", "sync_status": "OK"}, "last_task": None}
|
||||
{"id": 1, "title": "Sales Report", "slug": "sales"},
|
||||
{"id": 2, "title": "Marketing Dashboard", "slug": "marketing"}
|
||||
]
|
||||
mock_deps["resource"].get_dashboards_with_status = AsyncMock(
|
||||
side_effect=mock_get_dashboards
|
||||
)
|
||||
|
||||
response = client.get("/api/dashboards?env_id=prod&search=sales")
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# @POST: Filtered result count must match search
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# [DEF:__tests__/test_tasks_logs:Module]
|
||||
# @RELATION: VERIFIES -> ../tasks.py
|
||||
# @PURPOSE: Contract testing for task logs API endpoints.
|
||||
# [/DEF:__tests__/test_tasks_logs:Module]
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from unittest.mock import MagicMock
|
||||
from src.dependencies import get_task_manager, has_permission
|
||||
from src.api.routes.tasks import router
|
||||
|
||||
# @TEST_FIXTURE: mock_app
|
||||
@pytest.fixture
|
||||
def client():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/tasks")
|
||||
|
||||
# Mock TaskManager
|
||||
mock_tm = MagicMock()
|
||||
app.dependency_overrides[get_task_manager] = lambda: mock_tm
|
||||
|
||||
# Mock permissions (bypass for unit test)
|
||||
app.dependency_overrides[has_permission("tasks", "READ")] = lambda: True
|
||||
|
||||
return TestClient(app), mock_tm
|
||||
|
||||
# @TEST_CONTRACT: get_task_logs_api -> Invariants
|
||||
# @TEST_FIXTURE: valid_task_logs_request
|
||||
def test_get_task_logs_success(client):
|
||||
tc, tm = client
|
||||
|
||||
# Setup mock task
|
||||
mock_task = MagicMock()
|
||||
tm.get_task.return_value = mock_task
|
||||
tm.get_task_logs.return_value = [{"level": "INFO", "message": "msg1"}]
|
||||
|
||||
response = tc.get("/tasks/task-1/logs?level=INFO")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [{"level": "INFO", "message": "msg1"}]
|
||||
tm.get_task.assert_called_with("task-1")
|
||||
# Verify filter construction inside route
|
||||
args = tm.get_task_logs.call_args
|
||||
assert args[0][0] == "task-1"
|
||||
assert args[0][1].level == "INFO"
|
||||
|
||||
# @TEST_EDGE: task_not_found
|
||||
def test_get_task_logs_not_found(client):
|
||||
tc, tm = client
|
||||
tm.get_task.return_value = None
|
||||
|
||||
response = tc.get("/tasks/missing/logs")
|
||||
assert response.status_code == 404
|
||||
assert response.json()["detail"] == "Task not found"
|
||||
|
||||
# @TEST_EDGE: invalid_limit
|
||||
def test_get_task_logs_invalid_limit(client):
|
||||
tc, tm = client
|
||||
# limit=0 is ge=1 in Query
|
||||
response = tc.get("/tasks/task-1/logs?limit=0")
|
||||
assert response.status_code == 422
|
||||
|
||||
# @TEST_INVARIANT: response_purity
|
||||
def test_get_task_log_stats_success(client):
|
||||
tc, tm = client
|
||||
tm.get_task.return_value = MagicMock()
|
||||
tm.get_task_log_stats.return_value = {"INFO": 5, "ERROR": 1}
|
||||
|
||||
response = tc.get("/tasks/task-1/logs/stats")
|
||||
assert response.status_code == 200
|
||||
# response_model=LogStats might wrap this, but let's check basic structure
|
||||
# assuming tm.get_task_log_stats returns something compatible with LogStats
|
||||
@@ -4,30 +4,30 @@
|
||||
# @PURPOSE: Defines the FastAPI router for task-related endpoints, allowing clients to create, list, and get the status of tasks.
|
||||
# @LAYER: UI (API)
|
||||
# @RELATION: Depends on the TaskManager. It is included by the main app.
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import List, Dict, Any, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from pydantic import BaseModel
|
||||
from ...core.logger import belief_scope
|
||||
|
||||
from ...core.task_manager import TaskManager, Task, TaskStatus, LogEntry
|
||||
from ...core.task_manager.models import LogFilter, LogStats
|
||||
from ...dependencies import get_task_manager, has_permission, get_current_user, get_config_manager
|
||||
from ...core.config_manager import ConfigManager
|
||||
from ...services.llm_prompt_templates import (
|
||||
is_multimodal_model,
|
||||
normalize_llm_settings,
|
||||
resolve_bound_provider_id,
|
||||
)
|
||||
from ...core.task_manager import TaskManager, Task, TaskStatus, LogEntry
|
||||
from ...core.task_manager.models import LogFilter, LogStats
|
||||
from ...dependencies import get_task_manager, has_permission, get_current_user, get_config_manager
|
||||
from ...core.config_manager import ConfigManager
|
||||
from ...services.llm_prompt_templates import (
|
||||
is_multimodal_model,
|
||||
normalize_llm_settings,
|
||||
resolve_bound_provider_id,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
TASK_TYPE_PLUGIN_MAP = {
|
||||
"llm_validation": ["llm_dashboard_validation"],
|
||||
"backup": ["superset-backup"],
|
||||
"migration": ["superset-migration"],
|
||||
}
|
||||
|
||||
class CreateTaskRequest(BaseModel):
|
||||
router = APIRouter()
|
||||
|
||||
TASK_TYPE_PLUGIN_MAP = {
|
||||
"llm_validation": ["llm_dashboard_validation"],
|
||||
"backup": ["superset-backup"],
|
||||
"migration": ["superset-migration"],
|
||||
}
|
||||
|
||||
class CreateTaskRequest(BaseModel):
|
||||
plugin_id: str
|
||||
params: Dict[str, Any]
|
||||
|
||||
@@ -45,54 +45,54 @@ class ResumeTaskRequest(BaseModel):
|
||||
# @PRE: plugin_id must exist and params must be valid for that plugin.
|
||||
# @POST: A new task is created and started.
|
||||
# @RETURN: Task - The created task instance.
|
||||
async def create_task(
|
||||
request: CreateTaskRequest,
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
current_user = Depends(get_current_user),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
async def create_task(
|
||||
request: CreateTaskRequest,
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
current_user = Depends(get_current_user),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
# Dynamic permission check based on plugin_id
|
||||
has_permission(f"plugin:{request.plugin_id}", "EXECUTE")(current_user)
|
||||
"""
|
||||
Create and start a new task for a given plugin.
|
||||
"""
|
||||
with belief_scope("create_task"):
|
||||
try:
|
||||
# Special handling for LLM tasks to resolve provider config by task binding.
|
||||
if request.plugin_id in {"llm_dashboard_validation", "llm_documentation"}:
|
||||
from ...core.database import SessionLocal
|
||||
from ...services.llm_provider import LLMProviderService
|
||||
db = SessionLocal()
|
||||
try:
|
||||
llm_service = LLMProviderService(db)
|
||||
provider_id = request.params.get("provider_id")
|
||||
if not provider_id:
|
||||
llm_settings = normalize_llm_settings(config_manager.get_config().settings.llm)
|
||||
binding_key = "dashboard_validation" if request.plugin_id == "llm_dashboard_validation" else "documentation"
|
||||
provider_id = resolve_bound_provider_id(llm_settings, binding_key)
|
||||
if provider_id:
|
||||
request.params["provider_id"] = provider_id
|
||||
if not provider_id:
|
||||
providers = llm_service.get_all_providers()
|
||||
active_provider = next((p for p in providers if p.is_active), None)
|
||||
if active_provider:
|
||||
provider_id = active_provider.id
|
||||
request.params["provider_id"] = provider_id
|
||||
|
||||
if provider_id:
|
||||
db_provider = llm_service.get_provider(provider_id)
|
||||
if not db_provider:
|
||||
raise ValueError(f"LLM Provider {provider_id} not found")
|
||||
if request.plugin_id == "llm_dashboard_validation" and not is_multimodal_model(
|
||||
db_provider.default_model,
|
||||
db_provider.provider_type,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Selected provider model is not multimodal for dashboard validation",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
try:
|
||||
# Special handling for LLM tasks to resolve provider config by task binding.
|
||||
if request.plugin_id in {"llm_dashboard_validation", "llm_documentation"}:
|
||||
from ...core.database import SessionLocal
|
||||
from ...services.llm_provider import LLMProviderService
|
||||
db = SessionLocal()
|
||||
try:
|
||||
llm_service = LLMProviderService(db)
|
||||
provider_id = request.params.get("provider_id")
|
||||
if not provider_id:
|
||||
llm_settings = normalize_llm_settings(config_manager.get_config().settings.llm)
|
||||
binding_key = "dashboard_validation" if request.plugin_id == "llm_dashboard_validation" else "documentation"
|
||||
provider_id = resolve_bound_provider_id(llm_settings, binding_key)
|
||||
if provider_id:
|
||||
request.params["provider_id"] = provider_id
|
||||
if not provider_id:
|
||||
providers = llm_service.get_all_providers()
|
||||
active_provider = next((p for p in providers if p.is_active), None)
|
||||
if active_provider:
|
||||
provider_id = active_provider.id
|
||||
request.params["provider_id"] = provider_id
|
||||
|
||||
if provider_id:
|
||||
db_provider = llm_service.get_provider(provider_id)
|
||||
if not db_provider:
|
||||
raise ValueError(f"LLM Provider {provider_id} not found")
|
||||
if request.plugin_id == "llm_dashboard_validation" and not is_multimodal_model(
|
||||
db_provider.default_model,
|
||||
db_provider.provider_type,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Selected provider model is not multimodal for dashboard validation",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
task = await task_manager.create_task(
|
||||
plugin_id=request.plugin_id,
|
||||
@@ -113,36 +113,36 @@ async def create_task(
|
||||
# @PRE: task_manager must be available.
|
||||
# @POST: Returns a list of tasks.
|
||||
# @RETURN: List[Task] - List of tasks.
|
||||
async def list_tasks(
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
status_filter: Optional[TaskStatus] = Query(None, alias="status"),
|
||||
task_type: Optional[str] = Query(None, description="Task category: llm_validation, backup, migration"),
|
||||
plugin_id: Optional[List[str]] = Query(None, description="Filter by plugin_id (repeatable query param)"),
|
||||
completed_only: bool = Query(False, description="Return only completed tasks (SUCCESS/FAILED)"),
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
_ = Depends(has_permission("tasks", "READ"))
|
||||
):
|
||||
"""
|
||||
Retrieve a list of tasks with pagination and optional status filter.
|
||||
"""
|
||||
with belief_scope("list_tasks"):
|
||||
plugin_filters = list(plugin_id) if plugin_id else []
|
||||
if task_type:
|
||||
if task_type not in TASK_TYPE_PLUGIN_MAP:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unsupported task_type '{task_type}'. Allowed: {', '.join(TASK_TYPE_PLUGIN_MAP.keys())}"
|
||||
)
|
||||
plugin_filters.extend(TASK_TYPE_PLUGIN_MAP[task_type])
|
||||
|
||||
return task_manager.get_tasks(
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
status=status_filter,
|
||||
plugin_ids=plugin_filters or None,
|
||||
completed_only=completed_only
|
||||
)
|
||||
async def list_tasks(
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
status_filter: Optional[TaskStatus] = Query(None, alias="status"),
|
||||
task_type: Optional[str] = Query(None, description="Task category: llm_validation, backup, migration"),
|
||||
plugin_id: Optional[List[str]] = Query(None, description="Filter by plugin_id (repeatable query param)"),
|
||||
completed_only: bool = Query(False, description="Return only completed tasks (SUCCESS/FAILED)"),
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
_ = Depends(has_permission("tasks", "READ"))
|
||||
):
|
||||
"""
|
||||
Retrieve a list of tasks with pagination and optional status filter.
|
||||
"""
|
||||
with belief_scope("list_tasks"):
|
||||
plugin_filters = list(plugin_id) if plugin_id else []
|
||||
if task_type:
|
||||
if task_type not in TASK_TYPE_PLUGIN_MAP:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unsupported task_type '{task_type}'. Allowed: {', '.join(TASK_TYPE_PLUGIN_MAP.keys())}"
|
||||
)
|
||||
plugin_filters.extend(TASK_TYPE_PLUGIN_MAP[task_type])
|
||||
|
||||
return task_manager.get_tasks(
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
status=status_filter,
|
||||
plugin_ids=plugin_filters or None,
|
||||
completed_only=completed_only
|
||||
)
|
||||
# [/DEF:list_tasks:Function]
|
||||
|
||||
@router.get("/{task_id}", response_model=Task)
|
||||
@@ -182,23 +182,6 @@ async def get_task(
|
||||
# @POST: Returns a list of log entries or raises 404.
|
||||
# @RETURN: List[LogEntry] - List of log entries.
|
||||
# @TIER: CRITICAL
|
||||
# @TEST_CONTRACT get_task_logs_api ->
|
||||
# {
|
||||
# required_params: {task_id: str},
|
||||
# optional_params: {level: str, source: str, search: str},
|
||||
# invariants: ["returns 404 for non-existent task", "applies filters correctly"]
|
||||
# }
|
||||
# @TEST_FIXTURE valid_task_logs_request -> {"task_id": "test_1", "level": "INFO"}
|
||||
# @TEST_EDGE task_not_found -> raises 404
|
||||
# @TEST_EDGE invalid_limit -> Query(limit=0) returns 422
|
||||
# @TEST_INVARIANT response_purity -> verifies: [valid_task_logs_request]
|
||||
# @TEST_CONTRACT: TaskLogQueryInput -> List[LogEntry]
|
||||
# @TEST_SCENARIO: existing_task_logs_filtered -> Returns filtered logs by level/source/search with pagination.
|
||||
# @TEST_FIXTURE: valid_task_with_mixed_logs -> backend/tests/fixtures/task_logs/valid_task_with_mixed_logs.json
|
||||
# @TEST_EDGE: missing_task -> Unknown task_id returns 404 Task not found.
|
||||
# @TEST_EDGE: invalid_level_type -> Non-string/invalid level query rejected by validation or yields empty result.
|
||||
# @TEST_EDGE: pagination_bounds -> offset=0 and limit=1000 remain within API bounds and do not overflow.
|
||||
# @TEST_INVARIANT: logs_only_for_existing_task -> VERIFIED_BY: [existing_task_logs_filtered, missing_task]
|
||||
async def get_task_logs(
|
||||
task_id: str,
|
||||
level: Optional[str] = Query(None, description="Filter by log level (DEBUG, INFO, WARNING, ERROR)"),
|
||||
@@ -345,4 +328,4 @@ async def clear_tasks(
|
||||
task_manager.clear_tasks(status)
|
||||
return
|
||||
# [/DEF:clear_tasks:Function]
|
||||
# [/DEF:TasksRouter:Module]
|
||||
# [/DEF:TasksRouter:Module]
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# [DEF:__tests__/test_task_logger:Module]
|
||||
# @RELATION: VERIFIES -> ../task_logger.py
|
||||
# @PURPOSE: Contract testing for TaskLogger
|
||||
# [/DEF:__tests__/test_task_logger:Module]
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
from src.core.task_manager.task_logger import TaskLogger
|
||||
|
||||
# @TEST_FIXTURE: valid_task_logger -> {"task_id": "test_123", "add_log_fn": lambda *args: None, "source": "test_plugin"}
|
||||
@pytest.fixture
|
||||
def mock_add_log():
|
||||
return MagicMock()
|
||||
|
||||
@pytest.fixture
|
||||
def task_logger(mock_add_log):
|
||||
return TaskLogger(task_id="test_123", add_log_fn=mock_add_log, source="test_plugin")
|
||||
|
||||
# @TEST_CONTRACT: TaskLoggerModel -> Invariants
|
||||
def test_task_logger_initialization(task_logger):
|
||||
"""Verify TaskLogger is bound to specific task_id and source."""
|
||||
assert task_logger._task_id == "test_123"
|
||||
assert task_logger._default_source == "test_plugin"
|
||||
|
||||
# @TEST_CONTRACT: invariants -> "All specific log methods (info, error) delegate to _log"
|
||||
def test_log_methods_delegation(task_logger, mock_add_log):
|
||||
"""Verify info, error, warning, debug delegate to internal _log."""
|
||||
task_logger.info("info message", metadata={"k": "v"})
|
||||
mock_add_log.assert_called_with(
|
||||
task_id="test_123",
|
||||
level="INFO",
|
||||
message="info message",
|
||||
source="test_plugin",
|
||||
metadata={"k": "v"}
|
||||
)
|
||||
|
||||
task_logger.error("error message", source="override")
|
||||
mock_add_log.assert_called_with(
|
||||
task_id="test_123",
|
||||
level="ERROR",
|
||||
message="error message",
|
||||
source="override",
|
||||
metadata=None
|
||||
)
|
||||
|
||||
task_logger.warning("warning message")
|
||||
mock_add_log.assert_called_with(
|
||||
task_id="test_123",
|
||||
level="WARNING",
|
||||
message="warning message",
|
||||
source="test_plugin",
|
||||
metadata=None
|
||||
)
|
||||
|
||||
task_logger.debug("debug message")
|
||||
mock_add_log.assert_called_with(
|
||||
task_id="test_123",
|
||||
level="DEBUG",
|
||||
message="debug message",
|
||||
source="test_plugin",
|
||||
metadata=None
|
||||
)
|
||||
|
||||
# @TEST_CONTRACT: invariants -> "with_source creates a new logger with the same task_id"
|
||||
def test_with_source(task_logger):
|
||||
"""Verify with_source returns a new instance with updated default source."""
|
||||
new_logger = task_logger.with_source("new_source")
|
||||
assert isinstance(new_logger, TaskLogger)
|
||||
assert new_logger._task_id == "test_123"
|
||||
assert new_logger._default_source == "new_source"
|
||||
assert new_logger is not task_logger
|
||||
|
||||
# @TEST_EDGE: missing_task_id -> raises TypeError
|
||||
def test_missing_task_id():
|
||||
with pytest.raises(TypeError):
|
||||
TaskLogger(add_log_fn=lambda x: x)
|
||||
|
||||
# @TEST_EDGE: invalid_add_log_fn -> raises TypeError
|
||||
# (Python doesn't strictly enforce this at init, but let's verify it fails on call if not callable)
|
||||
def test_invalid_add_log_fn():
|
||||
logger = TaskLogger(task_id="msg", add_log_fn=None)
|
||||
with pytest.raises(TypeError):
|
||||
logger.info("test")
|
||||
|
||||
# @TEST_INVARIANT: consistent_delegation
|
||||
def test_progress_log(task_logger, mock_add_log):
|
||||
"""Verify progress method correctly formats metadata."""
|
||||
task_logger.progress("Step 1", 45.5)
|
||||
mock_add_log.assert_called_with(
|
||||
task_id="test_123",
|
||||
level="INFO",
|
||||
message="Step 1",
|
||||
source="test_plugin",
|
||||
metadata={"progress": 45.5}
|
||||
)
|
||||
|
||||
# Boundary checks
|
||||
task_logger.progress("Step high", 150)
|
||||
assert mock_add_log.call_args[1]["metadata"]["progress"] == 100
|
||||
|
||||
task_logger.progress("Step low", -10)
|
||||
assert mock_add_log.call_args[1]["metadata"]["progress"] == 0
|
||||
@@ -1,149 +0,0 @@
|
||||
# [DEF:__tests__/test_clean_release:Module]
|
||||
# @RELATION: VERIFIES -> ../clean_release.py
|
||||
# @PURPOSE: Contract testing for Clean Release models
|
||||
# [/DEF:__tests__/test_clean_release:Module]
|
||||
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from pydantic import ValidationError
|
||||
from src.models.clean_release import (
|
||||
ReleaseCandidate,
|
||||
ReleaseCandidateStatus,
|
||||
ProfileType,
|
||||
CleanProfilePolicy,
|
||||
DistributionManifest,
|
||||
ManifestItem,
|
||||
ManifestSummary,
|
||||
ClassificationType,
|
||||
ComplianceCheckRun,
|
||||
CheckFinalStatus,
|
||||
CheckStageResult,
|
||||
CheckStageName,
|
||||
CheckStageStatus,
|
||||
ComplianceReport,
|
||||
ExecutionMode
|
||||
)
|
||||
|
||||
# @TEST_FIXTURE: valid_enterprise_candidate
|
||||
@pytest.fixture
|
||||
def valid_candidate_data():
|
||||
return {
|
||||
"candidate_id": "RC-001",
|
||||
"version": "1.0.0",
|
||||
"profile": ProfileType.ENTERPRISE_CLEAN,
|
||||
"created_at": datetime.now(),
|
||||
"created_by": "admin",
|
||||
"source_snapshot_ref": "v1.0.0-snapshot"
|
||||
}
|
||||
|
||||
def test_release_candidate_valid(valid_candidate_data):
|
||||
rc = ReleaseCandidate(**valid_candidate_data)
|
||||
assert rc.candidate_id == "RC-001"
|
||||
assert rc.status == ReleaseCandidateStatus.DRAFT
|
||||
|
||||
def test_release_candidate_empty_id(valid_candidate_data):
|
||||
valid_candidate_data["candidate_id"] = " "
|
||||
with pytest.raises(ValueError, match="candidate_id must be non-empty"):
|
||||
ReleaseCandidate(**valid_candidate_data)
|
||||
|
||||
# @TEST_FIXTURE: valid_enterprise_policy
|
||||
@pytest.fixture
|
||||
def valid_policy_data():
|
||||
return {
|
||||
"policy_id": "POL-001",
|
||||
"policy_version": "1",
|
||||
"active": True,
|
||||
"prohibited_artifact_categories": ["test-data"],
|
||||
"required_system_categories": ["core"],
|
||||
"internal_source_registry_ref": "REG-1",
|
||||
"effective_from": datetime.now(),
|
||||
"profile": ProfileType.ENTERPRISE_CLEAN
|
||||
}
|
||||
|
||||
# @TEST_INVARIANT: policy_purity
|
||||
def test_enterprise_policy_valid(valid_policy_data):
|
||||
policy = CleanProfilePolicy(**valid_policy_data)
|
||||
assert policy.external_source_forbidden is True
|
||||
|
||||
# @TEST_EDGE: enterprise_policy_missing_prohibited
|
||||
def test_enterprise_policy_missing_prohibited(valid_policy_data):
|
||||
valid_policy_data["prohibited_artifact_categories"] = []
|
||||
with pytest.raises(ValueError, match="enterprise-clean policy requires prohibited_artifact_categories"):
|
||||
CleanProfilePolicy(**valid_policy_data)
|
||||
|
||||
# @TEST_EDGE: enterprise_policy_external_allowed
|
||||
def test_enterprise_policy_external_allowed(valid_policy_data):
|
||||
valid_policy_data["external_source_forbidden"] = False
|
||||
with pytest.raises(ValueError, match="enterprise-clean policy requires external_source_forbidden=true"):
|
||||
CleanProfilePolicy(**valid_policy_data)
|
||||
|
||||
# @TEST_INVARIANT: manifest_consistency
|
||||
# @TEST_EDGE: manifest_count_mismatch
|
||||
def test_manifest_count_mismatch():
|
||||
summary = ManifestSummary(included_count=1, excluded_count=0, prohibited_detected_count=0)
|
||||
item = ManifestItem(path="p", category="c", classification=ClassificationType.ALLOWED, reason="r")
|
||||
|
||||
# Valid
|
||||
DistributionManifest(
|
||||
manifest_id="m1", candidate_id="rc1", policy_id="p1",
|
||||
generated_at=datetime.now(), generated_by="u", items=[item],
|
||||
summary=summary, deterministic_hash="h"
|
||||
)
|
||||
|
||||
# Invalid count
|
||||
summary.included_count = 2
|
||||
with pytest.raises(ValueError, match="manifest summary counts must match items size"):
|
||||
DistributionManifest(
|
||||
manifest_id="m1", candidate_id="rc1", policy_id="p1",
|
||||
generated_at=datetime.now(), generated_by="u", items=[item],
|
||||
summary=summary, deterministic_hash="h"
|
||||
)
|
||||
|
||||
# @TEST_INVARIANT: run_integrity
|
||||
# @TEST_EDGE: compliant_run_stage_fail
|
||||
def test_compliant_run_validation():
|
||||
base_run = {
|
||||
"check_run_id": "run1",
|
||||
"candidate_id": "rc1",
|
||||
"policy_id": "p1",
|
||||
"started_at": datetime.now(),
|
||||
"triggered_by": "u",
|
||||
"execution_mode": ExecutionMode.TUI,
|
||||
"final_status": CheckFinalStatus.COMPLIANT,
|
||||
"checks": [
|
||||
CheckStageResult(stage=CheckStageName.DATA_PURITY, status=CheckStageStatus.PASS),
|
||||
CheckStageResult(stage=CheckStageName.INTERNAL_SOURCES_ONLY, status=CheckStageStatus.PASS),
|
||||
CheckStageResult(stage=CheckStageName.NO_EXTERNAL_ENDPOINTS, status=CheckStageStatus.PASS),
|
||||
CheckStageResult(stage=CheckStageName.MANIFEST_CONSISTENCY, status=CheckStageStatus.PASS),
|
||||
]
|
||||
}
|
||||
# Valid
|
||||
ComplianceCheckRun(**base_run)
|
||||
|
||||
# One stage fails -> cannot be COMPLIANT
|
||||
base_run["checks"][0].status = CheckStageStatus.FAIL
|
||||
with pytest.raises(ValueError, match="compliant run requires PASS on all mandatory stages"):
|
||||
ComplianceCheckRun(**base_run)
|
||||
|
||||
# Missing stage -> cannot be COMPLIANT
|
||||
base_run["checks"] = base_run["checks"][1:]
|
||||
with pytest.raises(ValueError, match="compliant run requires all mandatory stages"):
|
||||
ComplianceCheckRun(**base_run)
|
||||
|
||||
def test_report_validation():
|
||||
# Valid blocked report
|
||||
ComplianceReport(
|
||||
report_id="rep1", check_run_id="run1", candidate_id="rc1",
|
||||
generated_at=datetime.now(), final_status=CheckFinalStatus.BLOCKED,
|
||||
operator_summary="Blocked", structured_payload_ref="ref",
|
||||
violations_count=2, blocking_violations_count=2
|
||||
)
|
||||
|
||||
# BLOCKED with 0 blocking violations
|
||||
with pytest.raises(ValueError, match="blocked report requires blocking violations"):
|
||||
ComplianceReport(
|
||||
report_id="rep1", check_run_id="run1", candidate_id="rc1",
|
||||
generated_at=datetime.now(), final_status=CheckFinalStatus.BLOCKED,
|
||||
operator_summary="Blocked", structured_payload_ref="ref",
|
||||
violations_count=2, blocking_violations_count=0
|
||||
)
|
||||
@@ -5,35 +5,6 @@
|
||||
# @LAYER: Domain
|
||||
# @RELATION: BINDS_TO -> specs/023-clean-repo-enterprise/data-model.md
|
||||
# @INVARIANT: Enterprise-clean policy always forbids external sources.
|
||||
#
|
||||
# @TEST_CONTRACT CleanReleaseModels ->
|
||||
# {
|
||||
# required_fields: {
|
||||
# ReleaseCandidate: [candidate_id, version, profile, source_snapshot_ref],
|
||||
# CleanProfilePolicy: [policy_id, policy_version, internal_source_registry_ref]
|
||||
# },
|
||||
# invariants: [
|
||||
# "enterprise-clean profile enforces external_source_forbidden=True",
|
||||
# "manifest summary counts are consistent with items",
|
||||
# "compliant run requires all mandatory stages to pass"
|
||||
# ]
|
||||
# }
|
||||
# @TEST_FIXTURE valid_enterprise_candidate -> {"candidate_id": "RC-001", "version": "1.0.0", "profile": "enterprise-clean", "source_snapshot_ref": "v1.0.0-snapshot"}
|
||||
# @TEST_FIXTURE valid_enterprise_policy -> {"policy_id": "POL-001", "policy_version": "1", "internal_source_registry_ref": "REG-1", "prohibited_artifact_categories": ["test-data"]}
|
||||
# @TEST_EDGE enterprise_policy_missing_prohibited -> profile=enterprise-clean with empty prohibited_artifact_categories raises ValueError
|
||||
# @TEST_EDGE enterprise_policy_external_allowed -> profile=enterprise-clean with external_source_forbidden=False raises ValueError
|
||||
# @TEST_EDGE manifest_count_mismatch -> included + excluded != len(items) raises ValueError
|
||||
# @TEST_EDGE compliant_run_stage_fail -> COMPLIANT run with failed stage raises ValueError
|
||||
# @TEST_INVARIANT policy_purity -> verifies: [valid_enterprise_policy, enterprise_policy_external_allowed]
|
||||
# @TEST_INVARIANT manifest_consistency -> verifies: [manifest_count_mismatch]
|
||||
# @TEST_INVARIANT run_integrity -> verifies: [compliant_run_stage_fail]
|
||||
# @TEST_CONTRACT: CleanReleaseModelPayload -> ValidatedCleanReleaseModel | ValidationError
|
||||
# @TEST_SCENARIO: valid_enterprise_models -> CRITICAL entities validate and preserve lifecycle/compliance invariants.
|
||||
# @TEST_FIXTURE: clean_release_models_baseline -> backend/tests/fixtures/clean_release/fixtures_clean_release.json
|
||||
# @TEST_EDGE: empty_required_identifiers -> Empty candidate_id/source_snapshot_ref/internal_source_registry_ref fails validation.
|
||||
# @TEST_EDGE: compliant_run_missing_mandatory_stage -> COMPLIANT run without all mandatory PASS stages fails validation.
|
||||
# @TEST_EDGE: blocked_report_without_blocking_violations -> BLOCKED report with zero blocking violations fails validation.
|
||||
# @TEST_INVARIANT: external_source_must_block -> VERIFIED_BY: [valid_enterprise_models, blocked_report_without_blocking_violations]
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"changed_by_name": "Superset Admin",
|
||||
"changed_on": "2026-02-24T19:24:01.850617",
|
||||
"changed_on_delta_humanized": "7 days ago",
|
||||
"changed_on": "2026-02-10T13:39:35.945662",
|
||||
"changed_on_delta_humanized": "16 days ago",
|
||||
"charts": [
|
||||
"TA-0001-001 test_chart"
|
||||
],
|
||||
@@ -19,12 +19,12 @@
|
||||
"id": 1,
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"created_on_delta_humanized": "13 days ago",
|
||||
"created_on_delta_humanized": "16 days ago",
|
||||
"css": null,
|
||||
"dashboard_title": "TA-0001 Test dashboard",
|
||||
"id": 13,
|
||||
"is_managed_externally": false,
|
||||
"json_metadata": "{\"color_scheme_domain\": [], \"shared_label_colors\": [], \"map_label_colors\": {}, \"label_colors\": {}, \"native_filter_configuration\": []}",
|
||||
"json_metadata": "{\"color_scheme_domain\": [], \"shared_label_colors\": [], \"map_label_colors\": {}, \"label_colors\": {}}",
|
||||
"owners": [
|
||||
{
|
||||
"first_name": "Superset",
|
||||
@@ -32,13 +32,13 @@
|
||||
"last_name": "Admin"
|
||||
}
|
||||
],
|
||||
"position_json": "{\"DASHBOARD_VERSION_KEY\": \"v2\", \"ROOT_ID\": {\"children\": [\"GRID_ID\"], \"id\": \"ROOT_ID\", \"type\": \"ROOT\"}, \"GRID_ID\": {\"children\": [\"ROW-N-LH8TG1XX\"], \"id\": \"GRID_ID\", \"parents\": [\"ROOT_ID\"], \"type\": \"GRID\"}, \"HEADER_ID\": {\"id\": \"HEADER_ID\", \"meta\": {\"text\": \"TA-0001 Test dashboard\"}, \"type\": \"HEADER\"}, \"ROW-N-LH8TG1XX\": {\"children\": [\"CHART-1EKC8H7C\"], \"id\": \"ROW-N-LH8TG1XX\", \"meta\": {\"0\": \"ROOT_ID\", \"background\": \"BACKGROUND_TRANSPARENT\"}, \"type\": \"ROW\", \"parents\": [\"ROOT_ID\", \"GRID_ID\"]}, \"CHART-1EKC8H7C\": {\"children\": [], \"id\": \"CHART-1EKC8H7C\", \"meta\": {\"chartId\": 162, \"height\": 50, \"sliceName\": \"TA-0001-001 test_chart\", \"uuid\": \"008cdaa7-21b3-4042-9f55-f15653609ebd\", \"width\": 4}, \"type\": \"CHART\", \"parents\": [\"ROOT_ID\", \"GRID_ID\", \"ROW-N-LH8TG1XX\"]}}",
|
||||
"position_json": null,
|
||||
"published": true,
|
||||
"roles": [],
|
||||
"slug": null,
|
||||
"tags": [],
|
||||
"theme": null,
|
||||
"thumbnail_url": "/api/v1/dashboard/13/thumbnail/97dfd5d8d24f7cf01de45671c9a0699d/",
|
||||
"thumbnail_url": "/api/v1/dashboard/13/thumbnail/3cfc57e6aea7188b139f94fb437a1426/",
|
||||
"url": "/superset/dashboard/13/",
|
||||
"uuid": "124b28d4-d54a-4ade-ade7-2d0473b90686"
|
||||
}
|
||||
@@ -53,15 +53,15 @@
|
||||
"first_name": "Superset",
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"changed_on": "2026-02-18T14:56:04.863722",
|
||||
"changed_on_humanized": "13 days ago",
|
||||
"changed_on": "2026-02-10T13:38:26.175551",
|
||||
"changed_on_humanized": "16 days ago",
|
||||
"column_formats": {},
|
||||
"columns": [
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.382289",
|
||||
"column_name": "has_2fa",
|
||||
"created_on": "2026-02-18T14:56:05.382138",
|
||||
"changed_on": "2026-02-10T13:38:26.158196",
|
||||
"column_name": "color",
|
||||
"created_on": "2026-02-10T13:38:26.158189",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -71,16 +71,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "fe374f2a-9e06-4708-89fd-c3926e3e5faa",
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "4fa810ee-99cc-4d1f-8c0d-0f289c3b01f4",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.545701",
|
||||
"column_name": "is_ultra_restricted",
|
||||
"created_on": "2026-02-18T14:56:05.545465",
|
||||
"changed_on": "2026-02-10T13:38:26.158249",
|
||||
"column_name": "deleted",
|
||||
"created_on": "2026-02-10T13:38:26.158245",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -92,14 +92,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "eac7ecce-d472-4933-9652-d4f2811074fd",
|
||||
"uuid": "ebc07e82-7250-4eef-8d13-ea61561fa52c",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.683578",
|
||||
"column_name": "is_primary_owner",
|
||||
"created_on": "2026-02-18T14:56:05.683257",
|
||||
"changed_on": "2026-02-10T13:38:26.158289",
|
||||
"column_name": "has_2fa",
|
||||
"created_on": "2026-02-10T13:38:26.158285",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -111,14 +111,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "94a15acd-ef98-425b-8f0d-1ce038ca95c5",
|
||||
"uuid": "08e72f4d-3ced-4d9a-9f7d-2f85291ce88b",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.758231",
|
||||
"column_name": "is_app_user",
|
||||
"created_on": "2026-02-18T14:56:05.758142",
|
||||
"changed_on": "2026-02-10T13:38:26.158328",
|
||||
"column_name": "id",
|
||||
"created_on": "2026-02-10T13:38:26.158324",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -128,16 +128,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "d3fcd712-dc96-4bba-a026-aa82022eccf5",
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "fd11955c-0130-4ea1-b3c0-d8b159971789",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.799597",
|
||||
"changed_on": "2026-02-10T13:38:26.158366",
|
||||
"column_name": "is_admin",
|
||||
"created_on": "2026-02-18T14:56:05.799519",
|
||||
"created_on": "2026-02-10T13:38:26.158362",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -149,14 +149,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "5a1c9de5-80f1-4fe8-a91b-e6e530688aae",
|
||||
"uuid": "13a6c8e1-c9f8-4f08-aa62-05bca7be547b",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.819443",
|
||||
"column_name": "is_bot",
|
||||
"created_on": "2026-02-18T14:56:05.819382",
|
||||
"changed_on": "2026-02-10T13:38:26.158404",
|
||||
"column_name": "is_app_user",
|
||||
"created_on": "2026-02-10T13:38:26.158400",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -168,14 +168,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "6c93e5de-e0d7-430c-88d7-87158905d60a",
|
||||
"uuid": "6321ba8a-28d7-4d68-a6b3-5cef6cd681a2",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.827568",
|
||||
"column_name": "is_restricted",
|
||||
"created_on": "2026-02-18T14:56:05.827556",
|
||||
"changed_on": "2026-02-10T13:38:26.158442",
|
||||
"column_name": "is_bot",
|
||||
"created_on": "2026-02-10T13:38:26.158438",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -187,14 +187,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "2e8e6d32-0124-4e3a-a53f-6f200f852439",
|
||||
"uuid": "f3ded50e-b1a2-4a88-b805-781d5923e062",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.835380",
|
||||
"changed_on": "2026-02-10T13:38:26.158480",
|
||||
"column_name": "is_owner",
|
||||
"created_on": "2026-02-18T14:56:05.835366",
|
||||
"created_on": "2026-02-10T13:38:26.158477",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -206,14 +206,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "510d651b-a595-4261-98e4-278af0a06594",
|
||||
"uuid": "8a1408eb-050d-4455-878c-22342df5da3d",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.843802",
|
||||
"column_name": "deleted",
|
||||
"created_on": "2026-02-18T14:56:05.843784",
|
||||
"changed_on": "2026-02-10T13:38:26.158532",
|
||||
"column_name": "is_primary_owner",
|
||||
"created_on": "2026-02-10T13:38:26.158528",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -225,14 +225,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "2653fd2f-c0ce-484e-a5df-d2515b1e822d",
|
||||
"uuid": "054b8c16-82fd-480c-82e0-a0975229673a",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.851074",
|
||||
"column_name": "updated",
|
||||
"created_on": "2026-02-18T14:56:05.851063",
|
||||
"changed_on": "2026-02-10T13:38:26.158583",
|
||||
"column_name": "is_restricted",
|
||||
"created_on": "2026-02-10T13:38:26.158579",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -240,18 +240,18 @@
|
||||
"groupby": true,
|
||||
"id": 781,
|
||||
"is_active": true,
|
||||
"is_dttm": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "DATETIME",
|
||||
"type_generic": 2,
|
||||
"uuid": "1b1f90c8-2567-49b8-9398-e7246396461e",
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "6932c25f-0273-4595-85c1-29422a801ded",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.857578",
|
||||
"column_name": "tz_offset",
|
||||
"created_on": "2026-02-18T14:56:05.857571",
|
||||
"changed_on": "2026-02-10T13:38:26.158621",
|
||||
"column_name": "is_ultra_restricted",
|
||||
"created_on": "2026-02-10T13:38:26.158618",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -261,16 +261,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "LONGINTEGER",
|
||||
"type_generic": 0,
|
||||
"uuid": "e6d19b74-7f5d-447b-8071-951961dc2295",
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "9b14e5f9-3ab4-498e-b1e3-bbf49e9d61fe",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.863101",
|
||||
"column_name": "channel_name",
|
||||
"created_on": "2026-02-18T14:56:05.863094",
|
||||
"changed_on": "2026-02-10T13:38:26.158660",
|
||||
"column_name": "name",
|
||||
"created_on": "2026-02-10T13:38:26.158656",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -282,14 +282,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "e1f34628-ebc1-4e0c-8eea-54c3c9efba1b",
|
||||
"uuid": "ebee8249-0e10-4157-8a8e-96ae107887a3",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.877136",
|
||||
"changed_on": "2026-02-10T13:38:26.158697",
|
||||
"column_name": "real_name",
|
||||
"created_on": "2026-02-18T14:56:05.877083",
|
||||
"created_on": "2026-02-10T13:38:26.158694",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -301,14 +301,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "6cc5ab57-9431-428a-a331-0a5b10e4b074",
|
||||
"uuid": "553517a0-fe05-4ff5-a4eb-e9d2165d6f64",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.893859",
|
||||
"column_name": "tz_label",
|
||||
"created_on": "2026-02-18T14:56:05.893834",
|
||||
"changed_on": "2026-02-10T13:38:26.158735",
|
||||
"column_name": "team_id",
|
||||
"created_on": "2026-02-10T13:38:26.158731",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -320,14 +320,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "8e6dbd8e-b880-4517-a5f6-64e429bd1bea",
|
||||
"uuid": "6c207fac-424d-465c-b80a-306b42b55ce8",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.902363",
|
||||
"column_name": "team_id",
|
||||
"created_on": "2026-02-18T14:56:05.902352",
|
||||
"changed_on": "2026-02-10T13:38:26.158773",
|
||||
"column_name": "tz",
|
||||
"created_on": "2026-02-10T13:38:26.158769",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -339,14 +339,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "ba8e225d-221b-4275-aadb-e79557756f89",
|
||||
"uuid": "6efcc042-0b78-4362-9373-2f684077d574",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.910169",
|
||||
"column_name": "name",
|
||||
"created_on": "2026-02-18T14:56:05.910151",
|
||||
"changed_on": "2026-02-10T13:38:26.158824",
|
||||
"column_name": "tz_label",
|
||||
"created_on": "2026-02-10T13:38:26.158820",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -358,14 +358,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "02a7a026-d9f3-49e9-9586-534ebccdd867",
|
||||
"uuid": "c6a6ac40-5c60-472d-a878-4b65b8460ccc",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.915366",
|
||||
"column_name": "color",
|
||||
"created_on": "2026-02-18T14:56:05.915357",
|
||||
"changed_on": "2026-02-10T13:38:26.158861",
|
||||
"column_name": "tz_offset",
|
||||
"created_on": "2026-02-10T13:38:26.158857",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -375,16 +375,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "0702fcdf-2d03-45db-8496-697d47b300d6",
|
||||
"type": "LONGINTEGER",
|
||||
"type_generic": 0,
|
||||
"uuid": "cf6da93a-bba9-47df-9154-6cfd0c9922fc",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.919466",
|
||||
"column_name": "id",
|
||||
"created_on": "2026-02-18T14:56:05.919460",
|
||||
"changed_on": "2026-02-10T13:38:26.158913",
|
||||
"column_name": "updated",
|
||||
"created_on": "2026-02-10T13:38:26.158909",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -392,18 +392,18 @@
|
||||
"groupby": true,
|
||||
"id": 789,
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"is_dttm": true,
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "a4b58528-fcbf-45e9-af39-fe9d737ba380",
|
||||
"type": "DATETIME",
|
||||
"type_generic": 2,
|
||||
"uuid": "2aa0a72a-5602-4799-b5ab-f22000108d62",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-18T14:56:05.932553",
|
||||
"column_name": "tz",
|
||||
"created_on": "2026-02-18T14:56:05.932530",
|
||||
"changed_on": "2026-02-10T13:38:26.158967",
|
||||
"column_name": "channel_name",
|
||||
"created_on": "2026-02-10T13:38:26.158963",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -415,7 +415,7 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "bc872357-1920-42f3-aeda-b596122bcdb8",
|
||||
"uuid": "a84bd658-c83c-4e7f-9e1b-192595092d9b",
|
||||
"verbose_name": null
|
||||
}
|
||||
],
|
||||
@@ -423,8 +423,8 @@
|
||||
"first_name": "Superset",
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"created_on": "2026-02-18T14:56:04.317950",
|
||||
"created_on_humanized": "13 days ago",
|
||||
"created_on": "2026-02-10T13:38:26.050436",
|
||||
"created_on_humanized": "16 days ago",
|
||||
"database": {
|
||||
"allow_multi_catalog": false,
|
||||
"backend": "postgresql",
|
||||
@@ -452,8 +452,8 @@
|
||||
"main_dttm_col": "updated",
|
||||
"metrics": [
|
||||
{
|
||||
"changed_on": "2026-02-18T14:56:05.085244",
|
||||
"created_on": "2026-02-18T14:56:05.085166",
|
||||
"changed_on": "2026-02-10T13:38:26.182269",
|
||||
"created_on": "2026-02-10T13:38:26.182264",
|
||||
"currency": null,
|
||||
"d3format": null,
|
||||
"description": null,
|
||||
@@ -462,7 +462,7 @@
|
||||
"id": 33,
|
||||
"metric_name": "count",
|
||||
"metric_type": "count",
|
||||
"uuid": "10c8b8cf-b697-4512-9e9e-2996721f829e",
|
||||
"uuid": "7510f8ca-05ee-4a37-bec1-4a5d7bf2ac50",
|
||||
"verbose_name": "COUNT(*)",
|
||||
"warning_text": null
|
||||
}
|
||||
|
||||
@@ -100,10 +100,7 @@ def test_dashboard_dataset_relations():
|
||||
logger.info(f" Found {len(dashboards)} dashboards using this dataset:")
|
||||
|
||||
for dash in dashboards:
|
||||
if isinstance(dash, dict):
|
||||
logger.info(f" - Dashboard ID {dash.get('id')}: {dash.get('dashboard_title', dash.get('title', 'Unknown'))}")
|
||||
else:
|
||||
logger.info(f" - Dashboard: {dash}")
|
||||
logger.info(f" - Dashboard ID {dash.get('id')}: {dash.get('dashboard_title', dash.get('title', 'Unknown'))}")
|
||||
elif 'result' in related_objects:
|
||||
# Some Superset versions use 'result' wrapper
|
||||
result = related_objects['result']
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestEncryptionManager:
|
||||
# Re-implement the same logic as EncryptionManager to avoid import issues
|
||||
# with the llm_provider module's relative imports
|
||||
import os
|
||||
key = os.getenv("ENCRYPTION_KEY", "REMOVED_HISTORICAL_SECRET_DO_NOT_USE").encode()
|
||||
key = os.getenv("ENCRYPTION_KEY", "ZcytYzi0iHIl4Ttr-GdAEk117aGRogkGvN3wiTxrPpE=").encode()
|
||||
fernet = Fernet(key)
|
||||
|
||||
class EncryptionManager:
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# [DEF:__tests__/test_llm_provider:Module]
|
||||
# @RELATION: VERIFIES -> ../llm_provider.py
|
||||
# @PURPOSE: Contract testing for LLMProviderService and EncryptionManager
|
||||
# [/DEF:__tests__/test_llm_provider:Module]
|
||||
|
||||
import pytest
|
||||
import os
|
||||
from unittest.mock import MagicMock
|
||||
from sqlalchemy.orm import Session
|
||||
from src.services.llm_provider import EncryptionManager, LLMProviderService
|
||||
from src.models.llm import LLMProvider
|
||||
from src.plugins.llm_analysis.models import LLMProviderConfig, ProviderType
|
||||
|
||||
# @TEST_CONTRACT: EncryptionManagerModel -> Invariants
|
||||
# @TEST_INVARIANT: symmetric_encryption
|
||||
def test_encryption_cycle():
|
||||
"""Verify encrypted data can be decrypted back to original string."""
|
||||
manager = EncryptionManager()
|
||||
original = "secret_api_key_123"
|
||||
encrypted = manager.encrypt(original)
|
||||
assert encrypted != original
|
||||
assert manager.decrypt(encrypted) == original
|
||||
|
||||
# @TEST_EDGE: empty_string_encryption
|
||||
def test_empty_string_encryption():
|
||||
manager = EncryptionManager()
|
||||
original = ""
|
||||
encrypted = manager.encrypt(original)
|
||||
assert manager.decrypt(encrypted) == ""
|
||||
|
||||
# @TEST_EDGE: decrypt_invalid_data
|
||||
def test_decrypt_invalid_data():
|
||||
manager = EncryptionManager()
|
||||
with pytest.raises(Exception):
|
||||
manager.decrypt("not-encrypted-string")
|
||||
|
||||
# @TEST_FIXTURE: mock_db_session
|
||||
@pytest.fixture
|
||||
def mock_db():
|
||||
return MagicMock(spec=Session)
|
||||
|
||||
@pytest.fixture
|
||||
def service(mock_db):
|
||||
return LLMProviderService(db=mock_db)
|
||||
|
||||
def test_get_all_providers(service, mock_db):
|
||||
service.get_all_providers()
|
||||
mock_db.query.assert_called()
|
||||
mock_db.query().all.assert_called()
|
||||
|
||||
def test_create_provider(service, mock_db):
|
||||
config = LLMProviderConfig(
|
||||
provider_type=ProviderType.OPENAI,
|
||||
name="Test OpenAI",
|
||||
base_url="https://api.openai.com",
|
||||
api_key="sk-test",
|
||||
default_model="gpt-4",
|
||||
is_active=True
|
||||
)
|
||||
|
||||
provider = service.create_provider(config)
|
||||
|
||||
mock_db.add.assert_called()
|
||||
mock_db.commit.assert_called()
|
||||
# Verify API key was encrypted
|
||||
assert provider.api_key != "sk-test"
|
||||
# Decrypt to verify it matches
|
||||
assert EncryptionManager().decrypt(provider.api_key) == "sk-test"
|
||||
|
||||
def test_get_decrypted_api_key(service, mock_db):
|
||||
# Setup mock provider
|
||||
encrypted_key = EncryptionManager().encrypt("secret-value")
|
||||
mock_provider = LLMProvider(id="p1", api_key=encrypted_key)
|
||||
mock_db.query().filter().first.return_value = mock_provider
|
||||
|
||||
key = service.get_decrypted_api_key("p1")
|
||||
assert key == "secret-value"
|
||||
|
||||
def test_get_decrypted_api_key_not_found(service, mock_db):
|
||||
mock_db.query().filter().first.return_value = None
|
||||
assert service.get_decrypted_api_key("missing") is None
|
||||
@@ -1,24 +0,0 @@
|
||||
# [DEF:backend.tests.services.clean_release.test_audit_service:Module]
|
||||
# @TIER: STANDARD
|
||||
# @SEMANTICS: tests, clean-release, audit, logging
|
||||
# @PURPOSE: Validate audit hooks emit expected log patterns for clean release lifecycle.
|
||||
# @LAYER: Infra
|
||||
# @RELATION: TESTS -> backend.src.services.clean_release.audit_service
|
||||
|
||||
from unittest.mock import patch
|
||||
from src.services.clean_release.audit_service import audit_preparation, audit_check_run, audit_report
|
||||
|
||||
@patch("src.services.clean_release.audit_service.logger")
|
||||
def test_audit_preparation(mock_logger):
|
||||
audit_preparation("cand-1", "PREPARED")
|
||||
mock_logger.info.assert_called_with("[REASON] clean-release preparation candidate=cand-1 status=PREPARED")
|
||||
|
||||
@patch("src.services.clean_release.audit_service.logger")
|
||||
def test_audit_check_run(mock_logger):
|
||||
audit_check_run("check-1", "COMPLIANT")
|
||||
mock_logger.info.assert_called_with("[REFLECT] clean-release check_run=check-1 final_status=COMPLIANT")
|
||||
|
||||
@patch("src.services.clean_release.audit_service.logger")
|
||||
def test_audit_report(mock_logger):
|
||||
audit_report("rep-1", "cand-1")
|
||||
mock_logger.info.assert_called_with("[EXPLORE] clean-release report_id=rep-1 candidate=cand-1")
|
||||
@@ -48,33 +48,6 @@ def test_orchestrator_stage_failure_blocks_release():
|
||||
# [/DEF:test_orchestrator_stage_failure_blocks_release:Function]
|
||||
|
||||
|
||||
# [DEF:test_orchestrator_compliant_candidate:Function]
|
||||
# @PURPOSE: Verify happy path where all mandatory stages pass yields COMPLIANT.
|
||||
def test_orchestrator_compliant_candidate():
|
||||
repository = CleanReleaseRepository()
|
||||
orchestrator = CleanComplianceOrchestrator(repository)
|
||||
|
||||
run = orchestrator.start_check_run(
|
||||
candidate_id="2026.03.03-rc1",
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
triggered_by="tester",
|
||||
execution_mode="tui",
|
||||
)
|
||||
run = orchestrator.execute_stages(
|
||||
run,
|
||||
forced_results=[
|
||||
CheckStageResult(stage=CheckStageName.DATA_PURITY, status=CheckStageStatus.PASS, details="ok"),
|
||||
CheckStageResult(stage=CheckStageName.INTERNAL_SOURCES_ONLY, status=CheckStageStatus.PASS, details="ok"),
|
||||
CheckStageResult(stage=CheckStageName.NO_EXTERNAL_ENDPOINTS, status=CheckStageStatus.PASS, details="ok"),
|
||||
CheckStageResult(stage=CheckStageName.MANIFEST_CONSISTENCY, status=CheckStageStatus.PASS, details="ok"),
|
||||
],
|
||||
)
|
||||
run = orchestrator.finalize_run(run)
|
||||
|
||||
assert run.final_status == CheckFinalStatus.COMPLIANT
|
||||
# [/DEF:test_orchestrator_compliant_candidate:Function]
|
||||
|
||||
|
||||
# [DEF:test_orchestrator_missing_stage_result:Function]
|
||||
# @PURPOSE: Verify incomplete mandatory stage set cannot end as COMPLIANT and results in FAILED.
|
||||
def test_orchestrator_missing_stage_result():
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# [DEF:__tests__/test_policy_engine:Module]
|
||||
# @RELATION: VERIFIES -> ../policy_engine.py
|
||||
# @PURPOSE: Contract testing for CleanPolicyEngine
|
||||
# [/DEF:__tests__/test_policy_engine:Module]
|
||||
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from src.models.clean_release import (
|
||||
CleanProfilePolicy,
|
||||
ResourceSourceRegistry,
|
||||
ResourceSourceEntry,
|
||||
ProfileType,
|
||||
RegistryStatus
|
||||
)
|
||||
from src.services.clean_release.policy_engine import CleanPolicyEngine
|
||||
|
||||
# @TEST_FIXTURE: policy_enterprise_clean
|
||||
@pytest.fixture
|
||||
def enterprise_clean_setup():
|
||||
policy = CleanProfilePolicy(
|
||||
policy_id="POL-1",
|
||||
policy_version="1",
|
||||
active=True,
|
||||
prohibited_artifact_categories=["demo", "test"],
|
||||
required_system_categories=["core"],
|
||||
internal_source_registry_ref="REG-1",
|
||||
effective_from=datetime.now(),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN
|
||||
)
|
||||
registry = ResourceSourceRegistry(
|
||||
registry_id="REG-1",
|
||||
name="Internal Registry",
|
||||
entries=[
|
||||
ResourceSourceEntry(source_id="S1", host="internal.com", protocol="https", purpose="p1", enabled=True)
|
||||
],
|
||||
updated_at=datetime.now(),
|
||||
updated_by="admin",
|
||||
status=RegistryStatus.ACTIVE
|
||||
)
|
||||
return policy, registry
|
||||
|
||||
# @TEST_SCENARIO: policy_valid
|
||||
def test_policy_valid(enterprise_clean_setup):
|
||||
policy, registry = enterprise_clean_setup
|
||||
engine = CleanPolicyEngine(policy, registry)
|
||||
result = engine.validate_policy()
|
||||
assert result.ok is True
|
||||
assert not result.blocking_reasons
|
||||
|
||||
# @TEST_EDGE: missing_registry_ref
|
||||
def test_missing_registry_ref(enterprise_clean_setup):
|
||||
policy, registry = enterprise_clean_setup
|
||||
policy.internal_source_registry_ref = " "
|
||||
engine = CleanPolicyEngine(policy, registry)
|
||||
result = engine.validate_policy()
|
||||
assert result.ok is False
|
||||
assert "Policy missing internal_source_registry_ref" in result.blocking_reasons
|
||||
|
||||
# @TEST_EDGE: conflicting_registry
|
||||
def test_conflicting_registry(enterprise_clean_setup):
|
||||
policy, registry = enterprise_clean_setup
|
||||
registry.registry_id = "WRONG-REG"
|
||||
engine = CleanPolicyEngine(policy, registry)
|
||||
result = engine.validate_policy()
|
||||
assert result.ok is False
|
||||
assert "Policy registry ref does not match provided registry" in result.blocking_reasons
|
||||
|
||||
# @TEST_INVARIANT: deterministic_classification
|
||||
def test_classify_artifact(enterprise_clean_setup):
|
||||
policy, registry = enterprise_clean_setup
|
||||
engine = CleanPolicyEngine(policy, registry)
|
||||
|
||||
# Required
|
||||
assert engine.classify_artifact({"category": "core", "path": "p1"}) == "required-system"
|
||||
# Prohibited
|
||||
assert engine.classify_artifact({"category": "demo", "path": "p2"}) == "excluded-prohibited"
|
||||
# Allowed
|
||||
assert engine.classify_artifact({"category": "others", "path": "p3"}) == "allowed"
|
||||
|
||||
# @TEST_EDGE: external_endpoint
|
||||
def test_validate_resource_source(enterprise_clean_setup):
|
||||
policy, registry = enterprise_clean_setup
|
||||
engine = CleanPolicyEngine(policy, registry)
|
||||
|
||||
# Internal (OK)
|
||||
res_ok = engine.validate_resource_source("internal.com")
|
||||
assert res_ok.ok is True
|
||||
|
||||
# External (Blocked)
|
||||
res_fail = engine.validate_resource_source("external.evil")
|
||||
assert res_fail.ok is False
|
||||
assert res_fail.violation["category"] == "external-source"
|
||||
assert res_fail.violation["blocked_release"] is True
|
||||
|
||||
def test_evaluate_candidate(enterprise_clean_setup):
|
||||
policy, registry = enterprise_clean_setup
|
||||
engine = CleanPolicyEngine(policy, registry)
|
||||
|
||||
artifacts = [
|
||||
{"path": "core.js", "category": "core"},
|
||||
{"path": "demo.sql", "category": "demo"}
|
||||
]
|
||||
sources = ["internal.com", "google.com"]
|
||||
|
||||
classified, violations = engine.evaluate_candidate(artifacts, sources)
|
||||
|
||||
assert len(classified) == 2
|
||||
assert classified[0]["classification"] == "required-system"
|
||||
assert classified[1]["classification"] == "excluded-prohibited"
|
||||
|
||||
# 1 violation for demo artifact + 1 for google.com source
|
||||
assert len(violations) == 2
|
||||
assert violations[0]["category"] == "data-purity"
|
||||
assert violations[1]["category"] == "external-source"
|
||||
@@ -1,127 +0,0 @@
|
||||
# [DEF:backend.tests.services.clean_release.test_preparation_service:Module]
|
||||
# @TIER: STANDARD
|
||||
# @SEMANTICS: tests, clean-release, preparation, flow
|
||||
# @PURPOSE: Validate release candidate preparation flow, including policy evaluation and manifest persisting.
|
||||
# @LAYER: Domain
|
||||
# @RELATION: TESTS -> backend.src.services.clean_release.preparation_service
|
||||
# @INVARIANT: Candidate preparation always persists manifest and candidate status deterministically.
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from src.models.clean_release import (
|
||||
CleanProfilePolicy,
|
||||
ResourceSourceRegistry,
|
||||
ResourceSourceEntry,
|
||||
ReleaseCandidate,
|
||||
ReleaseCandidateStatus,
|
||||
ProfileType,
|
||||
DistributionManifest
|
||||
)
|
||||
from src.services.clean_release.preparation_service import prepare_candidate
|
||||
|
||||
def _mock_policy() -> CleanProfilePolicy:
|
||||
return CleanProfilePolicy(
|
||||
policy_id="pol-1",
|
||||
policy_version="1.0.0",
|
||||
active=True,
|
||||
prohibited_artifact_categories=["prohibited"],
|
||||
required_system_categories=["system"],
|
||||
external_source_forbidden=True,
|
||||
internal_source_registry_ref="reg-1",
|
||||
effective_from=datetime.now(timezone.utc),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
)
|
||||
|
||||
def _mock_registry() -> ResourceSourceRegistry:
|
||||
return ResourceSourceRegistry(
|
||||
registry_id="reg-1",
|
||||
name="Reg",
|
||||
entries=[ResourceSourceEntry(source_id="s1", host="nexus.internal", protocol="https", purpose="pkg", enabled=True)],
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
updated_by="tester"
|
||||
)
|
||||
|
||||
def _mock_candidate(candidate_id: str) -> ReleaseCandidate:
|
||||
return ReleaseCandidate(
|
||||
candidate_id=candidate_id,
|
||||
version="1.0.0",
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
status=ReleaseCandidateStatus.DRAFT,
|
||||
created_by="tester",
|
||||
source_snapshot_ref="v1.0.0-snapshot"
|
||||
)
|
||||
|
||||
def test_prepare_candidate_success():
|
||||
# Setup
|
||||
repository = MagicMock()
|
||||
candidate_id = "cand-1"
|
||||
candidate = _mock_candidate(candidate_id)
|
||||
repository.get_candidate.return_value = candidate
|
||||
repository.get_active_policy.return_value = _mock_policy()
|
||||
repository.get_registry.return_value = _mock_registry()
|
||||
|
||||
artifacts = [{"path": "file1.txt", "category": "system"}]
|
||||
sources = ["nexus.internal"]
|
||||
|
||||
# Execute
|
||||
with patch("src.services.clean_release.preparation_service.CleanPolicyEngine") as MockEngine:
|
||||
mock_engine_instance = MockEngine.return_value
|
||||
mock_engine_instance.validate_policy.return_value.ok = True
|
||||
mock_engine_instance.evaluate_candidate.return_value = (
|
||||
[{"path": "file1.txt", "category": "system", "classification": "required-system", "reason": "system-core"}],
|
||||
[]
|
||||
)
|
||||
|
||||
result = prepare_candidate(repository, candidate_id, artifacts, sources, "operator-1")
|
||||
|
||||
# Verify
|
||||
assert result["status"] == ReleaseCandidateStatus.PREPARED.value
|
||||
assert candidate.status == ReleaseCandidateStatus.PREPARED
|
||||
repository.save_manifest.assert_called_once()
|
||||
repository.save_candidate.assert_called_with(candidate)
|
||||
|
||||
def test_prepare_candidate_with_violations():
|
||||
# Setup
|
||||
repository = MagicMock()
|
||||
candidate_id = "cand-1"
|
||||
candidate = _mock_candidate(candidate_id)
|
||||
repository.get_candidate.return_value = candidate
|
||||
repository.get_active_policy.return_value = _mock_policy()
|
||||
repository.get_registry.return_value = _mock_registry()
|
||||
|
||||
artifacts = [{"path": "bad.txt", "category": "prohibited"}]
|
||||
sources = []
|
||||
|
||||
# Execute
|
||||
with patch("src.services.clean_release.preparation_service.CleanPolicyEngine") as MockEngine:
|
||||
mock_engine_instance = MockEngine.return_value
|
||||
mock_engine_instance.validate_policy.return_value.ok = True
|
||||
mock_engine_instance.evaluate_candidate.return_value = (
|
||||
[{"path": "bad.txt", "category": "prohibited", "classification": "excluded-prohibited", "reason": "test-data"}],
|
||||
[{"category": "data-purity", "blocked_release": True}]
|
||||
)
|
||||
|
||||
result = prepare_candidate(repository, candidate_id, artifacts, sources, "operator-1")
|
||||
|
||||
# Verify
|
||||
assert result["status"] == ReleaseCandidateStatus.BLOCKED.value
|
||||
assert candidate.status == ReleaseCandidateStatus.BLOCKED
|
||||
assert len(result["violations"]) == 1
|
||||
|
||||
def test_prepare_candidate_not_found():
|
||||
repository = MagicMock()
|
||||
repository.get_candidate.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="Candidate not found"):
|
||||
prepare_candidate(repository, "non-existent", [], [], "op")
|
||||
|
||||
def test_prepare_candidate_no_active_policy():
|
||||
repository = MagicMock()
|
||||
repository.get_candidate.return_value = _mock_candidate("cand-1")
|
||||
repository.get_active_policy.return_value = None
|
||||
|
||||
with pytest.raises(ValueError, match="Active clean policy not found"):
|
||||
prepare_candidate(repository, "cand-1", [], [], "op")
|
||||
@@ -66,26 +66,6 @@ def test_report_builder_blocked_requires_blocking_violations():
|
||||
# [/DEF:test_report_builder_blocked_requires_blocking_violations:Function]
|
||||
|
||||
|
||||
# [DEF:test_report_builder_blocked_with_two_violations:Function]
|
||||
# @PURPOSE: Verify report builder generates conformant payload for a BLOCKED run with violations.
|
||||
def test_report_builder_blocked_with_two_violations():
|
||||
builder = ComplianceReportBuilder(CleanReleaseRepository())
|
||||
run = _terminal_run(CheckFinalStatus.BLOCKED)
|
||||
v1 = _blocking_violation()
|
||||
v2 = _blocking_violation()
|
||||
v2.violation_id = "viol-2"
|
||||
v2.category = ViolationCategory.DATA_PURITY
|
||||
|
||||
report = builder.build_report_payload(run, [v1, v2])
|
||||
|
||||
assert report.check_run_id == run.check_run_id
|
||||
assert report.candidate_id == run.candidate_id
|
||||
assert report.final_status == CheckFinalStatus.BLOCKED
|
||||
assert report.violations_count == 2
|
||||
assert report.blocking_violations_count == 2
|
||||
# [/DEF:test_report_builder_blocked_with_two_violations:Function]
|
||||
|
||||
|
||||
# [DEF:test_report_builder_counter_consistency:Function]
|
||||
# @PURPOSE: Verify violations counters remain consistent for blocking payload.
|
||||
def test_report_builder_counter_consistency():
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# [DEF:backend.tests.services.clean_release.test_stages:Module]
|
||||
# @TIER: STANDARD
|
||||
# @SEMANTICS: tests, clean-release, compliance, stages
|
||||
# @PURPOSE: Validate final status derivation logic from stage results.
|
||||
# @LAYER: Domain
|
||||
# @RELATION: TESTS -> backend.src.services.clean_release.stages
|
||||
|
||||
from src.models.clean_release import CheckFinalStatus, CheckStageName, CheckStageResult, CheckStageStatus
|
||||
from src.services.clean_release.stages import derive_final_status, MANDATORY_STAGE_ORDER
|
||||
|
||||
def test_derive_final_status_compliant():
|
||||
results = [CheckStageResult(stage=s, status=CheckStageStatus.PASS, details="ok") for s in MANDATORY_STAGE_ORDER]
|
||||
assert derive_final_status(results) == CheckFinalStatus.COMPLIANT
|
||||
|
||||
def test_derive_final_status_blocked():
|
||||
results = [CheckStageResult(stage=s, status=CheckStageStatus.PASS, details="ok") for s in MANDATORY_STAGE_ORDER]
|
||||
results[1].status = CheckStageStatus.FAIL
|
||||
assert derive_final_status(results) == CheckFinalStatus.BLOCKED
|
||||
|
||||
def test_derive_final_status_failed_missing():
|
||||
results = [CheckStageResult(stage=MANDATORY_STAGE_ORDER[0], status=CheckStageStatus.PASS, details="ok")]
|
||||
assert derive_final_status(results) == CheckFinalStatus.FAILED
|
||||
|
||||
def test_derive_final_status_failed_skipped():
|
||||
results = [CheckStageResult(stage=s, status=CheckStageStatus.PASS, details="ok") for s in MANDATORY_STAGE_ORDER]
|
||||
results[2].status = CheckStageStatus.SKIPPED
|
||||
assert derive_final_status(results) == CheckFinalStatus.FAILED
|
||||
@@ -46,21 +46,10 @@ class GitService:
|
||||
backend_root = Path(__file__).parents[2]
|
||||
self.legacy_base_path = str((backend_root / "git_repos").resolve())
|
||||
self.base_path = self._resolve_base_path(base_path)
|
||||
self._ensure_base_path_exists()
|
||||
if not os.path.exists(self.base_path):
|
||||
os.makedirs(self.base_path)
|
||||
# [/DEF:__init__:Function]
|
||||
|
||||
# [DEF:_ensure_base_path_exists:Function]
|
||||
# @PURPOSE: Ensure the repositories root directory exists and is a directory.
|
||||
# @PRE: self.base_path is resolved to filesystem path.
|
||||
# @POST: self.base_path exists as directory or raises ValueError.
|
||||
# @RETURN: None
|
||||
def _ensure_base_path_exists(self) -> None:
|
||||
base = Path(self.base_path)
|
||||
if base.exists() and not base.is_dir():
|
||||
raise ValueError(f"Git repositories base path is not a directory: {self.base_path}")
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
# [/DEF:_ensure_base_path_exists:Function]
|
||||
|
||||
# [DEF:_resolve_base_path:Function]
|
||||
# @PURPOSE: Resolve base repository directory from explicit argument or global storage settings.
|
||||
# @PRE: base_path is a string path.
|
||||
@@ -178,7 +167,6 @@ class GitService:
|
||||
with belief_scope("GitService._get_repo_path"):
|
||||
if dashboard_id is None:
|
||||
raise ValueError("dashboard_id cannot be None")
|
||||
self._ensure_base_path_exists()
|
||||
fallback_key = repo_key if repo_key is not None else str(dashboard_id)
|
||||
normalized_key = self._normalize_repo_key(fallback_key)
|
||||
target_path = os.path.join(self.base_path, normalized_key)
|
||||
@@ -226,7 +214,6 @@ class GitService:
|
||||
# @RETURN: Repo - GitPython Repo object.
|
||||
def init_repo(self, dashboard_id: int, remote_url: str, pat: str, repo_key: Optional[str] = None) -> Repo:
|
||||
with belief_scope("GitService.init_repo"):
|
||||
self._ensure_base_path_exists()
|
||||
repo_path = self._get_repo_path(dashboard_id, repo_key=repo_key or str(dashboard_id))
|
||||
Path(repo_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class EncryptionManager:
|
||||
# @PRE: ENCRYPTION_KEY env var must be set or use default dev key.
|
||||
# @POST: Fernet instance ready for encryption/decryption.
|
||||
def __init__(self):
|
||||
self.key = os.getenv("ENCRYPTION_KEY", "REMOVED_HISTORICAL_SECRET_DO_NOT_USE").encode()
|
||||
self.key = os.getenv("ENCRYPTION_KEY", "ZcytYzi0iHIl4Ttr-GdAEk117aGRogkGvN3wiTxrPpE=").encode()
|
||||
self.fernet = Fernet(self.key)
|
||||
# [/DEF:EncryptionManager.__init__:Function]
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# [DEF:__tests__/test_report_type_profiles:Module]
|
||||
# @RELATION: VERIFIES -> ../type_profiles.py
|
||||
# @PURPOSE: Contract testing for task type profiles and resolution logic.
|
||||
# [/DEF:__tests__/test_report_type_profiles:Module]
|
||||
|
||||
import pytest
|
||||
from src.models.report import TaskType
|
||||
from src.services.reports.type_profiles import resolve_task_type, get_type_profile
|
||||
|
||||
# @TEST_CONTRACT: ResolveTaskType -> Invariants
|
||||
# @TEST_INVARIANT: fallback_to_unknown
|
||||
def test_resolve_task_type_fallbacks():
|
||||
"""Verify missing/unmapped plugin_id returns TaskType.UNKNOWN."""
|
||||
assert resolve_task_type(None) == TaskType.UNKNOWN
|
||||
assert resolve_task_type("") == TaskType.UNKNOWN
|
||||
assert resolve_task_type(" ") == TaskType.UNKNOWN
|
||||
assert resolve_task_type("invalid_plugin") == TaskType.UNKNOWN
|
||||
|
||||
# @TEST_FIXTURE: valid_plugin
|
||||
def test_resolve_task_type_valid():
|
||||
"""Verify known plugin IDs map correctly."""
|
||||
assert resolve_task_type("superset-migration") == TaskType.MIGRATION
|
||||
assert resolve_task_type("llm_dashboard_validation") == TaskType.LLM_VERIFICATION
|
||||
assert resolve_task_type("superset-backup") == TaskType.BACKUP
|
||||
assert resolve_task_type("documentation") == TaskType.DOCUMENTATION
|
||||
|
||||
# @TEST_FIXTURE: valid_profile
|
||||
def test_get_type_profile_valid():
|
||||
"""Verify known task types return correct profile metadata."""
|
||||
profile = get_type_profile(TaskType.MIGRATION)
|
||||
assert profile["display_label"] == "Migration"
|
||||
assert profile["visual_variant"] == "migration"
|
||||
assert profile["fallback"] is False
|
||||
|
||||
# @TEST_INVARIANT: always_returns_dict
|
||||
# @TEST_EDGE: missing_profile
|
||||
def test_get_type_profile_fallback():
|
||||
"""Verify unknown task type returns fallback profile."""
|
||||
# Assuming TaskType.UNKNOWN or any non-mapped value
|
||||
profile = get_type_profile(TaskType.UNKNOWN)
|
||||
assert profile["display_label"] == "Other / Unknown"
|
||||
assert profile["fallback"] is True
|
||||
|
||||
# Passing a value that might not be in the dict explicitly
|
||||
profile_fallback = get_type_profile("non-enum-value")
|
||||
assert profile_fallback["display_label"] == "Other / Unknown"
|
||||
assert profile_fallback["fallback"] is True
|
||||
BIN
backend/tasks.db
Normal file
BIN
backend/tasks.db
Normal file
Binary file not shown.
76
backend/test_auth_debug.py
Normal file
76
backend/test_auth_debug.py
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Debug script to test Superset API authentication"""
|
||||
|
||||
from pprint import pprint
|
||||
from src.core.superset_client import SupersetClient
|
||||
from src.core.config_manager import ConfigManager
|
||||
|
||||
|
||||
def main():
|
||||
print("Debugging Superset API authentication...")
|
||||
|
||||
config = ConfigManager()
|
||||
|
||||
# Select first available environment
|
||||
environments = config.get_environments()
|
||||
|
||||
if not environments:
|
||||
print("No environments configured")
|
||||
return
|
||||
|
||||
env = environments[0]
|
||||
print(f"\nTesting environment: {env.name}")
|
||||
print(f"URL: {env.url}")
|
||||
|
||||
try:
|
||||
# Test API client authentication
|
||||
print("\n--- Testing API Authentication ---")
|
||||
client = SupersetClient(env)
|
||||
tokens = client.authenticate()
|
||||
|
||||
print("\nAPI Auth Success!")
|
||||
print(f"Access Token: {tokens.get('access_token', 'N/A')}")
|
||||
print(f"CSRF Token: {tokens.get('csrf_token', 'N/A')}")
|
||||
|
||||
# Debug cookies from session
|
||||
print("\n--- Session Cookies ---")
|
||||
for cookie in client.network.session.cookies:
|
||||
print(f"{cookie.name}={cookie.value}")
|
||||
|
||||
# Test accessing UI via requests
|
||||
print("\n--- Testing UI Access ---")
|
||||
ui_url = env.url.rstrip('/').replace('/api/v1', '')
|
||||
print(f"UI URL: {ui_url}")
|
||||
|
||||
# Try to access UI home page
|
||||
ui_response = client.network.session.get(ui_url, timeout=30, allow_redirects=True)
|
||||
print(f"Status Code: {ui_response.status_code}")
|
||||
print(f"URL: {ui_response.url}")
|
||||
|
||||
# Check response headers
|
||||
print("\n--- Response Headers ---")
|
||||
pprint(dict(ui_response.headers))
|
||||
|
||||
print("\n--- Response Content Preview (200 chars) ---")
|
||||
print(repr(ui_response.text[:200]))
|
||||
|
||||
if ui_response.status_code == 200:
|
||||
print("\nUI Access: Success")
|
||||
|
||||
# Try to access a dashboard
|
||||
# For testing, just use the home page
|
||||
print("\n--- Checking if login is required ---")
|
||||
if "login" in ui_response.url.lower() or "login" in ui_response.text.lower():
|
||||
print("❌ Not logged in to UI")
|
||||
else:
|
||||
print("✅ Logged in to UI")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {type(e).__name__}: {e}")
|
||||
import traceback
|
||||
print("\nStack Trace:")
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
44
backend/test_decryption.py
Normal file
44
backend/test_decryption.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test script to debug API key decryption issue."""
|
||||
|
||||
from src.core.database import SessionLocal
|
||||
from src.models.llm import LLMProvider
|
||||
from cryptography.fernet import Fernet
|
||||
import os
|
||||
|
||||
# Get the encryption key
|
||||
key = os.getenv("ENCRYPTION_KEY", "ZcytYzi0iHIl4Ttr-GdAEk117aGRogkGvN3wiTxrPpE=").encode()
|
||||
print(f"Encryption key (first 20 chars): {key[:20]}")
|
||||
print(f"Encryption key length: {len(key)}")
|
||||
|
||||
# Create Fernet instance
|
||||
fernet = Fernet(key)
|
||||
|
||||
# Get provider from database
|
||||
db = SessionLocal()
|
||||
provider = db.query(LLMProvider).filter(LLMProvider.id == '6c899741-4108-4196-aea4-f38ad2f0150e').first()
|
||||
|
||||
if provider:
|
||||
print("\nProvider found:")
|
||||
print(f" ID: {provider.id}")
|
||||
print(f" Name: {provider.name}")
|
||||
print(f" Encrypted API Key (first 50 chars): {provider.api_key[:50]}")
|
||||
print(f" Encrypted API Key Length: {len(provider.api_key)}")
|
||||
|
||||
# Test decryption
|
||||
print("\nAttempting decryption...")
|
||||
try:
|
||||
decrypted = fernet.decrypt(provider.api_key.encode()).decode()
|
||||
print("Decryption successful!")
|
||||
print(f" Decrypted key length: {len(decrypted)}")
|
||||
print(f" Decrypted key (first 8 chars): {decrypted[:8]}")
|
||||
print(f" Decrypted key is empty: {len(decrypted) == 0}")
|
||||
except Exception as e:
|
||||
print(f"Decryption failed with error: {e}")
|
||||
print(f"Error type: {type(e).__name__}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print("Provider not found")
|
||||
|
||||
db.close()
|
||||
1
backend/test_encryption.py
Normal file
1
backend/test_encryption.py
Normal file
@@ -0,0 +1 @@
|
||||
[{"key[": 20, ")\n\n# Create Fernet instance\nfernet = Fernet(key)\n\n# Test encrypting an empty string\nempty_encrypted = fernet.encrypt(b\"": ".", "print(f": "nEncrypted empty string: {empty_encrypted"}, {"test-api-key-12345\"\ntest_encrypted = fernet.encrypt(test_key.encode()).decode()\nprint(f": "nEncrypted test key: {test_encrypted"}, {"gAAAAABphhwSZie0OwXjJ78Fk-c4Uo6doNJXipX49AX7Bypzp4ohiRX3hXPXKb45R1vhNUOqbm6Ke3-eRwu_KdWMZ9chFBKmqw==\"\nprint(f": "nStored encrypted key: {stored_key"}, {"len(stored_key)}": "Check if stored key matches empty string encryption\nif stored_key == empty_encrypted:\n print(", "string!": "else:\n print(", "print(f": "mpty string encryption: {empty_encrypted"}, {"stored_key}": "Try to decrypt the stored key\ntry:\n decrypted = fernet.decrypt(stored_key.encode()).decode()\n print(f", "print(f": "ecrypted key length: {len(decrypted)"}, {")\nexcept Exception as e:\n print(f": "nDecryption failed with error: {e"}]
|
||||
@@ -19,8 +19,6 @@ from src.models.clean_release import (
|
||||
ReleaseCandidateStatus,
|
||||
ResourceSourceEntry,
|
||||
ResourceSourceRegistry,
|
||||
ComplianceReport,
|
||||
CheckFinalStatus,
|
||||
)
|
||||
from src.services.clean_release.repository import CleanReleaseRepository
|
||||
|
||||
@@ -109,49 +107,5 @@ def test_get_report_not_found_returns_404():
|
||||
client = TestClient(app)
|
||||
resp = client.get("/api/clean-release/reports/unknown-report")
|
||||
assert resp.status_code == 404
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
def test_get_report_success():
|
||||
repo = _repo_with_seed_data()
|
||||
report = ComplianceReport(
|
||||
report_id="rep-1",
|
||||
check_run_id="run-1",
|
||||
candidate_id="2026.03.03-rc1",
|
||||
generated_at=datetime.now(timezone.utc),
|
||||
final_status=CheckFinalStatus.COMPLIANT,
|
||||
operator_summary="all systems go",
|
||||
structured_payload_ref="manifest-1",
|
||||
violations_count=0,
|
||||
blocking_violations_count=0
|
||||
)
|
||||
repo.save_report(report)
|
||||
app.dependency_overrides[get_clean_release_repository] = lambda: repo
|
||||
try:
|
||||
client = TestClient(app)
|
||||
resp = client.get("/api/clean-release/reports/rep-1")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["report_id"] == "rep-1"
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
def test_prepare_candidate_api_success():
|
||||
repo = _repo_with_seed_data()
|
||||
app.dependency_overrides[get_clean_release_repository] = lambda: repo
|
||||
try:
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
"/api/clean-release/candidates/prepare",
|
||||
json={
|
||||
"candidate_id": "2026.03.03-rc1",
|
||||
"artifacts": [{"path": "file1.txt", "category": "system-init", "reason": "core"}],
|
||||
"sources": ["repo.intra.company.local"],
|
||||
"operator_id": "operator-1",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "prepared"
|
||||
assert "manifest_id" in data
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
@@ -1,6 +1,5 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -16,17 +15,6 @@ def test_git_service_get_repo_path_guard():
|
||||
with pytest.raises(ValueError, match="dashboard_id cannot be None"):
|
||||
service._get_repo_path(None)
|
||||
|
||||
|
||||
def test_git_service_get_repo_path_recreates_base_dir():
|
||||
"""Verify _get_repo_path recreates missing base directory before returning repo path."""
|
||||
service = GitService(base_path="test_repos_runtime_recreate")
|
||||
shutil.rmtree(service.base_path, ignore_errors=True)
|
||||
|
||||
repo_path = service._get_repo_path(42)
|
||||
|
||||
assert Path(service.base_path).is_dir()
|
||||
assert repo_path == str(Path(service.base_path) / "42")
|
||||
|
||||
def test_superset_client_import_dashboard_guard():
|
||||
"""Verify that import_dashboard raises ValueError if file_name is None."""
|
||||
mock_env = Environment(
|
||||
|
||||
144
backend/tests/services/clean_release/test_policy_engine.py
Normal file
144
backend/tests/services/clean_release/test_policy_engine.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# [DEF:backend.tests.services.clean_release.test_policy_engine:Module]
|
||||
# @TIER: CRITICAL
|
||||
# @SEMANTICS: tests, clean-release, policy-engine, deterministic
|
||||
# @PURPOSE: Validate policy model contracts and deterministic classification prerequisites for US1.
|
||||
# @LAYER: Domain
|
||||
# @RELATION: VERIFIES -> backend.src.models.clean_release.CleanProfilePolicy
|
||||
# @INVARIANT: Enterprise policy rejects invalid activation states.
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from src.models.clean_release import CleanProfilePolicy, ProfileType
|
||||
|
||||
|
||||
# [DEF:test_policy_enterprise_clean_valid:Function]
|
||||
# @PURPOSE: Ensure valid enterprise policy payload is accepted.
|
||||
# @PRE: Fixture-like payload contains prohibited categories and registry ref.
|
||||
# @POST: Model is created with external_source_forbidden=True.
|
||||
def test_policy_enterprise_clean_valid():
|
||||
policy = CleanProfilePolicy(
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
policy_version="1.0.0",
|
||||
active=True,
|
||||
prohibited_artifact_categories=["test-data", "demo-data"],
|
||||
required_system_categories=["system-init"],
|
||||
external_source_forbidden=True,
|
||||
internal_source_registry_ref="registry-internal-v1",
|
||||
effective_from=datetime.now(timezone.utc),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
)
|
||||
assert policy.external_source_forbidden is True
|
||||
assert policy.prohibited_artifact_categories == ["test-data", "demo-data"]
|
||||
# [/DEF:test_policy_enterprise_clean_valid:Function]
|
||||
|
||||
|
||||
# [DEF:test_policy_missing_registry_fails:Function]
|
||||
# @PURPOSE: Verify missing registry ref violates policy contract.
|
||||
# @PRE: enterprise-clean policy payload has blank registry ref.
|
||||
# @POST: Validation error is raised.
|
||||
def test_policy_missing_registry_fails():
|
||||
with pytest.raises(ValueError):
|
||||
CleanProfilePolicy(
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
policy_version="1.0.0",
|
||||
active=True,
|
||||
prohibited_artifact_categories=["test-data"],
|
||||
required_system_categories=["system-init"],
|
||||
external_source_forbidden=True,
|
||||
internal_source_registry_ref="",
|
||||
effective_from=datetime.now(timezone.utc),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
)
|
||||
# [/DEF:test_policy_missing_registry_fails:Function]
|
||||
|
||||
|
||||
# [DEF:test_policy_empty_prohibited_categories_fails:Function]
|
||||
# @PURPOSE: Verify enterprise policy cannot activate without prohibited categories.
|
||||
# @PRE: enterprise-clean policy payload has empty prohibited categories.
|
||||
# @POST: Validation error is raised.
|
||||
def test_policy_empty_prohibited_categories_fails():
|
||||
with pytest.raises(ValueError):
|
||||
CleanProfilePolicy(
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
policy_version="1.0.0",
|
||||
active=True,
|
||||
prohibited_artifact_categories=[],
|
||||
required_system_categories=["system-init"],
|
||||
external_source_forbidden=True,
|
||||
internal_source_registry_ref="registry-internal-v1",
|
||||
effective_from=datetime.now(timezone.utc),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
)
|
||||
# [/DEF:test_policy_empty_prohibited_categories_fails:Function]
|
||||
|
||||
|
||||
# [DEF:test_policy_conflicting_external_forbidden_flag_fails:Function]
|
||||
# @PURPOSE: Verify enterprise policy enforces external_source_forbidden=true.
|
||||
# @PRE: enterprise-clean policy payload sets external_source_forbidden to false.
|
||||
# @POST: Validation error is raised.
|
||||
def test_policy_conflicting_external_forbidden_flag_fails():
|
||||
with pytest.raises(ValueError):
|
||||
CleanProfilePolicy(
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
policy_version="1.0.0",
|
||||
active=True,
|
||||
prohibited_artifact_categories=["test-data"],
|
||||
required_system_categories=["system-init"],
|
||||
external_source_forbidden=False,
|
||||
internal_source_registry_ref="registry-internal-v1",
|
||||
effective_from=datetime.now(timezone.utc),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
)
|
||||
# [/DEF:test_policy_conflicting_external_forbidden_flag_fails:Function]
|
||||
# [/DEF:backend.tests.services.clean_release.test_policy_engine:Module]
|
||||
from src.models.clean_release import ResourceSourceRegistry, ResourceSourceEntry, RegistryStatus
|
||||
from src.services.clean_release.policy_engine import CleanPolicyEngine
|
||||
|
||||
def _policy_enterprise_clean() -> CleanProfilePolicy:
|
||||
return CleanProfilePolicy(
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
policy_version="1.0.0",
|
||||
active=True,
|
||||
prohibited_artifact_categories=["test-data"],
|
||||
required_system_categories=["system-init"],
|
||||
external_source_forbidden=True,
|
||||
internal_source_registry_ref="registry-internal-v1",
|
||||
effective_from=datetime.now(timezone.utc),
|
||||
profile=ProfileType.ENTERPRISE_CLEAN,
|
||||
)
|
||||
|
||||
def _registry() -> ResourceSourceRegistry:
|
||||
return ResourceSourceRegistry(
|
||||
registry_id="registry-internal-v1",
|
||||
name="Internal",
|
||||
entries=[ResourceSourceEntry(source_id="1", host="nexus.internal", protocol="https", purpose="pkg", enabled=True)],
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
updated_by="tester",
|
||||
)
|
||||
|
||||
# [DEF:test_policy_valid:Function]
|
||||
# @PURPOSE: Validate policy valid scenario
|
||||
def test_policy_valid():
|
||||
engine = CleanPolicyEngine(_policy_enterprise_clean(), _registry())
|
||||
res = engine.validate_policy()
|
||||
assert res.ok is True
|
||||
|
||||
# [DEF:test_conflicting_registry:Function]
|
||||
# @PURPOSE: Validate policy conflicting registry edge
|
||||
def test_conflicting_registry():
|
||||
reg = _registry()
|
||||
reg.registry_id = "other-registry"
|
||||
engine = CleanPolicyEngine(_policy_enterprise_clean(), reg)
|
||||
res = engine.validate_policy()
|
||||
assert res.ok is False
|
||||
assert "Policy registry ref does not match provided registry" in res.blocking_reasons
|
||||
|
||||
# [DEF:test_external_endpoint:Function]
|
||||
# @PURPOSE: Validate policy external endpoint edge
|
||||
def test_external_endpoint():
|
||||
engine = CleanPolicyEngine(_policy_enterprise_clean(), _registry())
|
||||
res = engine.validate_resource_source("external.org")
|
||||
assert res.ok is False
|
||||
assert res.violation["category"] == "external-source"
|
||||
|
||||
27
check_test_data.py
Normal file
27
check_test_data.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
|
||||
def check_file(filepath):
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if '@TIER: CRITICAL' in content:
|
||||
if '@TEST_DATA' not in content:
|
||||
return filepath
|
||||
except Exception as e:
|
||||
print(f"Error reading {filepath}: {e}")
|
||||
return None
|
||||
|
||||
missing_files = []
|
||||
for root_dir in ['backend/src', 'frontend/src']:
|
||||
for dirpath, _, filenames in os.walk(root_dir):
|
||||
for name in filenames:
|
||||
ext = os.path.splitext(name)[1]
|
||||
if ext in ['.py', '.js', '.ts', '.svelte']:
|
||||
full_path = os.path.join(dirpath, name)
|
||||
res = check_file(full_path)
|
||||
if res:
|
||||
missing_files.append(res)
|
||||
|
||||
print("Files missing @TEST_DATA:")
|
||||
for f in missing_files:
|
||||
print(f)
|
||||
@@ -1,17 +1,10 @@
|
||||
// [DEF:frontend.src.components.__tests__.task_log_viewer:Module]
|
||||
// @TIER: STANDARD
|
||||
// @TIER: CRITICAL
|
||||
// @SEMANTICS: tests, task-log, viewer, mount, components
|
||||
// @PURPOSE: Unit tests for TaskLogViewer component by mounting it and observing the DOM.
|
||||
// @LAYER: UI (Tests)
|
||||
// @RELATION: VERIFIES -> frontend/src/components/TaskLogViewer.svelte
|
||||
// @INVARIANT: Duplicate logs are never appended. Polling only active for in-progress tasks.
|
||||
// @TEST_CONTRACT: TaskLogViewerPropsAndLogStream -> RenderedLogTimeline
|
||||
// @TEST_SCENARIO: historical_and_realtime_merge -> Historical logs render and realtime logs append without duplication.
|
||||
// @TEST_FIXTURE: valid_viewer -> INLINE_JSON
|
||||
// @TEST_EDGE: no_task_id -> Null taskId does not trigger fetch.
|
||||
// @TEST_EDGE: fetch_failure -> Network failure renders recoverable error state with retry action.
|
||||
// @TEST_EDGE: duplicate_realtime_entry -> Existing log is not duplicated when repeated in realtime stream.
|
||||
// @TEST_INVARIANT: no_duplicate_log_rows -> VERIFIED_BY: [historical_and_realtime_merge, duplicate_realtime_entry]
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/svelte';
|
||||
@@ -22,8 +15,6 @@ vi.mock('../../services/taskService.js', () => ({
|
||||
getTaskLogs: vi.fn()
|
||||
}));
|
||||
|
||||
const getTaskLogsMock = vi.mocked(getTaskLogs);
|
||||
|
||||
vi.mock('../../lib/i18n', () => ({
|
||||
t: {
|
||||
subscribe: (fn) => {
|
||||
@@ -48,13 +39,13 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('renders loading state initially', () => {
|
||||
getTaskLogsMock.mockResolvedValue([]);
|
||||
getTaskLogs.mockResolvedValue([]);
|
||||
render(TaskLogViewer, { inline: true, taskId: 'task-123' });
|
||||
expect(screen.getByText('Loading...')).toBeDefined();
|
||||
});
|
||||
|
||||
it('fetches and displays historical logs', async () => {
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
getTaskLogs.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Historical log entry' }
|
||||
]);
|
||||
|
||||
@@ -68,7 +59,7 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('displays error message on fetch failure', async () => {
|
||||
getTaskLogsMock.mockRejectedValue(new Error('Network error fetching logs'));
|
||||
getTaskLogs.mockRejectedValue(new Error('Network error fetching logs'));
|
||||
|
||||
render(TaskLogViewer, { inline: true, taskId: 'task-123' });
|
||||
|
||||
@@ -79,7 +70,7 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('appends real-time logs passed as props', async () => {
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
getTaskLogs.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Historical log entry' }
|
||||
]);
|
||||
|
||||
@@ -108,7 +99,7 @@ describe('TaskLogViewer Component', () => {
|
||||
});
|
||||
|
||||
it('deduplicates real-time logs that are already in historical logs', async () => {
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
getTaskLogs.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Duplicate log entry' }
|
||||
]);
|
||||
|
||||
@@ -141,7 +132,7 @@ describe('TaskLogViewer Component', () => {
|
||||
|
||||
// @TEST_FIXTURE valid_viewer
|
||||
it('fetches and displays historical logs in modal mode under valid_viewer fixture', async () => {
|
||||
getTaskLogsMock.mockResolvedValue([
|
||||
getTaskLogs.mockResolvedValue([
|
||||
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Modal log entry' }
|
||||
]);
|
||||
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
// @LAYER: UI Tests
|
||||
// @RELATION: VERIFIES -> frontend/src/lib/components/assistant/AssistantChatPanel.svelte
|
||||
// @INVARIANT: Critical assistant UX states and action hooks remain present in component source.
|
||||
// @TEST_CONTRACT: AssistantChatSourceArtifacts -> ContractAssertions
|
||||
// @TEST_SCENARIO: assistant_contract_and_i18n_intact -> Component semantic/UX anchors and locale keys stay consistent.
|
||||
// @TEST_FIXTURE: assistant_locales_en_ru -> file:src/lib/i18n/locales/en.json + file:src/lib/i18n/locales/ru.json
|
||||
// @TEST_EDGE: missing_component_anchor -> Missing DEF/UX tags fails contract assertion.
|
||||
// @TEST_EDGE: missing_action_hook -> Missing confirm/cancel/open_task hooks fails integration assertion.
|
||||
// @TEST_EDGE: missing_locale_key -> Missing assistant locale key in en/ru fails dictionary assertion.
|
||||
// @TEST_INVARIANT: assistant_ux_contract_visible -> VERIFIED_BY: [assistant_contract_and_i18n_intact]
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
@@ -48,7 +41,7 @@ describe('AssistantChatPanel integration contract', () => {
|
||||
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
|
||||
|
||||
expect(source).toContain('<!-- [DEF' + ':AssistantChatPanel:Component] -->');
|
||||
expect(source).toContain('@TIER' + ': CRITICAL');
|
||||
expect(source).toContain('@TIER: CRITICAL');
|
||||
expect(source).toContain('@UX_STATE: LoadingHistory');
|
||||
expect(source).toContain('@UX_STATE: Sending');
|
||||
expect(source).toContain('@UX_STATE: Error');
|
||||
|
||||
@@ -27,12 +27,14 @@
|
||||
* @TEST_INVARIANT correct_iteration -> verifies: [renders_list, empty_list]
|
||||
*/
|
||||
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import ReportCard from "./ReportCard.svelte";
|
||||
|
||||
let { reports = [], selectedReportId = null, onselect } = $props();
|
||||
let { reports = [], selectedReportId = null } = $props();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function handleSelect(event) {
|
||||
if (onselect) onselect({ report: event.detail.report });
|
||||
dispatch("select", { report: event.detail.report });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,19 +2,12 @@
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
// [DEF:frontend.src.lib.components.reports.__tests__.report_card.ux:Module]
|
||||
// @TIER: STANDARD
|
||||
// @TIER: CRITICAL
|
||||
// @SEMANTICS: reports, ux-tests, card, states, recovery
|
||||
// @PURPOSE: Test UX states and transitions for ReportCard component
|
||||
// @LAYER: UI
|
||||
// @RELATION: VERIFIES -> ../ReportCard.svelte
|
||||
// @INVARIANT: Each test asserts at least one observable UX contract outcome.
|
||||
// @TEST_CONTRACT: ReportCardInputProps -> ObservableUXOutput
|
||||
// @TEST_SCENARIO: ready_state_shows_summary_status_type -> Ready state renders summary/status/type labels.
|
||||
// @TEST_FIXTURE: valid_report_card -> INLINE_JSON
|
||||
// @TEST_EDGE: empty_report_object -> Missing fields use placeholders and fallback labels.
|
||||
// @TEST_EDGE: random_status -> Unknown status is rendered without crashing.
|
||||
// @TEST_EDGE: missing_optional_fields -> Partial report keeps component interactive and emits select.
|
||||
// @TEST_INVARIANT: report_card_state_is_observable -> VERIFIED_BY: [ready_state_shows_summary_status_type, empty_report_object, random_status]
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/svelte';
|
||||
@@ -46,7 +39,7 @@ describe('ReportCard UX Contract', () => {
|
||||
|
||||
// @UX_STATE: Ready -> Card displays summary/status/type.
|
||||
it('should display summary, status and type in Ready state', () => {
|
||||
render(ReportCard, { report: mockReport, onselect: vi.fn() });
|
||||
render(ReportCard, { report: mockReport });
|
||||
expect(screen.getByText(mockReport.summary)).toBeDefined();
|
||||
// mockReport.status is "success", getStatusLabel(status) returns $t.reports?.status_success
|
||||
expect(screen.getByText('Success')).toBeDefined();
|
||||
@@ -68,7 +61,7 @@ describe('ReportCard UX Contract', () => {
|
||||
// @UX_RECOVERY: Missing fields are rendered with explicit placeholder text.
|
||||
it('should render placeholders for missing fields', () => {
|
||||
const partialReport = { report_id: 'partial-1' };
|
||||
render(ReportCard, { report: partialReport, onselect: vi.fn() });
|
||||
render(ReportCard, { report: partialReport });
|
||||
|
||||
// Check placeholders (using text from mocked $t)
|
||||
const placeholders = screen.getAllByText('Not provided');
|
||||
@@ -86,7 +79,7 @@ describe('ReportCard UX Contract', () => {
|
||||
summary: "Test Summary",
|
||||
updated_at: "2024-01-01"
|
||||
};
|
||||
render(ReportCard, { report: validReportCard, onselect: vi.fn() });
|
||||
render(ReportCard, { report: validReportCard });
|
||||
|
||||
expect(screen.getByText('Test Summary')).toBeDefined();
|
||||
expect(screen.getByText('Success')).toBeDefined();
|
||||
@@ -94,14 +87,14 @@ describe('ReportCard UX Contract', () => {
|
||||
|
||||
// @TEST_EDGE empty_report_object
|
||||
it('should handle completely empty report object gracefully', () => {
|
||||
render(ReportCard, { report: {}, onselect: vi.fn() });
|
||||
render(ReportCard, { report: {} });
|
||||
const placeholders = screen.getAllByText('Not provided');
|
||||
expect(placeholders.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// @TEST_EDGE random_status
|
||||
it('should render random status directly if no translation matches', () => {
|
||||
render(ReportCard, { report: { status: "unknown_status_code" }, onselect: vi.fn() });
|
||||
render(ReportCard, { report: { status: "unknown_status_code" } });
|
||||
expect(screen.getByText('unknown_status_code')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
// [DEF:frontend.src.lib.components.reports.__tests__.reports_list.ux:Module]
|
||||
// @TIER: STANDARD
|
||||
// @SEMANTICS: reports, list, ux-tests, events, iteration
|
||||
// @PURPOSE: Test ReportsList component iteration and event forwarding.
|
||||
// @LAYER: UI
|
||||
// @RELATION: VERIFIES -> ../ReportsList.svelte
|
||||
// [/DEF:frontend.src.lib.components.reports.__tests__.reports_list.ux:Module]
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/svelte';
|
||||
import ReportsList from '../ReportsList.svelte';
|
||||
|
||||
// Mock i18n since ReportsList -> ReportCard -> i18n
|
||||
vi.mock('$lib/i18n', () => ({
|
||||
t: {
|
||||
subscribe: (fn) => {
|
||||
fn({
|
||||
reports: {
|
||||
not_provided: 'N/A',
|
||||
status_success: 'OK',
|
||||
status_failed: 'ERR'
|
||||
}
|
||||
});
|
||||
return () => { };
|
||||
}
|
||||
},
|
||||
_: vi.fn((key) => key)
|
||||
}));
|
||||
|
||||
describe('ReportsList UX Contract', () => {
|
||||
const mockReports = [
|
||||
{ report_id: '1', summary: 'Report One', task_type: 'migration', status: 'success' },
|
||||
{ report_id: '2', summary: 'Report Two', task_type: 'backup', status: 'failed' }
|
||||
];
|
||||
|
||||
// @TEST_FIXTURE renders_list
|
||||
it('should render multiple report cards and mark the selected one', () => {
|
||||
const { container } = render(ReportsList, { reports: mockReports, selectedReportId: '2' });
|
||||
|
||||
expect(screen.getByText('Report One')).toBeDefined();
|
||||
expect(screen.getByText('Report Two')).toBeDefined();
|
||||
|
||||
// Check selection logic - we look for a marker or class change in the child cards
|
||||
// In our simplified test, we check if screen find two buttons
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons.length).toBe(2);
|
||||
});
|
||||
|
||||
// @TEST_EDGE empty_list
|
||||
// @TEST_INVARIANT correct_iteration
|
||||
it('should render empty container for empty list', () => {
|
||||
const { container } = render(ReportsList, { reports: [] });
|
||||
// Root div should have space-y-2 class but be empty
|
||||
const div = container.querySelector('.space-y-2');
|
||||
expect(div).toBeDefined();
|
||||
expect(div.children.length).toBe(0);
|
||||
});
|
||||
|
||||
// @UX_FEEDBACK: Click on report emits select event.
|
||||
// @TEST_CONTRACT Component_ReportsList -> Forwards select events from children
|
||||
it('should forward select event when a report card is clicked', async () => {
|
||||
const onSelect = vi.fn();
|
||||
const { component } = render(ReportsList, { reports: [mockReports[0]], onselect: onSelect });
|
||||
|
||||
const button = screen.getByRole('button');
|
||||
await fireEvent.click(button);
|
||||
|
||||
expect(onSelect).toHaveBeenCalled();
|
||||
expect(onSelect.mock.calls[0][0].report.report_id).toBe('1');
|
||||
});
|
||||
});
|
||||
@@ -189,36 +189,9 @@ class SemanticEntity:
|
||||
with belief_scope("get_tier"):
|
||||
tier_str = self.tags.get("TIER", "STANDARD").upper()
|
||||
try:
|
||||
base_tier = Tier(tier_str)
|
||||
return Tier(tier_str)
|
||||
except ValueError:
|
||||
base_tier = Tier.STANDARD
|
||||
|
||||
# Dynamic Tier Adjustments based on User Feedback
|
||||
|
||||
# 1. Tests should never be higher than STANDARD
|
||||
if "test" in self.file_path.lower() or "/__tests__/" in self.file_path or self.name.startswith("test_"):
|
||||
if base_tier == Tier.CRITICAL:
|
||||
return Tier.STANDARD
|
||||
|
||||
# 2. Svelte components -> TRIVIAL/STANDARD (unless layout/page)
|
||||
if self.file_path.endswith(".svelte"):
|
||||
if "+page" not in self.name and "+layout" not in self.name and "Page" not in self.name and "Layout" not in self.name:
|
||||
if base_tier == Tier.CRITICAL:
|
||||
return Tier.STANDARD
|
||||
|
||||
# 3. Tooling scripts
|
||||
if "scripts/" in self.file_path or "_tui.py" in self.file_path:
|
||||
if base_tier == Tier.CRITICAL:
|
||||
return Tier.STANDARD
|
||||
|
||||
# 4. Promote critical security/data paths
|
||||
critical_keywords = ["auth", "security", "jwt", "database", "migration", "config", "session"]
|
||||
if any(keyword in self.file_path.lower() for keyword in critical_keywords) and "test" not in self.file_path.lower():
|
||||
# Allow explicit overrides to lower tiers if explicitly tagged TRIVIAL, otherwise promote logic mapping
|
||||
if base_tier != Tier.TRIVIAL:
|
||||
return Tier.CRITICAL
|
||||
|
||||
return base_tier
|
||||
return Tier.STANDARD
|
||||
# [/DEF:get_tier:Function]
|
||||
|
||||
# [DEF:to_dict:Function]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -137,7 +137,6 @@
|
||||
- [X] T041 Add release checklist artifact template for compliance evidence packaging in `specs/023-clean-repo-enterprise/checklists/release-readiness.md`
|
||||
- [X] T042 Resolve numeric-prefix governance conflict note (`020-*`) and document decision in `specs/023-clean-repo-enterprise/plan.md`
|
||||
- [X] T043 Update feature status traceability and final notes in `specs/023-clean-repo-enterprise/plan.md`
|
||||
- [X] T044 Remediate CRITICAL semantic test-contract gaps by adding `@TEST_CONTRACT` metadata in backend/frontend flagged modules and recording coverage update in `specs/023-clean-repo-enterprise/tests/coverage.md`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,15 +7,7 @@
|
||||
| `clean_release.report_builder` | `report_builder.py` | CRITICAL | ✅ Yes | 1/1 | 3/3 | 1/1 |
|
||||
| `clean_release.manifest_builder` | `manifest_builder.py` | STANDARD | ✅ Yes | N/A | N/A | N/A |
|
||||
| `clean_release.source_isolation` | `source_isolation.py` | STANDARD | ✅ Yes | N/A | N/A | N/A |
|
||||
| `clean_release.preparation_service` | `preparation_service.py` | STANDARD | ✅ Yes | 1/1 | 2/2 | 1/1 |
|
||||
| `clean_release.audit_service` | `audit_service.py` | STANDARD | ✅ Yes | N/A | N/A | 1/1 |
|
||||
| `clean_release.stages` | `stages.py` | STANDARD | ✅ Yes | N/A | 3/3 | N/A |
|
||||
| `api.routes.clean_release` | `clean_release.py` | STANDARD | ✅ Yes | 1/1 | 2/2 | 1/1 |
|
||||
| `api.routes.tasks.get_task_logs` | `tasks.py` | CRITICAL | ✅ Yes | 1/1 | 3/3 | 1/1 |
|
||||
| `models.clean_release` | `clean_release.py` | CRITICAL | ✅ Yes | 1/1 | 3/3 | 1/1 |
|
||||
| `frontend.assistant_chat.integration` | `assistant_chat.integration.test.js` | CRITICAL | ✅ Yes | 1/1 | 3/3 | 1/1 |
|
||||
| `frontend.reports.report_card.ux` | `report_card.ux.test.js` | CRITICAL | ✅ Yes | 1/1 | 3/3 | 1/1 |
|
||||
| `frontend.task_log_viewer` | `task_log_viewer.test.js` | CRITICAL | ✅ Yes | 1/1 | 3/3 | 1/1 |
|
||||
| `api.routes.clean_release` | `clean_release.py` | STANDARD | ✅ Yes | N/A | N/A | N/A |
|
||||
|
||||
## CRITICAL Edge Cases Covered
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Test Report: Global CRITICAL Coverage
|
||||
|
||||
Date: 2026-03-04
|
||||
Executor: GRACE Tester
|
||||
|
||||
## Coverage Matrix
|
||||
|
||||
| Module | TIER | Tests | Edge Covered | Invariants Covered |
|
||||
|--------|------|------|----------|------------|
|
||||
| backend/src/api/routes/tasks.py | CRITICAL | - | - | - |
|
||||
| backend/src/models/clean_release.py | CRITICAL | - | - | - |
|
||||
| frontend/src/lib/components/assistant/__tests__/assistant_chat.integration.test.js | CRITICAL | - | - | - |
|
||||
| frontend/src/lib/components/reports/__tests__/report_card.ux.test.js | CRITICAL | - | - | - |
|
||||
| frontend/src/components/__tests__/task_log_viewer.test.js | CRITICAL | - | - | - |
|
||||
|
||||
*(Note: Matrix focuses only on modules that triggered the fail policy)*
|
||||
|
||||
## Contract Validation
|
||||
|
||||
- TEST_CONTRACT validated ❌
|
||||
- All FIXTURES tested ❌
|
||||
- All EDGES tested ❌
|
||||
- All INVARIANTS verified ❌
|
||||
|
||||
## Results
|
||||
|
||||
Total: 0
|
||||
Passed: 0
|
||||
Failed: 5
|
||||
Skipped: 38
|
||||
|
||||
## Violations
|
||||
|
||||
| Module | Problem | Severity |
|
||||
|--------|---------|----------|
|
||||
| `backend/src/api/routes/tasks.py` | [COHERENCE_CHECK_FAILED] Missing TEST_CONTRACT | CRITICAL |
|
||||
| `backend/src/models/clean_release.py` | [COHERENCE_CHECK_FAILED] Missing TEST_CONTRACT | CRITICAL |
|
||||
| `frontend/src/lib/components/assistant/__tests__/assistant_chat.integration.test.js` | [COHERENCE_CHECK_FAILED] Missing TEST_CONTRACT | CRITICAL |
|
||||
| `frontend/src/lib/components/reports/__tests__/report_card.ux.test.js` | [COHERENCE_CHECK_FAILED] Missing TEST_CONTRACT | CRITICAL |
|
||||
| `frontend/src/components/__tests__/task_log_viewer.test.js` | [COHERENCE_CHECK_FAILED] Missing TEST_CONTRACT | CRITICAL |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- [ ] Add `@TEST_CONTRACT` to `backend/src/api/routes/tasks.py` (for `get_task_logs` method)
|
||||
- [ ] Add `@TEST_CONTRACT` to `backend/src/models/clean_release.py`
|
||||
- [ ] Add `@TEST_CONTRACT` to `frontend/src/lib/components/assistant/__tests__/assistant_chat.integration.test.js` or adjust TIER
|
||||
- [ ] Add `@TEST_CONTRACT` to `frontend/src/lib/components/reports/__tests__/report_card.ux.test.js` or adjust TIER
|
||||
- [ ] Add `@TEST_CONTRACT` to `frontend/src/components/__tests__/task_log_viewer.test.js` or adjust TIER
|
||||
Reference in New Issue
Block a user