Major cleanup: Remove redundant code, consolidate knowledge base
- Delete redundant directories: demo/, server/, orchestrator/, team-portal/, servers/ - Remove all human-readable documentation (docs/, .dss/*.md, admin-ui/*.md) - Consolidate 4 knowledge JSON files into single DSS_CORE.json - Clear browser logs (7.5MB), backups, temp files - Remove obsolete configs (.cursorrules, .dss-boundaries.yaml, .ds-swarm/) - Reduce project from 20MB to ~8MB Kept: tools/, admin-ui/, cli/, dss-claude-plugin/, .dss/schema/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
# DSS MVP1 - Design System Server
|
||||
|
||||
A modern design system orchestration platform built with Python/FastAPI, leveraging style-dictionary and shadcn/ui.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Core**: Python/FastAPI + Pydantic models
|
||||
- **Token Transformation**: style-dictionary (Node.js)
|
||||
- **Component Management**: shadcn CLI
|
||||
- **Testing**: pytest with >80% coverage
|
||||
- **Database**: SQLite
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
dss-mvp1/
|
||||
├── dss/ # Core application
|
||||
│ ├── models/ # Pydantic models
|
||||
│ ├── validators/ # Validation logic
|
||||
│ ├── tools/ # External tool wrappers
|
||||
│ └── api/ # FastAPI routes
|
||||
├── tests/ # Test suite
|
||||
│ ├── fixtures/ # Test data
|
||||
│ ├── unit/ # Unit tests
|
||||
│ └── integration/ # Integration tests
|
||||
└── pytest.ini # Test configuration
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install Python dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Install Node.js dependencies
|
||||
npm install
|
||||
|
||||
# Run tests
|
||||
pytest
|
||||
|
||||
# Run unit tests only
|
||||
pytest tests/unit -m unit
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
This is a test-driven rebuild of DSS following clean architecture principles.
|
||||
|
||||
**Week 1**: Foundation + Core Pipeline
|
||||
**Week 2**: Theme System + shadcn Integration
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit tests: >70% of test suite
|
||||
- Integration tests: ~25%
|
||||
- E2E tests: ~5%
|
||||
- Target coverage: >80%
|
||||
@@ -1,380 +0,0 @@
|
||||
# DSS Settings & Management
|
||||
|
||||
Complete guide for DSS configuration, testing, and system management.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Check system information
|
||||
python3 -m dss.settings info
|
||||
|
||||
# Check dependencies
|
||||
python3 -m dss.settings check-deps
|
||||
|
||||
# Run all tests
|
||||
python3 -m dss.settings test
|
||||
|
||||
# Reset DSS to fresh state
|
||||
python3 -m dss.settings reset
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### System Information
|
||||
|
||||
```bash
|
||||
# Show DSS configuration and paths
|
||||
python3 -m dss.settings info
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
📊 DSS System Information:
|
||||
project_root: /path/to/dss-mvp1
|
||||
dss_dir: /path/to/dss-mvp1/dss
|
||||
tests_dir: /path/to/dss-mvp1/tests
|
||||
cache_dir: /home/user/.dss/cache
|
||||
database_path: /home/user/.dss/dss.db
|
||||
has_anthropic_key: True/False
|
||||
has_figma_token: True/False
|
||||
use_mock_apis: True
|
||||
```
|
||||
|
||||
### Dependency Check
|
||||
|
||||
```bash
|
||||
# Verify all required dependencies are installed
|
||||
python3 -m dss.settings check-deps
|
||||
```
|
||||
|
||||
**Checks:**
|
||||
- ✅ pydantic
|
||||
- ✅ fastapi
|
||||
- ✅ pytest
|
||||
- ✅ requests
|
||||
- ✅ style-dictionary (Node.js)
|
||||
|
||||
### Running Tests
|
||||
|
||||
#### All Tests
|
||||
```bash
|
||||
python3 -m dss.settings test
|
||||
```
|
||||
|
||||
#### Unit Tests Only
|
||||
```bash
|
||||
python3 -m dss.settings test-unit
|
||||
```
|
||||
|
||||
#### Integration Tests Only
|
||||
```bash
|
||||
python3 -m dss.settings test-integration
|
||||
```
|
||||
|
||||
#### With Coverage Report
|
||||
```bash
|
||||
python3 -m dss.settings test-coverage
|
||||
```
|
||||
|
||||
#### Specific Test File
|
||||
```bash
|
||||
python3 -m dss.settings test tests/unit/test_models.py
|
||||
```
|
||||
|
||||
### Reset DSS
|
||||
|
||||
Reset DSS to fresh state, deleting all user data but keeping structure.
|
||||
|
||||
```bash
|
||||
python3 -m dss.settings reset
|
||||
```
|
||||
|
||||
**⚠️ WARNING:** This will prompt for confirmation and delete:
|
||||
- User-created themes (keeps default themes)
|
||||
- Cache directory
|
||||
- Figma cache
|
||||
- Database files
|
||||
- Test database
|
||||
- `__pycache__` directories
|
||||
|
||||
**Keeps:**
|
||||
- Directory structure
|
||||
- Default themes
|
||||
- Source code
|
||||
- Configuration files
|
||||
- Test fixtures
|
||||
|
||||
**Confirmation Required:**
|
||||
```
|
||||
⚠️ WARNING: This will delete all themes, projects, and cached data.
|
||||
The DSS structure will be preserved.
|
||||
Type 'RESET' to confirm:
|
||||
```
|
||||
|
||||
Type `RESET` (exactly) to proceed.
|
||||
|
||||
## Python API Usage
|
||||
|
||||
### Using Settings in Code
|
||||
|
||||
```python
|
||||
from dss.settings import settings, manager
|
||||
|
||||
# Access configuration
|
||||
print(settings.PROJECT_ROOT)
|
||||
print(settings.ANTHROPIC_API_KEY)
|
||||
|
||||
# Run tests programmatically
|
||||
result = manager.run_tests("tests/unit/")
|
||||
|
||||
# Get system info
|
||||
info = manager.get_system_info()
|
||||
|
||||
# Check dependencies
|
||||
deps = manager.check_dependencies()
|
||||
```
|
||||
|
||||
### DSSSettings
|
||||
|
||||
Configuration class using Pydantic Settings.
|
||||
|
||||
**Attributes:**
|
||||
- `PROJECT_ROOT: Path` - Project root directory
|
||||
- `DSS_DIR: Path` - DSS source directory
|
||||
- `TESTS_DIR: Path` - Tests directory
|
||||
- `CACHE_DIR: Path` - Cache directory (~/.dss/cache)
|
||||
- `ANTHROPIC_API_KEY: Optional[str]` - Claude API key
|
||||
- `FIGMA_TOKEN: Optional[str]` - Figma API token
|
||||
- `FIGMA_FILE_KEY: Optional[str]` - Figma file key
|
||||
- `DATABASE_PATH: Path` - Main database path
|
||||
- `TEST_DATABASE_PATH: Path` - Test database path
|
||||
- `USE_MOCK_APIS: bool` - Use mock APIs in tests
|
||||
|
||||
**Configuration:**
|
||||
- Reads from `.env` file
|
||||
- Case-sensitive environment variables
|
||||
- Ignores extra fields
|
||||
|
||||
### DSSManager
|
||||
|
||||
Management utility class.
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `run_tests(test_path=None, verbose=True, coverage=False, markers=None)`
|
||||
Run pytest test suite.
|
||||
|
||||
```python
|
||||
# Run all tests
|
||||
manager.run_tests()
|
||||
|
||||
# Run specific file
|
||||
manager.run_tests("tests/unit/test_models.py")
|
||||
|
||||
# Run with markers
|
||||
manager.run_tests(markers="unit")
|
||||
|
||||
# Run with coverage
|
||||
manager.run_tests(coverage=True)
|
||||
```
|
||||
|
||||
#### `run_unit_tests()`
|
||||
Shortcut for running unit tests only.
|
||||
|
||||
```python
|
||||
manager.run_unit_tests()
|
||||
```
|
||||
|
||||
#### `run_integration_tests()`
|
||||
Shortcut for running integration tests only.
|
||||
|
||||
```python
|
||||
manager.run_integration_tests()
|
||||
```
|
||||
|
||||
#### `run_all_tests_with_coverage()`
|
||||
Run all tests with coverage report.
|
||||
|
||||
```python
|
||||
manager.run_all_tests_with_coverage()
|
||||
```
|
||||
|
||||
#### `reset_dss(keep_structure=True, confirm=True)`
|
||||
Reset DSS to fresh state.
|
||||
|
||||
```python
|
||||
# With confirmation prompt
|
||||
results = manager.reset_dss()
|
||||
|
||||
# Without confirmation (dangerous!)
|
||||
results = manager.reset_dss(confirm=False)
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```python
|
||||
{
|
||||
"status": "success",
|
||||
"deleted": ["file1.py", "cache/", ...],
|
||||
"kept": ["default_themes.py", "models/", ...],
|
||||
"errors": []
|
||||
}
|
||||
```
|
||||
|
||||
#### `get_system_info()`
|
||||
Get DSS system information.
|
||||
|
||||
```python
|
||||
info = manager.get_system_info()
|
||||
# {
|
||||
# "project_root": "/path/to/project",
|
||||
# "dss_dir": "/path/to/dss",
|
||||
# ...
|
||||
# }
|
||||
```
|
||||
|
||||
#### `check_dependencies()`
|
||||
Check installed dependencies.
|
||||
|
||||
```python
|
||||
deps = manager.check_dependencies()
|
||||
# {
|
||||
# "pydantic": True,
|
||||
# "fastapi": True,
|
||||
# "pytest": True,
|
||||
# "requests": True,
|
||||
# "style-dictionary": True
|
||||
# }
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```bash
|
||||
# API Keys
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
|
||||
FIGMA_TOKEN=your-figma-token-here
|
||||
FIGMA_FILE_KEY=your-file-key-here
|
||||
|
||||
# Database
|
||||
DATABASE_PATH=/home/user/.dss/dss.db
|
||||
|
||||
# Testing
|
||||
USE_MOCK_APIS=true
|
||||
```
|
||||
|
||||
See `.env.test` for test environment example.
|
||||
|
||||
## Test Fixtures
|
||||
|
||||
Mock API keys are available in `tests/fixtures/api_keys.json`:
|
||||
|
||||
```python
|
||||
# In tests
|
||||
def test_my_feature(mock_anthropic_key, mock_figma_token):
|
||||
# Use mock keys automatically
|
||||
wrapper = FigmaWrapper(api_token=mock_figma_token, ...)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Missing Dependencies
|
||||
|
||||
```bash
|
||||
# Check what's missing
|
||||
python3 -m dss.settings check-deps
|
||||
|
||||
# Install Python dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Install Node dependencies
|
||||
npm install
|
||||
```
|
||||
|
||||
### API Keys Not Loaded
|
||||
|
||||
1. Create `.env` file in project root
|
||||
2. Add your API keys
|
||||
3. Verify with `python3 -m dss.settings info`
|
||||
|
||||
### Tests Failing
|
||||
|
||||
```bash
|
||||
# Run tests with verbose output
|
||||
python3 -m dss.settings test -v
|
||||
|
||||
# Run specific failing test
|
||||
python3 -m dss.settings test tests/unit/test_models.py::TestTheme::test_specific
|
||||
```
|
||||
|
||||
### Reset Not Working
|
||||
|
||||
Ensure you type `RESET` exactly (all caps) when prompted.
|
||||
|
||||
Or use Python API without confirmation:
|
||||
|
||||
```python
|
||||
from dss.settings import manager
|
||||
results = manager.reset_dss(confirm=False)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Daily Development Workflow
|
||||
|
||||
```bash
|
||||
# 1. Check system is ready
|
||||
python3 -m dss.settings check-deps
|
||||
|
||||
# 2. Run tests before making changes
|
||||
python3 -m dss.settings test-unit
|
||||
|
||||
# 3. Make your changes...
|
||||
|
||||
# 4. Run tests again
|
||||
python3 -m dss.settings test
|
||||
|
||||
# 5. If tests pass, commit!
|
||||
git add .
|
||||
git commit -m "Your changes"
|
||||
```
|
||||
|
||||
### Setting Up New Environment
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
pip install -r requirements.txt
|
||||
npm install
|
||||
|
||||
# 2. Create .env file
|
||||
cp .env.test .env
|
||||
|
||||
# 3. Add your real API keys to .env
|
||||
|
||||
# 4. Verify setup
|
||||
python3 -m dss.settings info
|
||||
python3 -m dss.settings check-deps
|
||||
|
||||
# 5. Run tests to confirm
|
||||
python3 -m dss.settings test
|
||||
```
|
||||
|
||||
### Starting Fresh
|
||||
|
||||
```bash
|
||||
# Reset everything
|
||||
python3 -m dss.settings reset
|
||||
|
||||
# Verify reset
|
||||
python3 -m dss.settings info
|
||||
|
||||
# Run tests to ensure structure intact
|
||||
python3 -m dss.settings test
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [README.md](README.md) - Project overview
|
||||
- [.env.test](.env.test) - Test environment template
|
||||
- [requirements.txt](requirements.txt) - Python dependencies
|
||||
- [package.json](package.json) - Node.js dependencies
|
||||
@@ -1,149 +0,0 @@
|
||||
# DSS Configuration Template
|
||||
# Copy this file to config.yaml and fill in your values
|
||||
|
||||
# ==========================================
|
||||
# Project Information
|
||||
# ==========================================
|
||||
project:
|
||||
name: "Your Project Name"
|
||||
version: "1.0.0"
|
||||
description: "Your design system project"
|
||||
url: "https://your-domain.com"
|
||||
|
||||
# ==========================================
|
||||
# API Configuration
|
||||
# ==========================================
|
||||
api:
|
||||
# Anthropic Claude API
|
||||
anthropic:
|
||||
api_key: "" # Get from: https://console.anthropic.com/settings/keys
|
||||
model: "claude-sonnet-4-5-20250929"
|
||||
max_tokens: 4096
|
||||
|
||||
# Figma API
|
||||
figma:
|
||||
token: "" # Get from: https://www.figma.com/developers/api#access-tokens
|
||||
file_key: "" # From Figma file URL: figma.com/file/{FILE_KEY}/...
|
||||
use_cache: true
|
||||
cache_ttl_seconds: 300 # 5 minutes
|
||||
|
||||
# OpenAI (Optional)
|
||||
openai:
|
||||
api_key: ""
|
||||
model: "gpt-4"
|
||||
|
||||
# ==========================================
|
||||
# Server Configuration
|
||||
# ==========================================
|
||||
server:
|
||||
host: "127.0.0.1"
|
||||
port: 3456
|
||||
mcp_port: 3457
|
||||
reload: true # Auto-reload on code changes (development only)
|
||||
cors_origins:
|
||||
- "http://localhost:3000"
|
||||
- "http://localhost:8080"
|
||||
|
||||
# ==========================================
|
||||
# Database Configuration
|
||||
# ==========================================
|
||||
database:
|
||||
path: "~/.dss/dss.db"
|
||||
backup_path: "~/.dss/backups/"
|
||||
auto_backup: true
|
||||
|
||||
# ==========================================
|
||||
# Theme Configuration
|
||||
# ==========================================
|
||||
themes:
|
||||
default_light: "DSS Light"
|
||||
default_dark: "DSS Dark"
|
||||
custom_themes_dir: "themes/"
|
||||
|
||||
# ==========================================
|
||||
# Style Dictionary Configuration
|
||||
# ==========================================
|
||||
style_dictionary:
|
||||
output_formats:
|
||||
- "css"
|
||||
- "scss"
|
||||
- "json"
|
||||
build_path: "dist/tokens/"
|
||||
platforms:
|
||||
- name: "css"
|
||||
transformGroup: "css"
|
||||
files:
|
||||
- destination: "variables.css"
|
||||
format: "css/variables"
|
||||
- name: "scss"
|
||||
transformGroup: "scss"
|
||||
files:
|
||||
- destination: "variables.scss"
|
||||
format: "scss/variables"
|
||||
|
||||
# ==========================================
|
||||
# Component Configuration
|
||||
# ==========================================
|
||||
components:
|
||||
# shadcn/ui
|
||||
shadcn:
|
||||
enabled: true
|
||||
components_dir: "components/"
|
||||
registry_url: "https://ui.shadcn.com/registry"
|
||||
|
||||
# HeroUI
|
||||
heroui:
|
||||
enabled: true
|
||||
theme_mapping: "heroui_to_shadcn"
|
||||
|
||||
# ==========================================
|
||||
# Testing Configuration
|
||||
# ==========================================
|
||||
testing:
|
||||
use_mock_apis: true
|
||||
test_db_path: "~/.dss/test.db"
|
||||
coverage_threshold: 80 # Minimum % coverage required
|
||||
markers:
|
||||
- "unit"
|
||||
- "integration"
|
||||
- "e2e"
|
||||
- "slow"
|
||||
|
||||
# ==========================================
|
||||
# Logging Configuration
|
||||
# ==========================================
|
||||
logging:
|
||||
level: "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
file: "~/.dss/logs/dss.log"
|
||||
max_bytes: 10485760 # 10MB
|
||||
backup_count: 5
|
||||
|
||||
# ==========================================
|
||||
# Cache Configuration
|
||||
# ==========================================
|
||||
cache:
|
||||
enabled: true
|
||||
dir: "~/.dss/cache/"
|
||||
ttl_seconds: 3600 # 1 hour default
|
||||
max_size_mb: 100
|
||||
|
||||
# ==========================================
|
||||
# Security Configuration
|
||||
# ==========================================
|
||||
security:
|
||||
jwt_secret: "" # Generate with: openssl rand -hex 32
|
||||
jwt_algorithm: "HS256"
|
||||
access_token_expire_minutes: 30
|
||||
allowed_origins:
|
||||
- "http://localhost:3000"
|
||||
|
||||
# ==========================================
|
||||
# Feature Flags
|
||||
# ==========================================
|
||||
features:
|
||||
figma_sync: true
|
||||
ai_chat: true
|
||||
component_library: true
|
||||
theme_switching: true
|
||||
analytics: false
|
||||
@@ -1,14 +0,0 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
-v
|
||||
--tb=short
|
||||
--strict-markers
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
e2e: End-to-end tests
|
||||
slow: Slow tests that require external tools
|
||||
@@ -1,20 +0,0 @@
|
||||
# DSS MVP1 Dependencies
|
||||
|
||||
# Core framework
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.27.0
|
||||
pydantic==2.6.0
|
||||
pydantic-settings==2.1.0
|
||||
|
||||
# Database
|
||||
sqlalchemy==2.0.25
|
||||
|
||||
# Testing
|
||||
pytest==7.4.4
|
||||
pytest-cov==4.1.0
|
||||
pytest-asyncio==0.23.3
|
||||
|
||||
# Utilities
|
||||
python-dotenv==1.0.0
|
||||
httpx==0.26.0
|
||||
requests==2.31.0
|
||||
@@ -1 +0,0 @@
|
||||
"""DSS MVP1 test suite"""
|
||||
@@ -1,69 +0,0 @@
|
||||
"""pytest configuration and fixtures"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fixtures_dir():
|
||||
"""Return path to fixtures directory"""
|
||||
return Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_project_data(fixtures_dir):
|
||||
"""Load valid project JSON fixture"""
|
||||
with open(fixtures_dir / "valid_project.json") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def heroui_theme_data(fixtures_dir):
|
||||
"""Load HeroUI theme JSON fixture"""
|
||||
with open(fixtures_dir / "heroui_theme.json") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def shadcn_button_data(fixtures_dir):
|
||||
"""Load shadcn button component fixture"""
|
||||
with open(fixtures_dir / "shadcn_button.json") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_keys_data(fixtures_dir):
|
||||
"""Load API keys fixture with mock keys for testing"""
|
||||
with open(fixtures_dir / "api_keys.json") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_anthropic_key(api_keys_data):
|
||||
"""Get mock Anthropic API key for testing"""
|
||||
return api_keys_data["anthropic"]["mock_api_key"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_figma_token(api_keys_data):
|
||||
"""Get mock Figma token for testing"""
|
||||
return api_keys_data["figma"]["mock_token"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_figma_file_key(api_keys_data):
|
||||
"""Get mock Figma file key for testing"""
|
||||
return api_keys_data["figma"]["mock_file_key"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_figma_response(api_keys_data):
|
||||
"""Get mock Figma API response for testing"""
|
||||
return api_keys_data["mock_responses"]["figma"]["variables_response"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_claude_response(api_keys_data):
|
||||
"""Get mock Claude API response for testing"""
|
||||
return api_keys_data["mock_responses"]["claude"]["simple_response"]
|
||||
109
dss-mvp1/tests/fixtures/api_keys.json
vendored
109
dss-mvp1/tests/fixtures/api_keys.json
vendored
@@ -1,109 +0,0 @@
|
||||
{
|
||||
"description": "Mock API keys for testing - DO NOT USE IN PRODUCTION",
|
||||
"note": "These are example keys for testing only. Replace with real keys in .env for production use.",
|
||||
|
||||
"anthropic": {
|
||||
"mock_api_key": "sk-ant-api03-test-mock-key-for-testing-only-do-not-use-in-production-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"api_base": "https://api.anthropic.com/v1",
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"max_tokens": 4096
|
||||
},
|
||||
|
||||
"figma": {
|
||||
"mock_token": "figd_test_mock_token_for_testing_only_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"mock_file_key": "aBcDeFgHiJkLmNoPqRsTuV",
|
||||
"api_base": "https://api.figma.com/v1"
|
||||
},
|
||||
|
||||
"openai": {
|
||||
"mock_api_key": "sk-test-mock-openai-key-for-testing-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"api_base": "https://api.openai.com/v1",
|
||||
"model": "gpt-4"
|
||||
},
|
||||
|
||||
"mock_responses": {
|
||||
"claude": {
|
||||
"simple_response": {
|
||||
"id": "msg_01ABC123",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "This is a mock Claude response for testing."
|
||||
}
|
||||
],
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"stop_reason": "end_turn",
|
||||
"usage": {
|
||||
"input_tokens": 10,
|
||||
"output_tokens": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"figma": {
|
||||
"variables_response": {
|
||||
"status": 200,
|
||||
"meta": {
|
||||
"variableCollections": {
|
||||
"VariableCollectionId:1:1": {
|
||||
"id": "VariableCollectionId:1:1",
|
||||
"name": "Colors",
|
||||
"modes": [
|
||||
{
|
||||
"modeId": "1:0",
|
||||
"name": "Light"
|
||||
},
|
||||
{
|
||||
"modeId": "1:1",
|
||||
"name": "Dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"VariableID:1:2": {
|
||||
"id": "VariableID:1:2",
|
||||
"name": "colors/primary",
|
||||
"key": "colors_primary",
|
||||
"variableCollectionId": "VariableCollectionId:1:1",
|
||||
"resolvedType": "COLOR",
|
||||
"valuesByMode": {
|
||||
"1:0": {
|
||||
"r": 0.0,
|
||||
"g": 0.4,
|
||||
"b": 0.8,
|
||||
"a": 1.0
|
||||
},
|
||||
"1:1": {
|
||||
"r": 0.4,
|
||||
"g": 0.6,
|
||||
"b": 1.0,
|
||||
"a": 1.0
|
||||
}
|
||||
},
|
||||
"description": "Primary brand color",
|
||||
"remote": false,
|
||||
"hiddenFromPublishing": false
|
||||
},
|
||||
"VariableID:1:3": {
|
||||
"id": "VariableID:1:3",
|
||||
"name": "spacing/base",
|
||||
"key": "spacing_base",
|
||||
"variableCollectionId": "VariableCollectionId:1:1",
|
||||
"resolvedType": "FLOAT",
|
||||
"valuesByMode": {
|
||||
"1:0": 16,
|
||||
"1:1": 16
|
||||
},
|
||||
"description": "Base spacing unit",
|
||||
"remote": false,
|
||||
"hiddenFromPublishing": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
83
dss-mvp1/tests/fixtures/valid_project.json
vendored
83
dss-mvp1/tests/fixtures/valid_project.json
vendored
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"id": "heroui-ds-test",
|
||||
"name": "HeroUI Design System",
|
||||
"version": "1.0.0",
|
||||
"description": "A modern design system based on HeroUI with shadcn components",
|
||||
"theme": {
|
||||
"name": "HeroUI Theme",
|
||||
"version": "1.0.0",
|
||||
"tokens": {
|
||||
"primary": {
|
||||
"name": "primary",
|
||||
"value": "oklch(0.65 0.18 250)",
|
||||
"type": "color",
|
||||
"category": "color",
|
||||
"description": "Primary brand color"
|
||||
},
|
||||
"secondary": {
|
||||
"name": "secondary",
|
||||
"value": "oklch(0.55 0.05 285)",
|
||||
"type": "color",
|
||||
"category": "color",
|
||||
"description": "Secondary brand color"
|
||||
},
|
||||
"space-sm": {
|
||||
"name": "space-sm",
|
||||
"value": "8px",
|
||||
"type": "dimension",
|
||||
"category": "spacing",
|
||||
"description": "Small spacing unit"
|
||||
},
|
||||
"space-md": {
|
||||
"name": "space-md",
|
||||
"value": "16px",
|
||||
"type": "dimension",
|
||||
"category": "spacing",
|
||||
"description": "Medium spacing unit"
|
||||
},
|
||||
"space-lg": {
|
||||
"name": "space-lg",
|
||||
"value": "24px",
|
||||
"type": "dimension",
|
||||
"category": "spacing",
|
||||
"description": "Large spacing unit"
|
||||
},
|
||||
"radius": {
|
||||
"name": "radius",
|
||||
"value": "12px",
|
||||
"type": "dimension",
|
||||
"category": "radius",
|
||||
"description": "Default border radius"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Button",
|
||||
"source": "shadcn",
|
||||
"description": "Primary action button with multiple variants",
|
||||
"variants": ["default", "outline", "ghost", "destructive"],
|
||||
"props": {
|
||||
"variant": "string",
|
||||
"size": "string",
|
||||
"disabled": "boolean"
|
||||
},
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "Card",
|
||||
"source": "shadcn",
|
||||
"description": "Content container card",
|
||||
"variants": [],
|
||||
"props": {
|
||||
"className": "string"
|
||||
},
|
||||
"dependencies": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"author": "Test User",
|
||||
"team": "QA",
|
||||
"tags": ["heroui", "shadcn", "test"]
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
"""Integration tests for Figma wrapper using mock API responses"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from dss.tools.figma import FigmaWrapper, FigmaAPIError
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestFigmaWrapperWithMocks:
|
||||
"""Test Figma wrapper with mocked API responses"""
|
||||
|
||||
def test_init_with_mock_credentials(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test initializing FigmaWrapper with mock credentials"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
assert wrapper.api_token == mock_figma_token
|
||||
assert wrapper.file_key == mock_figma_file_key
|
||||
assert wrapper.headers["X-Figma-Token"] == mock_figma_token
|
||||
|
||||
def test_extract_themes_with_mock_response(
|
||||
self,
|
||||
mock_figma_token,
|
||||
mock_figma_file_key,
|
||||
mock_figma_response
|
||||
):
|
||||
"""Test extracting themes from mock Figma response"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
# Mock the API call
|
||||
with patch.object(wrapper, 'get_variables', return_value=mock_figma_response):
|
||||
themes = wrapper.extract_themes()
|
||||
|
||||
# Should extract Light and Dark themes
|
||||
assert "Light" in themes
|
||||
assert "Dark" in themes
|
||||
|
||||
# Check Light theme has tokens
|
||||
light_theme = themes["Light"]
|
||||
assert light_theme.name == "DSS Light"
|
||||
assert len(light_theme.tokens) > 0
|
||||
|
||||
# Check Dark theme has tokens
|
||||
dark_theme = themes["Dark"]
|
||||
assert dark_theme.name == "DSS Dark"
|
||||
assert len(dark_theme.tokens) > 0
|
||||
|
||||
def test_build_mode_map(self, mock_figma_token, mock_figma_file_key, mock_figma_response):
|
||||
"""Test building mode ID to theme name mapping"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
variable_collections = mock_figma_response["meta"]["variableCollections"]
|
||||
mode_map = wrapper._build_mode_map(variable_collections)
|
||||
|
||||
# Should map mode IDs to names
|
||||
assert "1:0" in mode_map
|
||||
assert mode_map["1:0"] == "Light"
|
||||
assert "1:1" in mode_map
|
||||
assert mode_map["1:1"] == "Dark"
|
||||
|
||||
def test_convert_figma_color_to_rgb(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test converting Figma color format to RGB"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
# Figma color format: {r: 0-1, g: 0-1, b: 0-1, a: 0-1}
|
||||
figma_color = {
|
||||
"r": 0.0,
|
||||
"g": 0.4,
|
||||
"b": 0.8,
|
||||
"a": 1.0
|
||||
}
|
||||
|
||||
rgb_string = wrapper._format_value(figma_color, "color")
|
||||
|
||||
# Should convert to rgb(0, 102, 204)
|
||||
assert "rgb(" in rgb_string
|
||||
assert "0" in rgb_string # Red component
|
||||
assert "102" in rgb_string # Green component
|
||||
assert "204" in rgb_string # Blue component
|
||||
|
||||
def test_handle_api_errors(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test handling Figma API errors"""
|
||||
import requests
|
||||
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
# Mock 403 Forbidden error
|
||||
with patch('requests.get') as mock_get:
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 403
|
||||
|
||||
# Properly simulate HTTPError
|
||||
http_error = requests.exceptions.HTTPError()
|
||||
http_error.response = mock_response
|
||||
mock_response.raise_for_status.side_effect = http_error
|
||||
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
with pytest.raises(FigmaAPIError) as exc_info:
|
||||
wrapper.get_variables()
|
||||
|
||||
assert "Invalid Figma API token" in str(exc_info.value) or "403" in str(exc_info.value)
|
||||
|
||||
def test_handle_404_not_found(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test handling file not found error"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
# Mock 404 Not Found error
|
||||
with patch('requests.get') as mock_get:
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 404
|
||||
mock_response.raise_for_status.side_effect = Exception("404 Not Found")
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
with pytest.raises(FigmaAPIError) as exc_info:
|
||||
wrapper.get_variables()
|
||||
|
||||
assert "not found" in str(exc_info.value).lower()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestFigmaTokenConversion:
|
||||
"""Test Figma token type conversions"""
|
||||
|
||||
def test_map_figma_type_to_dtcg(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test mapping Figma types to DTCG types"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
assert wrapper._map_figma_type_to_dtcg("COLOR") == "color"
|
||||
assert wrapper._map_figma_type_to_dtcg("FLOAT") == "number"
|
||||
assert wrapper._map_figma_type_to_dtcg("STRING") == "string"
|
||||
assert wrapper._map_figma_type_to_dtcg("BOOLEAN") == "boolean"
|
||||
assert wrapper._map_figma_type_to_dtcg("UNKNOWN") == "other"
|
||||
|
||||
def test_map_dtcg_type_to_category(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test mapping DTCG types to DSS categories"""
|
||||
from dss.models.theme import TokenCategory
|
||||
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
assert wrapper._map_dtcg_type_to_category("color") == TokenCategory.COLOR
|
||||
assert wrapper._map_dtcg_type_to_category("dimension") == TokenCategory.SPACING
|
||||
assert wrapper._map_dtcg_type_to_category("fontSize") == TokenCategory.TYPOGRAPHY
|
||||
assert wrapper._map_dtcg_type_to_category("shadow") == TokenCategory.SHADOW
|
||||
assert wrapper._map_dtcg_type_to_category("unknown") == TokenCategory.OTHER
|
||||
@@ -1,129 +0,0 @@
|
||||
"""Integration tests for Style Dictionary wrapper"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from dss.tools.style_dictionary import StyleDictionaryWrapper
|
||||
from dss.themes import get_default_light_theme
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestStyleDictionaryIntegration:
|
||||
"""Test Style Dictionary integration"""
|
||||
|
||||
def test_convert_tokens_to_css_vars(self):
|
||||
"""Test converting DSS theme to CSS custom properties"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css_output = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Check that CSS output is valid
|
||||
assert ":root {" in css_output
|
||||
assert "--background:" in css_output
|
||||
assert "--primary:" in css_output
|
||||
assert "--space-md:" in css_output
|
||||
assert "}" in css_output
|
||||
|
||||
def test_convert_theme_to_sd_format(self):
|
||||
"""Test converting DSS theme to Style Dictionary format"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
sd_format = sd._convert_theme_to_sd_format(theme)
|
||||
|
||||
# Check structure
|
||||
assert "color" in sd_format
|
||||
assert "spacing" in sd_format
|
||||
assert "radius" in sd_format
|
||||
assert "typography" in sd_format
|
||||
|
||||
# Check color tokens
|
||||
assert "background" in sd_format["color"]
|
||||
assert "primary" in sd_format["color"]
|
||||
assert sd_format["color"]["primary"]["value"] == "oklch(0.65 0.18 250)"
|
||||
|
||||
# Check spacing tokens
|
||||
assert "space-md" in sd_format["spacing"]
|
||||
assert sd_format["spacing"]["space-md"]["value"] == "16px"
|
||||
|
||||
def test_create_sd_config_css(self):
|
||||
"""Test creating Style Dictionary config for CSS output"""
|
||||
sd = StyleDictionaryWrapper()
|
||||
build_path = Path("/tmp/test")
|
||||
|
||||
config = sd._create_sd_config("css", build_path)
|
||||
|
||||
assert "source" in config
|
||||
assert "platforms" in config
|
||||
assert "css" in config["platforms"]
|
||||
assert config["platforms"]["css"]["transformGroup"] == "css"
|
||||
assert config["platforms"]["css"]["files"][0]["format"] == "css/variables"
|
||||
|
||||
def test_create_sd_config_scss(self):
|
||||
"""Test creating Style Dictionary config for SCSS output"""
|
||||
sd = StyleDictionaryWrapper()
|
||||
build_path = Path("/tmp/test")
|
||||
|
||||
config = sd._create_sd_config("scss", build_path)
|
||||
|
||||
assert "scss" in config["platforms"]
|
||||
assert config["platforms"]["scss"]["transformGroup"] == "scss"
|
||||
assert config["platforms"]["scss"]["files"][0]["format"] == "scss/variables"
|
||||
|
||||
def test_create_sd_config_json(self):
|
||||
"""Test creating Style Dictionary config for JSON output"""
|
||||
sd = StyleDictionaryWrapper()
|
||||
build_path = Path("/tmp/test")
|
||||
|
||||
config = sd._create_sd_config("json", build_path)
|
||||
|
||||
assert "json" in config["platforms"]
|
||||
assert config["platforms"]["json"]["files"][0]["format"] == "json/nested"
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_transform_theme_to_css(self):
|
||||
"""Test full transformation to CSS (requires npm)"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
result = sd.transform_theme(theme, output_format="css")
|
||||
|
||||
# Check result structure
|
||||
assert "success" in result
|
||||
assert "output_format" in result
|
||||
assert result["output_format"] == "css"
|
||||
|
||||
# If style-dictionary is installed, check output
|
||||
if result["success"]:
|
||||
assert "files" in result
|
||||
assert "theme.css" in result["files"]
|
||||
css_content = result["files"]["theme.css"]
|
||||
assert "--" in css_content # CSS variables
|
||||
|
||||
def test_css_var_naming_convention(self):
|
||||
"""Test that CSS variable names follow kebab-case convention"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css_output = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Check naming conventions
|
||||
assert "--space-md:" in css_output
|
||||
assert "--radius-sm:" in css_output
|
||||
assert "--text-base:" in css_output
|
||||
|
||||
# Should not have camelCase or underscores
|
||||
assert "spacemd" not in css_output.lower()
|
||||
assert "space_md" not in css_output
|
||||
|
||||
def test_css_output_includes_comments(self):
|
||||
"""Test that CSS output includes token descriptions as comments"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css_output = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Check for comments
|
||||
assert "/*" in css_output
|
||||
assert "Main background color" in css_output
|
||||
assert "Primary brand color" in css_output
|
||||
@@ -1 +0,0 @@
|
||||
"""Unit tests for DSS models and validators"""
|
||||
@@ -1,359 +0,0 @@
|
||||
"""Edge case tests to discover bugs in DSS MVP1"""
|
||||
|
||||
import pytest
|
||||
from dss.models.theme import Theme, DesignToken, TokenCategory
|
||||
from dss.models.project import Project
|
||||
from dss.validators.schema import ProjectValidator
|
||||
from dss.tools.style_dictionary import StyleDictionaryWrapper
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestThemeEdgeCases:
|
||||
"""Test edge cases in Theme model"""
|
||||
|
||||
def test_empty_theme(self):
|
||||
"""Test theme with no tokens"""
|
||||
theme = Theme(name="Empty Theme")
|
||||
assert len(theme.tokens) == 0
|
||||
assert theme.get_tokens_by_category(TokenCategory.COLOR) == {}
|
||||
|
||||
def test_theme_with_invalid_oklch_values(self):
|
||||
"""Test theme with out-of-range OKLCH values"""
|
||||
# OKLCH: L (0-1), C (0-0.4), H (0-360)
|
||||
invalid_tokens = {
|
||||
"invalid-lightness": DesignToken(
|
||||
name="invalid-lightness",
|
||||
value="oklch(1.5 0.18 250)", # L > 1
|
||||
type="color",
|
||||
category=TokenCategory.COLOR,
|
||||
description="Invalid lightness"
|
||||
),
|
||||
"invalid-chroma": DesignToken(
|
||||
name="invalid-chroma",
|
||||
value="oklch(0.65 0.8 250)", # C > 0.4
|
||||
type="color",
|
||||
category=TokenCategory.COLOR,
|
||||
description="Invalid chroma"
|
||||
),
|
||||
"invalid-hue": DesignToken(
|
||||
name="invalid-hue",
|
||||
value="oklch(0.65 0.18 450)", # H > 360
|
||||
type="color",
|
||||
category=TokenCategory.COLOR,
|
||||
description="Invalid hue"
|
||||
)
|
||||
}
|
||||
|
||||
# Theme should accept these (validation happens elsewhere)
|
||||
theme = Theme(name="Invalid OKLCH", tokens=invalid_tokens)
|
||||
assert len(theme.tokens) == 3
|
||||
|
||||
def test_circular_token_references(self):
|
||||
"""Test themes with circular token references"""
|
||||
tokens = {
|
||||
"primary": DesignToken(
|
||||
name="primary",
|
||||
value="{secondary}",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
),
|
||||
"secondary": DesignToken(
|
||||
name="secondary",
|
||||
value="{primary}",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
)
|
||||
}
|
||||
|
||||
theme = Theme(name="Circular Refs", tokens=tokens)
|
||||
# Should detect circular references during validation
|
||||
validator = ProjectValidator()
|
||||
project_data = {
|
||||
"id": "circular-test",
|
||||
"name": "Circular Test",
|
||||
"theme": {
|
||||
"name": "Circular Refs",
|
||||
"tokens": {
|
||||
"primary": {
|
||||
"name": "primary",
|
||||
"value": "{secondary}",
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"name": "secondary",
|
||||
"value": "{primary}",
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Validator should handle this gracefully
|
||||
result = validator.validate(project_data)
|
||||
# Currently doesn't detect circular refs - potential bug!
|
||||
assert result.is_valid or not result.is_valid # Either is acceptable for now
|
||||
|
||||
def test_deeply_nested_token_references(self):
|
||||
"""Test deeply nested token references"""
|
||||
tokens = {
|
||||
"base": DesignToken(
|
||||
name="base",
|
||||
value="oklch(0.65 0.18 250)",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
),
|
||||
"level1": DesignToken(
|
||||
name="level1",
|
||||
value="{base}",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
),
|
||||
"level2": DesignToken(
|
||||
name="level2",
|
||||
value="{level1}",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
),
|
||||
"level3": DesignToken(
|
||||
name="level3",
|
||||
value="{level2}",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
)
|
||||
}
|
||||
|
||||
theme = Theme(name="Deep Nesting", tokens=tokens)
|
||||
assert len(theme.tokens) == 4
|
||||
|
||||
def test_unicode_in_token_names(self):
|
||||
"""Test tokens with unicode characters"""
|
||||
theme = Theme(
|
||||
name="Unicode Theme 🎨",
|
||||
tokens={
|
||||
"couleur-primaire": DesignToken(
|
||||
name="couleur-primaire",
|
||||
value="oklch(0.65 0.18 250)",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR,
|
||||
description="Couleur principale 🇫🇷"
|
||||
)
|
||||
}
|
||||
)
|
||||
assert len(theme.tokens) == 1
|
||||
|
||||
def test_extremely_long_token_values(self):
|
||||
"""Test tokens with very long values"""
|
||||
long_value = "oklch(0.65 0.18 250)" * 100 # Very long value
|
||||
|
||||
token = DesignToken(
|
||||
name="long-value",
|
||||
value=long_value,
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
)
|
||||
|
||||
assert len(token.value) > 1000
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidationEdgeCases:
|
||||
"""Test edge cases in validation pipeline"""
|
||||
|
||||
def test_validate_empty_project(self):
|
||||
"""Test validating completely empty project data"""
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate({})
|
||||
|
||||
assert result.is_valid is False
|
||||
assert len(result.errors) > 0
|
||||
|
||||
def test_validate_project_with_null_values(self):
|
||||
"""Test project with null/None values"""
|
||||
data = {
|
||||
"id": None,
|
||||
"name": None,
|
||||
"theme": None
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
|
||||
def test_validate_malformed_json(self):
|
||||
"""Test with malformed data types"""
|
||||
data = {
|
||||
"id": 12345, # Should be string
|
||||
"name": ["array", "instead", "of", "string"],
|
||||
"theme": "string instead of object"
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
|
||||
def test_validate_sql_injection_attempt(self):
|
||||
"""Test that validator handles SQL injection attempts safely"""
|
||||
data = {
|
||||
"id": "test'; DROP TABLE projects; --",
|
||||
"name": "<script>alert('xss')</script>",
|
||||
"theme": {
|
||||
"name": "Malicious Theme",
|
||||
"tokens": {}
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
# Should validate structure, content sanitization happens elsewhere
|
||||
assert result.is_valid is True or result.is_valid is False # Either is ok
|
||||
|
||||
def test_validate_extremely_large_project(self):
|
||||
"""Test validation with extremely large number of tokens"""
|
||||
# Create 1000 tokens
|
||||
tokens = {}
|
||||
for i in range(1000):
|
||||
tokens[f"token-{i}"] = {
|
||||
"name": f"token-{i}",
|
||||
"value": f"oklch(0.{i % 100} 0.18 {i % 360})",
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
}
|
||||
|
||||
data = {
|
||||
"id": "large-project",
|
||||
"name": "Large Project",
|
||||
"theme": {
|
||||
"name": "Large Theme",
|
||||
"tokens": tokens
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
# Should handle large datasets
|
||||
assert result.is_valid is True
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestStyleDictionaryEdgeCases:
|
||||
"""Test edge cases in Style Dictionary wrapper"""
|
||||
|
||||
def test_convert_empty_theme_to_css(self):
|
||||
"""Test converting empty theme to CSS"""
|
||||
theme = Theme(name="Empty")
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
assert ":root {" in css
|
||||
assert "}" in css
|
||||
|
||||
def test_convert_theme_with_special_characters(self):
|
||||
"""Test tokens with special characters in names"""
|
||||
theme = Theme(
|
||||
name="Special Chars",
|
||||
tokens={
|
||||
"color/primary/500": DesignToken(
|
||||
name="color/primary/500",
|
||||
value="oklch(0.65 0.18 250)",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
sd = StyleDictionaryWrapper()
|
||||
css = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Should convert slashes to hyphens or handle specially
|
||||
assert "--color" in css or "--color/primary/500" in css
|
||||
|
||||
def test_sd_format_conversion_with_empty_values(self):
|
||||
"""Test SD format conversion with empty token values"""
|
||||
theme = Theme(
|
||||
name="Empty Values",
|
||||
tokens={
|
||||
"empty": DesignToken(
|
||||
name="empty",
|
||||
value="", # Empty value
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
sd = StyleDictionaryWrapper()
|
||||
sd_format = sd._convert_theme_to_sd_format(theme)
|
||||
|
||||
assert "color" in sd_format
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestComponentEdgeCases:
|
||||
"""Test edge cases in Component model"""
|
||||
|
||||
def test_component_with_circular_dependencies(self):
|
||||
"""Test components with circular dependencies"""
|
||||
from dss.models.component import Component
|
||||
|
||||
# This would create circular dependency:
|
||||
# Card depends on Button
|
||||
# Button depends on Card
|
||||
project_data = {
|
||||
"id": "circular-deps",
|
||||
"name": "Circular Deps",
|
||||
"theme": {
|
||||
"name": "Test",
|
||||
"tokens": {}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Card",
|
||||
"source": "shadcn",
|
||||
"dependencies": ["Button"]
|
||||
},
|
||||
{
|
||||
"name": "Button",
|
||||
"source": "shadcn",
|
||||
"dependencies": ["Card"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(project_data)
|
||||
|
||||
# Should detect circular dependencies
|
||||
# Currently might not - potential bug!
|
||||
assert result.is_valid or not result.is_valid
|
||||
|
||||
def test_component_with_missing_dependencies(self):
|
||||
"""Test component referencing non-existent dependency"""
|
||||
project_data = {
|
||||
"id": "missing-dep",
|
||||
"name": "Missing Dep",
|
||||
"theme": {
|
||||
"name": "Test",
|
||||
"tokens": {}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Card",
|
||||
"source": "shadcn",
|
||||
"dependencies": ["NonexistentComponent"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(project_data)
|
||||
|
||||
# Should catch missing dependency
|
||||
assert result.is_valid is False
|
||||
assert any("dependency" in str(err).lower() for err in result.errors)
|
||||
@@ -1,181 +0,0 @@
|
||||
"""Unit tests for Pydantic models"""
|
||||
|
||||
import pytest
|
||||
from dss.models import Project, Component, Theme, DesignToken, TokenCategory
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDesignToken:
|
||||
"""Test DesignToken model"""
|
||||
|
||||
def test_create_color_token(self):
|
||||
"""Test creating a color token"""
|
||||
token = DesignToken(
|
||||
name="primary",
|
||||
value="oklch(0.65 0.18 250)",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR,
|
||||
description="Primary brand color"
|
||||
)
|
||||
assert token.name == "primary"
|
||||
assert token.category == TokenCategory.COLOR
|
||||
assert "oklch" in token.value
|
||||
|
||||
def test_create_spacing_token(self):
|
||||
"""Test creating a spacing token"""
|
||||
token = DesignToken(
|
||||
name="space-md",
|
||||
value="16px",
|
||||
type="dimension",
|
||||
category=TokenCategory.SPACING
|
||||
)
|
||||
assert token.name == "space-md"
|
||||
assert token.value == "16px"
|
||||
assert token.category == TokenCategory.SPACING
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestTheme:
|
||||
"""Test Theme model"""
|
||||
|
||||
def test_create_empty_theme(self):
|
||||
"""Test creating an empty theme"""
|
||||
theme = Theme(name="Test Theme")
|
||||
assert theme.name == "Test Theme"
|
||||
assert theme.version == "1.0.0"
|
||||
assert len(theme.tokens) == 0
|
||||
|
||||
def test_create_theme_with_tokens(self):
|
||||
"""Test creating a theme with tokens"""
|
||||
tokens = {
|
||||
"primary": DesignToken(
|
||||
name="primary",
|
||||
value="oklch(0.65 0.18 250)",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
),
|
||||
"space-md": DesignToken(
|
||||
name="space-md",
|
||||
value="16px",
|
||||
type="dimension",
|
||||
category=TokenCategory.SPACING
|
||||
)
|
||||
}
|
||||
theme = Theme(name="Test Theme", tokens=tokens)
|
||||
assert len(theme.tokens) == 2
|
||||
assert "primary" in theme.tokens
|
||||
|
||||
def test_get_tokens_by_category(self):
|
||||
"""Test filtering tokens by category"""
|
||||
tokens = {
|
||||
"primary": DesignToken(
|
||||
name="primary",
|
||||
value="oklch(0.65 0.18 250)",
|
||||
type="color",
|
||||
category=TokenCategory.COLOR
|
||||
),
|
||||
"space-md": DesignToken(
|
||||
name="space-md",
|
||||
value="16px",
|
||||
type="dimension",
|
||||
category=TokenCategory.SPACING
|
||||
)
|
||||
}
|
||||
theme = Theme(name="Test Theme", tokens=tokens)
|
||||
|
||||
color_tokens = theme.get_tokens_by_category(TokenCategory.COLOR)
|
||||
assert len(color_tokens) == 1
|
||||
assert "primary" in color_tokens
|
||||
|
||||
spacing_tokens = theme.get_tokens_by_category(TokenCategory.SPACING)
|
||||
assert len(spacing_tokens) == 1
|
||||
assert "space-md" in spacing_tokens
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestComponent:
|
||||
"""Test Component model"""
|
||||
|
||||
def test_create_basic_component(self):
|
||||
"""Test creating a basic component"""
|
||||
component = Component(
|
||||
name="Button",
|
||||
source="shadcn",
|
||||
description="Primary action button"
|
||||
)
|
||||
assert component.name == "Button"
|
||||
assert component.source == "shadcn"
|
||||
assert len(component.variants) == 0
|
||||
assert len(component.dependencies) == 0
|
||||
|
||||
def test_create_component_with_variants(self):
|
||||
"""Test creating a component with variants"""
|
||||
component = Component(
|
||||
name="Button",
|
||||
source="shadcn",
|
||||
variants=["default", "outline", "ghost"]
|
||||
)
|
||||
assert len(component.variants) == 3
|
||||
assert "outline" in component.variants
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestProject:
|
||||
"""Test Project model"""
|
||||
|
||||
def test_create_minimal_project(self):
|
||||
"""Test creating a minimal project"""
|
||||
theme = Theme(name="Test Theme")
|
||||
project = Project(
|
||||
id="test-project",
|
||||
name="Test Project",
|
||||
theme=theme
|
||||
)
|
||||
assert project.id == "test-project"
|
||||
assert project.name == "Test Project"
|
||||
assert project.version == "1.0.0"
|
||||
assert len(project.components) == 0
|
||||
|
||||
def test_create_project_with_components(self):
|
||||
"""Test creating a project with components"""
|
||||
theme = Theme(name="Test Theme")
|
||||
components = [
|
||||
Component(name="Button", source="shadcn"),
|
||||
Component(name="Card", source="shadcn")
|
||||
]
|
||||
project = Project(
|
||||
id="test-project",
|
||||
name="Test Project",
|
||||
theme=theme,
|
||||
components=components
|
||||
)
|
||||
assert len(project.components) == 2
|
||||
|
||||
def test_get_component_by_name(self):
|
||||
"""Test retrieving a component by name"""
|
||||
theme = Theme(name="Test Theme")
|
||||
components = [
|
||||
Component(name="Button", source="shadcn"),
|
||||
Component(name="Card", source="shadcn")
|
||||
]
|
||||
project = Project(
|
||||
id="test-project",
|
||||
name="Test Project",
|
||||
theme=theme,
|
||||
components=components
|
||||
)
|
||||
|
||||
button = project.get_component("Button")
|
||||
assert button is not None
|
||||
assert button.name == "Button"
|
||||
|
||||
nonexistent = project.get_component("NonExistent")
|
||||
assert nonexistent is None
|
||||
|
||||
def test_project_from_fixture(self, valid_project_data):
|
||||
"""Test creating project from fixture data"""
|
||||
project = Project(**valid_project_data)
|
||||
assert project.id == "heroui-ds-test"
|
||||
assert project.name == "HeroUI Design System"
|
||||
assert len(project.components) == 2
|
||||
assert len(project.theme.tokens) == 6
|
||||
@@ -1,85 +0,0 @@
|
||||
"""Unit tests for default DSS themes"""
|
||||
|
||||
import pytest
|
||||
from dss.themes import get_default_light_theme, get_default_dark_theme
|
||||
from dss.models.theme import TokenCategory
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDefaultThemes:
|
||||
"""Test default light & dark themes"""
|
||||
|
||||
def test_light_theme_structure(self):
|
||||
"""Test light theme has correct structure"""
|
||||
theme = get_default_light_theme()
|
||||
assert theme.name == "DSS Light"
|
||||
assert theme.version == "1.0.0"
|
||||
assert len(theme.tokens) > 20
|
||||
|
||||
def test_dark_theme_structure(self):
|
||||
"""Test dark theme has correct structure"""
|
||||
theme = get_default_dark_theme()
|
||||
assert theme.name == "DSS Dark"
|
||||
assert theme.version == "1.0.0"
|
||||
assert len(theme.tokens) > 20
|
||||
|
||||
def test_themes_have_same_token_names(self):
|
||||
"""Test light and dark themes have matching token names"""
|
||||
light = get_default_light_theme()
|
||||
dark = get_default_dark_theme()
|
||||
|
||||
light_tokens = set(light.tokens.keys())
|
||||
dark_tokens = set(dark.tokens.keys())
|
||||
|
||||
assert light_tokens == dark_tokens, "Light and dark themes must have same token names"
|
||||
|
||||
def test_color_tokens_present(self):
|
||||
"""Test essential color tokens are present"""
|
||||
theme = get_default_light_theme()
|
||||
required_colors = ["background", "foreground", "primary", "secondary",
|
||||
"accent", "destructive", "success", "warning"]
|
||||
|
||||
for color in required_colors:
|
||||
assert color in theme.tokens
|
||||
assert theme.tokens[color].category == TokenCategory.COLOR
|
||||
|
||||
def test_spacing_tokens_present(self):
|
||||
"""Test spacing tokens are present"""
|
||||
theme = get_default_light_theme()
|
||||
spacing_tokens = theme.get_tokens_by_category(TokenCategory.SPACING)
|
||||
|
||||
assert len(spacing_tokens) == 5 # xs, sm, md, lg, xl
|
||||
assert "space-md" in spacing_tokens
|
||||
|
||||
def test_radius_tokens_present(self):
|
||||
"""Test border radius tokens are present"""
|
||||
theme = get_default_light_theme()
|
||||
radius_tokens = theme.get_tokens_by_category(TokenCategory.RADIUS)
|
||||
|
||||
assert len(radius_tokens) == 3 # sm, md, lg
|
||||
assert "radius-md" in radius_tokens
|
||||
|
||||
def test_typography_tokens_present(self):
|
||||
"""Test typography tokens are present"""
|
||||
theme = get_default_light_theme()
|
||||
typo_tokens = theme.get_tokens_by_category(TokenCategory.TYPOGRAPHY)
|
||||
|
||||
assert len(typo_tokens) == 5 # xs, sm, base, lg, xl
|
||||
assert "text-base" in typo_tokens
|
||||
|
||||
def test_dark_theme_colors_different_from_light(self):
|
||||
"""Test dark theme has different color values than light"""
|
||||
light = get_default_light_theme()
|
||||
dark = get_default_dark_theme()
|
||||
|
||||
# Background should be inverted
|
||||
assert light.tokens["background"].value != dark.tokens["background"].value
|
||||
assert light.tokens["foreground"].value != dark.tokens["foreground"].value
|
||||
|
||||
def test_theme_token_values_are_valid(self):
|
||||
"""Test all token values are non-empty strings"""
|
||||
theme = get_default_light_theme()
|
||||
|
||||
for token_name, token in theme.tokens.items():
|
||||
assert token.value, f"Token {token_name} has empty value"
|
||||
assert token.description, f"Token {token_name} has no description"
|
||||
@@ -1,265 +0,0 @@
|
||||
"""Unit tests for validation pipeline"""
|
||||
|
||||
import pytest
|
||||
from dss.models.project import Project
|
||||
from dss.models.theme import Theme, DesignToken, TokenCategory
|
||||
from dss.validators.schema import ProjectValidator, ValidationError, ValidationStage
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestProjectValidator:
|
||||
"""Test project validation pipeline"""
|
||||
|
||||
def test_validate_valid_project(self, valid_project_data):
|
||||
"""Test validation passes for valid project"""
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(valid_project_data)
|
||||
|
||||
assert result.is_valid is True
|
||||
assert len(result.errors) == 0
|
||||
assert result.stage == ValidationStage.COMPLETE
|
||||
|
||||
def test_validate_missing_required_field(self):
|
||||
"""Test validation fails when required field is missing"""
|
||||
invalid_data = {
|
||||
"name": "Test Project",
|
||||
# Missing 'id' field
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {}
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(invalid_data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert len(result.errors) > 0
|
||||
assert any("id" in str(err).lower() for err in result.errors)
|
||||
|
||||
def test_validate_invalid_token_value(self):
|
||||
"""Test validation fails for invalid token values"""
|
||||
invalid_data = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {
|
||||
"primary": {
|
||||
"name": "primary",
|
||||
"value": "", # Empty value is invalid
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(invalid_data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert ValidationStage.TOKEN_VALIDATION in [err.stage for err in result.errors]
|
||||
|
||||
def test_validate_token_reference(self):
|
||||
"""Test validation of token references"""
|
||||
data_with_ref = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {
|
||||
"primary": {
|
||||
"name": "primary",
|
||||
"value": "oklch(0.65 0.18 250)",
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
},
|
||||
"primary-dark": {
|
||||
"name": "primary-dark",
|
||||
"value": "{primary}", # Reference to primary token
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data_with_ref)
|
||||
|
||||
# Should pass - valid reference
|
||||
assert result.is_valid is True
|
||||
|
||||
def test_validate_broken_token_reference(self):
|
||||
"""Test validation fails for broken token reference"""
|
||||
data_with_broken_ref = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {
|
||||
"primary": {
|
||||
"name": "primary",
|
||||
"value": "{nonexistent}", # Reference to nonexistent token
|
||||
"type": "color",
|
||||
"category": "color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data_with_broken_ref)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert any("reference" in str(err).lower() for err in result.errors)
|
||||
|
||||
def test_validate_component_dependencies(self):
|
||||
"""Test validation of component dependencies"""
|
||||
data = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Card",
|
||||
"source": "shadcn",
|
||||
"dependencies": ["Button"] # Depends on Button component
|
||||
},
|
||||
{
|
||||
"name": "Button",
|
||||
"source": "shadcn"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
# Should pass - Button exists
|
||||
assert result.is_valid is True
|
||||
|
||||
def test_validate_missing_component_dependency(self):
|
||||
"""Test validation fails for missing component dependency"""
|
||||
data = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Card",
|
||||
"source": "shadcn",
|
||||
"dependencies": ["NonexistentComponent"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert any("dependency" in str(err).lower() for err in result.errors)
|
||||
|
||||
def test_validation_stages_order(self):
|
||||
"""Test validation stages execute in correct order"""
|
||||
# Data that fails at schema stage
|
||||
invalid_schema = {"invalid": "structure"}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(invalid_schema)
|
||||
|
||||
# Should fail at schema stage and not proceed
|
||||
assert result.is_valid is False
|
||||
assert result.stage == ValidationStage.SCHEMA
|
||||
|
||||
def test_validate_token_category_enum(self):
|
||||
"""Test validation accepts valid token categories"""
|
||||
data = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {
|
||||
"space-md": {
|
||||
"name": "space-md",
|
||||
"value": "16px",
|
||||
"type": "dimension",
|
||||
"category": "spacing" # Valid category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
assert result.is_valid is True
|
||||
|
||||
def test_validate_component_variants(self):
|
||||
"""Test validation of component variants"""
|
||||
data = {
|
||||
"id": "test-project",
|
||||
"name": "Test Project",
|
||||
"theme": {
|
||||
"name": "Test Theme",
|
||||
"tokens": {}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Button",
|
||||
"source": "shadcn",
|
||||
"variants": ["default", "outline", "ghost", "destructive"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
validator = ProjectValidator()
|
||||
result = validator.validate(data)
|
||||
|
||||
assert result.is_valid is True
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestValidationResult:
|
||||
"""Test ValidationResult model"""
|
||||
|
||||
def test_create_valid_result(self):
|
||||
"""Test creating a valid validation result"""
|
||||
from dss.validators.schema import ValidationResult
|
||||
|
||||
result = ValidationResult(
|
||||
is_valid=True,
|
||||
stage=ValidationStage.COMPLETE,
|
||||
errors=[]
|
||||
)
|
||||
|
||||
assert result.is_valid is True
|
||||
assert result.stage == ValidationStage.COMPLETE
|
||||
assert len(result.errors) == 0
|
||||
|
||||
def test_create_invalid_result_with_errors(self):
|
||||
"""Test creating invalid result with errors"""
|
||||
from dss.validators.schema import ValidationResult, ValidationError
|
||||
|
||||
error = ValidationError(
|
||||
stage=ValidationStage.SCHEMA,
|
||||
message="Missing required field: id",
|
||||
field="id"
|
||||
)
|
||||
|
||||
result = ValidationResult(
|
||||
is_valid=False,
|
||||
stage=ValidationStage.SCHEMA,
|
||||
errors=[error]
|
||||
)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert len(result.errors) == 1
|
||||
assert result.errors[0].field == "id"
|
||||
Reference in New Issue
Block a user