julee 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- julee/__init__.py +3 -0
- julee/api/__init__.py +20 -0
- julee/api/app.py +180 -0
- julee/api/dependencies.py +257 -0
- julee/api/requests.py +175 -0
- julee/api/responses.py +43 -0
- julee/api/routers/__init__.py +43 -0
- julee/api/routers/assembly_specifications.py +212 -0
- julee/api/routers/documents.py +182 -0
- julee/api/routers/knowledge_service_configs.py +79 -0
- julee/api/routers/knowledge_service_queries.py +293 -0
- julee/api/routers/system.py +137 -0
- julee/api/routers/workflows.py +234 -0
- julee/api/services/__init__.py +20 -0
- julee/api/services/system_initialization.py +214 -0
- julee/api/tests/__init__.py +14 -0
- julee/api/tests/routers/__init__.py +17 -0
- julee/api/tests/routers/test_assembly_specifications.py +749 -0
- julee/api/tests/routers/test_documents.py +301 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +234 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +738 -0
- julee/api/tests/routers/test_system.py +179 -0
- julee/api/tests/routers/test_workflows.py +393 -0
- julee/api/tests/test_app.py +285 -0
- julee/api/tests/test_dependencies.py +245 -0
- julee/api/tests/test_requests.py +250 -0
- julee/domain/__init__.py +22 -0
- julee/domain/models/__init__.py +49 -0
- julee/domain/models/assembly/__init__.py +17 -0
- julee/domain/models/assembly/assembly.py +103 -0
- julee/domain/models/assembly/tests/__init__.py +0 -0
- julee/domain/models/assembly/tests/factories.py +37 -0
- julee/domain/models/assembly/tests/test_assembly.py +430 -0
- julee/domain/models/assembly_specification/__init__.py +24 -0
- julee/domain/models/assembly_specification/assembly_specification.py +172 -0
- julee/domain/models/assembly_specification/knowledge_service_query.py +123 -0
- julee/domain/models/assembly_specification/tests/__init__.py +0 -0
- julee/domain/models/assembly_specification/tests/factories.py +78 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +490 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +310 -0
- julee/domain/models/custom_fields/__init__.py +0 -0
- julee/domain/models/custom_fields/content_stream.py +68 -0
- julee/domain/models/custom_fields/tests/__init__.py +0 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +53 -0
- julee/domain/models/document/__init__.py +17 -0
- julee/domain/models/document/document.py +150 -0
- julee/domain/models/document/tests/__init__.py +0 -0
- julee/domain/models/document/tests/factories.py +76 -0
- julee/domain/models/document/tests/test_document.py +297 -0
- julee/domain/models/knowledge_service_config/__init__.py +17 -0
- julee/domain/models/knowledge_service_config/knowledge_service_config.py +86 -0
- julee/domain/models/policy/__init__.py +15 -0
- julee/domain/models/policy/document_policy_validation.py +220 -0
- julee/domain/models/policy/policy.py +203 -0
- julee/domain/models/policy/tests/__init__.py +0 -0
- julee/domain/models/policy/tests/factories.py +47 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +420 -0
- julee/domain/models/policy/tests/test_policy.py +546 -0
- julee/domain/repositories/__init__.py +27 -0
- julee/domain/repositories/assembly.py +45 -0
- julee/domain/repositories/assembly_specification.py +52 -0
- julee/domain/repositories/base.py +146 -0
- julee/domain/repositories/document.py +49 -0
- julee/domain/repositories/document_policy_validation.py +52 -0
- julee/domain/repositories/knowledge_service_config.py +54 -0
- julee/domain/repositories/knowledge_service_query.py +44 -0
- julee/domain/repositories/policy.py +49 -0
- julee/domain/use_cases/__init__.py +17 -0
- julee/domain/use_cases/decorators.py +107 -0
- julee/domain/use_cases/extract_assemble_data.py +649 -0
- julee/domain/use_cases/initialize_system_data.py +842 -0
- julee/domain/use_cases/tests/__init__.py +7 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +548 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +455 -0
- julee/domain/use_cases/tests/test_validate_document.py +1228 -0
- julee/domain/use_cases/validate_document.py +736 -0
- julee/fixtures/assembly_specifications.yaml +70 -0
- julee/fixtures/documents.yaml +178 -0
- julee/fixtures/knowledge_service_configs.yaml +37 -0
- julee/fixtures/knowledge_service_queries.yaml +27 -0
- julee/repositories/__init__.py +17 -0
- julee/repositories/memory/__init__.py +31 -0
- julee/repositories/memory/assembly.py +84 -0
- julee/repositories/memory/assembly_specification.py +125 -0
- julee/repositories/memory/base.py +227 -0
- julee/repositories/memory/document.py +149 -0
- julee/repositories/memory/document_policy_validation.py +104 -0
- julee/repositories/memory/knowledge_service_config.py +123 -0
- julee/repositories/memory/knowledge_service_query.py +120 -0
- julee/repositories/memory/policy.py +87 -0
- julee/repositories/memory/tests/__init__.py +0 -0
- julee/repositories/memory/tests/test_document.py +212 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +161 -0
- julee/repositories/memory/tests/test_policy.py +443 -0
- julee/repositories/minio/__init__.py +31 -0
- julee/repositories/minio/assembly.py +103 -0
- julee/repositories/minio/assembly_specification.py +170 -0
- julee/repositories/minio/client.py +570 -0
- julee/repositories/minio/document.py +530 -0
- julee/repositories/minio/document_policy_validation.py +120 -0
- julee/repositories/minio/knowledge_service_config.py +187 -0
- julee/repositories/minio/knowledge_service_query.py +211 -0
- julee/repositories/minio/policy.py +106 -0
- julee/repositories/minio/tests/__init__.py +0 -0
- julee/repositories/minio/tests/fake_client.py +213 -0
- julee/repositories/minio/tests/test_assembly.py +374 -0
- julee/repositories/minio/tests/test_assembly_specification.py +391 -0
- julee/repositories/minio/tests/test_client_protocol.py +57 -0
- julee/repositories/minio/tests/test_document.py +591 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +192 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +374 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +438 -0
- julee/repositories/minio/tests/test_policy.py +559 -0
- julee/repositories/temporal/__init__.py +38 -0
- julee/repositories/temporal/activities.py +114 -0
- julee/repositories/temporal/activity_names.py +34 -0
- julee/repositories/temporal/proxies.py +159 -0
- julee/services/__init__.py +18 -0
- julee/services/knowledge_service/__init__.py +48 -0
- julee/services/knowledge_service/anthropic/__init__.py +12 -0
- julee/services/knowledge_service/anthropic/knowledge_service.py +331 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +318 -0
- julee/services/knowledge_service/factory.py +138 -0
- julee/services/knowledge_service/knowledge_service.py +160 -0
- julee/services/knowledge_service/memory/__init__.py +13 -0
- julee/services/knowledge_service/memory/knowledge_service.py +278 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +345 -0
- julee/services/knowledge_service/test_factory.py +112 -0
- julee/services/temporal/__init__.py +38 -0
- julee/services/temporal/activities.py +86 -0
- julee/services/temporal/activity_names.py +22 -0
- julee/services/temporal/proxies.py +41 -0
- julee/util/__init__.py +0 -0
- julee/util/domain.py +119 -0
- julee/util/repos/__init__.py +0 -0
- julee/util/repos/minio/__init__.py +0 -0
- julee/util/repos/minio/file_storage.py +213 -0
- julee/util/repos/temporal/__init__.py +11 -0
- julee/util/repos/temporal/client_proxies/file_storage.py +68 -0
- julee/util/repos/temporal/data_converter.py +123 -0
- julee/util/repos/temporal/minio_file_storage.py +12 -0
- julee/util/repos/temporal/proxies/__init__.py +0 -0
- julee/util/repos/temporal/proxies/file_storage.py +58 -0
- julee/util/repositories.py +55 -0
- julee/util/temporal/__init__.py +22 -0
- julee/util/temporal/activities.py +123 -0
- julee/util/temporal/decorators.py +473 -0
- julee/util/tests/__init__.py +1 -0
- julee/util/tests/test_decorators.py +770 -0
- julee/util/validation/__init__.py +29 -0
- julee/util/validation/repository.py +100 -0
- julee/util/validation/type_guards.py +369 -0
- julee/worker.py +211 -0
- julee/workflows/__init__.py +26 -0
- julee/workflows/extract_assemble.py +215 -0
- julee/workflows/validate_document.py +228 -0
- julee-0.1.0.dist-info/METADATA +195 -0
- julee-0.1.0.dist-info/RECORD +161 -0
- julee-0.1.0.dist-info/WHEEL +5 -0
- julee-0.1.0.dist-info/licenses/LICENSE +674 -0
- julee-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KnowledgeServiceQuery domain models for the Capture, Extract, Assemble,
|
|
3
|
+
Publish
|
|
4
|
+
workflow.
|
|
5
|
+
|
|
6
|
+
This module contains the KnowledgeServiceQuery domain object that represents
|
|
7
|
+
specific queries to knowledge services for data extraction in the CEAP
|
|
8
|
+
workflow system.
|
|
9
|
+
|
|
10
|
+
A KnowledgeServiceQuery defines a specific extraction operation that can be
|
|
11
|
+
performed against a knowledge service to extract data for a particular part
|
|
12
|
+
of an AssemblySpecification's JSON schema.
|
|
13
|
+
|
|
14
|
+
All domain models use Pydantic BaseModel for validation, serialization,
|
|
15
|
+
and type safety, following the patterns established in the sample project.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field, field_validator
|
|
19
|
+
from typing import Optional, Dict, Any
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class KnowledgeServiceQuery(BaseModel):
|
|
24
|
+
"""Knowledge service query configuration for extracting specific data.
|
|
25
|
+
|
|
26
|
+
A KnowledgeServiceQuery represents a specific extraction operation that
|
|
27
|
+
can be performed against a knowledge service. It defines which knowledge
|
|
28
|
+
service to use and what prompt to send for data extraction.
|
|
29
|
+
|
|
30
|
+
When executed, the relevant section of the AssemblySpecification's JSON
|
|
31
|
+
schema will be
|
|
32
|
+
passed along with the prompt to ensure the knowledge service response
|
|
33
|
+
conforms to the expected structure and validation requirements.
|
|
34
|
+
|
|
35
|
+
The mapping between queries and schema sections is handled by the
|
|
36
|
+
AssemblySpecification's knowledge_service_queries field.
|
|
37
|
+
|
|
38
|
+
Examples of query_metadata usage:
|
|
39
|
+
|
|
40
|
+
For Anthropic services:
|
|
41
|
+
query_metadata = {
|
|
42
|
+
"model": "claude-sonnet-4-20250514",
|
|
43
|
+
"max_tokens": 4000,
|
|
44
|
+
"temperature": 0.1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
For OpenAI services:
|
|
48
|
+
query_metadata = {
|
|
49
|
+
"model": "gpt-4",
|
|
50
|
+
"temperature": 0.2,
|
|
51
|
+
"top_p": 0.9
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
For custom services:
|
|
55
|
+
query_metadata = {
|
|
56
|
+
"endpoint": "custom-model-v2",
|
|
57
|
+
"timeout": 30,
|
|
58
|
+
"retries": 3
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# Core query identification
|
|
63
|
+
query_id: str = Field(description="Unique identifier for this query")
|
|
64
|
+
name: str = Field(description="Human-readable name describing the query purpose")
|
|
65
|
+
|
|
66
|
+
# Knowledge service configuration
|
|
67
|
+
knowledge_service_id: str = Field(
|
|
68
|
+
description="Identifier of the knowledge service to query"
|
|
69
|
+
)
|
|
70
|
+
prompt: str = Field(
|
|
71
|
+
description="The specific prompt to send to the knowledge service "
|
|
72
|
+
"for this extraction"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Service-specific configuration
|
|
76
|
+
query_metadata: Optional[Dict[str, Any]] = Field(
|
|
77
|
+
default_factory=dict,
|
|
78
|
+
description="Service-specific metadata and configuration options "
|
|
79
|
+
"such as model selection, temperature, max_tokens, etc. "
|
|
80
|
+
"The structure depends on the specific knowledge service being used.",
|
|
81
|
+
)
|
|
82
|
+
assistant_prompt: Optional[str] = Field(
|
|
83
|
+
default=None,
|
|
84
|
+
description="Optional assistant message content to constrain "
|
|
85
|
+
"or prime the model's response. This is added as the final "
|
|
86
|
+
"assistant message before the model generates its response, "
|
|
87
|
+
"allowing control over response format and structure.",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
created_at: Optional[datetime] = Field(
|
|
91
|
+
default_factory=lambda: datetime.now(timezone.utc)
|
|
92
|
+
)
|
|
93
|
+
updated_at: Optional[datetime] = Field(
|
|
94
|
+
default_factory=lambda: datetime.now(timezone.utc)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@field_validator("query_id")
|
|
98
|
+
@classmethod
|
|
99
|
+
def query_id_must_not_be_empty(cls, v: str) -> str:
|
|
100
|
+
if not v or not v.strip():
|
|
101
|
+
raise ValueError("Query ID cannot be empty")
|
|
102
|
+
return v.strip()
|
|
103
|
+
|
|
104
|
+
@field_validator("name")
|
|
105
|
+
@classmethod
|
|
106
|
+
def name_must_not_be_empty(cls, v: str) -> str:
|
|
107
|
+
if not v or not v.strip():
|
|
108
|
+
raise ValueError("Query name cannot be empty")
|
|
109
|
+
return v.strip()
|
|
110
|
+
|
|
111
|
+
@field_validator("knowledge_service_id")
|
|
112
|
+
@classmethod
|
|
113
|
+
def knowledge_service_id_must_not_be_empty(cls, v: str) -> str:
|
|
114
|
+
if not v or not v.strip():
|
|
115
|
+
raise ValueError("Knowledge service ID cannot be empty")
|
|
116
|
+
return v.strip()
|
|
117
|
+
|
|
118
|
+
@field_validator("prompt")
|
|
119
|
+
@classmethod
|
|
120
|
+
def prompt_must_not_be_empty(cls, v: str) -> str:
|
|
121
|
+
if not v or not v.strip():
|
|
122
|
+
raise ValueError("Query prompt cannot be empty")
|
|
123
|
+
return v.strip()
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test factories for AssemblySpecification domain objects using factory_boy.
|
|
3
|
+
|
|
4
|
+
This module provides factory_boy factories for creating test instances of
|
|
5
|
+
AssemblySpecification domain objects with sensible defaults.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Any
|
|
10
|
+
from factory.base import Factory
|
|
11
|
+
from factory.faker import Faker
|
|
12
|
+
from factory.declarations import LazyAttribute, LazyFunction
|
|
13
|
+
|
|
14
|
+
from julee.domain.models.assembly_specification import (
|
|
15
|
+
AssemblySpecification,
|
|
16
|
+
AssemblySpecificationStatus,
|
|
17
|
+
KnowledgeServiceQuery,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AssemblyFactory(Factory):
|
|
22
|
+
"""Factory for creating AssemblySpecification instances with sensible
|
|
23
|
+
test defaults."""
|
|
24
|
+
|
|
25
|
+
class Meta:
|
|
26
|
+
model = AssemblySpecification
|
|
27
|
+
|
|
28
|
+
# Core assembly identification
|
|
29
|
+
assembly_specification_id = Faker("uuid4")
|
|
30
|
+
name = "Test Assembly"
|
|
31
|
+
applicability = "Test documents for automated testing purposes"
|
|
32
|
+
|
|
33
|
+
# Valid JSON Schema for testing
|
|
34
|
+
@LazyAttribute
|
|
35
|
+
def jsonschema(self) -> dict[str, Any]:
|
|
36
|
+
return {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"title": {"type": "string"},
|
|
40
|
+
"content": {"type": "string"},
|
|
41
|
+
"metadata": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"author": {"type": "string"},
|
|
45
|
+
"created_date": {"type": "string", "format": "date"},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"required": ["title"],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Assembly configuration
|
|
53
|
+
status = AssemblySpecificationStatus.ACTIVE
|
|
54
|
+
version = "0.1.0"
|
|
55
|
+
|
|
56
|
+
# Timestamps
|
|
57
|
+
created_at = LazyFunction(lambda: datetime.now(timezone.utc))
|
|
58
|
+
updated_at = LazyFunction(lambda: datetime.now(timezone.utc))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class KnowledgeServiceQueryFactory(Factory):
|
|
62
|
+
"""Factory for creating KnowledgeServiceQuery instances with sensible
|
|
63
|
+
test defaults."""
|
|
64
|
+
|
|
65
|
+
class Meta:
|
|
66
|
+
model = KnowledgeServiceQuery
|
|
67
|
+
|
|
68
|
+
# Core query identification
|
|
69
|
+
query_id = Faker("uuid4")
|
|
70
|
+
name = "Test Knowledge Service Query"
|
|
71
|
+
|
|
72
|
+
# Knowledge service configuration
|
|
73
|
+
knowledge_service_id = "test-knowledge-service"
|
|
74
|
+
prompt = "Extract test data from the document"
|
|
75
|
+
|
|
76
|
+
# Timestamps
|
|
77
|
+
created_at = LazyFunction(lambda: datetime.now(timezone.utc))
|
|
78
|
+
updated_at = LazyFunction(lambda: datetime.now(timezone.utc))
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for AssemblySpecification domain model.
|
|
3
|
+
|
|
4
|
+
This test module documents the design decisions made for the
|
|
5
|
+
AssemblySpecification domain model using table-based tests. It covers:
|
|
6
|
+
|
|
7
|
+
- AssemblySpecification instantiation with various field combinations
|
|
8
|
+
- JSON Schema validation rules and error conditions
|
|
9
|
+
- JSON serialization behavior
|
|
10
|
+
- Field validation for required fields
|
|
11
|
+
|
|
12
|
+
Design decisions documented:
|
|
13
|
+
- AssemblySpecifications must have all required fields (id, name,
|
|
14
|
+
applicability, prompt, jsonschema)
|
|
15
|
+
- JSON Schema field must be a valid JSON Schema dictionary
|
|
16
|
+
- All text fields must be non-empty and non-whitespace
|
|
17
|
+
- Version field has a default but can be customized
|
|
18
|
+
- Status defaults to ACTIVE
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
import json
|
|
23
|
+
from typing import Dict, Any
|
|
24
|
+
|
|
25
|
+
from julee.domain.models.assembly_specification import (
|
|
26
|
+
AssemblySpecification,
|
|
27
|
+
AssemblySpecificationStatus,
|
|
28
|
+
)
|
|
29
|
+
from .factories import AssemblyFactory
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestAssemblyInstantiation:
|
|
33
|
+
"""Test AssemblySpecification creation with various field combinations."""
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize(
|
|
36
|
+
"assembly_specification_id,name,applicability,jsonschema,expected_success",
|
|
37
|
+
[
|
|
38
|
+
# Valid cases
|
|
39
|
+
(
|
|
40
|
+
"assembly-specification-1",
|
|
41
|
+
"Meeting Minutes",
|
|
42
|
+
"Corporate meeting recordings and transcripts",
|
|
43
|
+
{
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {"title": {"type": "string"}},
|
|
46
|
+
},
|
|
47
|
+
True,
|
|
48
|
+
),
|
|
49
|
+
(
|
|
50
|
+
"assembly-specification-2",
|
|
51
|
+
"Project Report",
|
|
52
|
+
"Technical project documentation and status reports",
|
|
53
|
+
{
|
|
54
|
+
"type": "object",
|
|
55
|
+
"properties": {
|
|
56
|
+
"project_name": {"type": "string"},
|
|
57
|
+
"status": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"enum": ["active", "completed"],
|
|
60
|
+
},
|
|
61
|
+
"milestones": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"items": {"type": "string"},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"required": ["project_name"],
|
|
67
|
+
},
|
|
68
|
+
True,
|
|
69
|
+
),
|
|
70
|
+
# Invalid cases - empty required fields
|
|
71
|
+
(
|
|
72
|
+
"",
|
|
73
|
+
"Test Assembly",
|
|
74
|
+
"Test applicability",
|
|
75
|
+
{
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {"test": {"type": "string"}},
|
|
78
|
+
},
|
|
79
|
+
False,
|
|
80
|
+
), # Empty assembly_specification_id
|
|
81
|
+
(
|
|
82
|
+
"assembly-specification-3",
|
|
83
|
+
"",
|
|
84
|
+
"Test applicability",
|
|
85
|
+
{
|
|
86
|
+
"type": "object",
|
|
87
|
+
"properties": {"test": {"type": "string"}},
|
|
88
|
+
},
|
|
89
|
+
False,
|
|
90
|
+
), # Empty name
|
|
91
|
+
(
|
|
92
|
+
"assembly-specification-4",
|
|
93
|
+
"Test Assembly",
|
|
94
|
+
"",
|
|
95
|
+
{
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {"test": {"type": "string"}},
|
|
98
|
+
},
|
|
99
|
+
False,
|
|
100
|
+
), # Empty applicability
|
|
101
|
+
# Invalid cases - whitespace only
|
|
102
|
+
(
|
|
103
|
+
" ",
|
|
104
|
+
"Test Assembly",
|
|
105
|
+
"Test applicability",
|
|
106
|
+
{
|
|
107
|
+
"type": "object",
|
|
108
|
+
"properties": {"test": {"type": "string"}},
|
|
109
|
+
},
|
|
110
|
+
False,
|
|
111
|
+
), # Whitespace assembly_specification_id
|
|
112
|
+
(
|
|
113
|
+
"assembly-specification-6",
|
|
114
|
+
" ",
|
|
115
|
+
"Test applicability",
|
|
116
|
+
{
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {"test": {"type": "string"}},
|
|
119
|
+
},
|
|
120
|
+
False,
|
|
121
|
+
), # Whitespace name
|
|
122
|
+
(
|
|
123
|
+
"assembly-specification-7",
|
|
124
|
+
"Test Assembly",
|
|
125
|
+
" ",
|
|
126
|
+
{
|
|
127
|
+
"type": "object",
|
|
128
|
+
"properties": {"test": {"type": "string"}},
|
|
129
|
+
},
|
|
130
|
+
False,
|
|
131
|
+
), # Whitespace applicability
|
|
132
|
+
],
|
|
133
|
+
)
|
|
134
|
+
def test_assembly_creation_validation(
|
|
135
|
+
self,
|
|
136
|
+
assembly_specification_id: str,
|
|
137
|
+
name: str,
|
|
138
|
+
applicability: str,
|
|
139
|
+
jsonschema: Dict[str, Any],
|
|
140
|
+
expected_success: bool,
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Test assembly creation with various field validation scenarios."""
|
|
143
|
+
if expected_success:
|
|
144
|
+
# Should create successfully
|
|
145
|
+
assembly = AssemblySpecification(
|
|
146
|
+
assembly_specification_id=assembly_specification_id,
|
|
147
|
+
name=name,
|
|
148
|
+
applicability=applicability,
|
|
149
|
+
jsonschema=jsonschema,
|
|
150
|
+
)
|
|
151
|
+
assert (
|
|
152
|
+
assembly.assembly_specification_id == assembly_specification_id.strip()
|
|
153
|
+
)
|
|
154
|
+
assert assembly.name == name.strip()
|
|
155
|
+
assert assembly.applicability == applicability.strip()
|
|
156
|
+
assert assembly.jsonschema == jsonschema
|
|
157
|
+
assert assembly.status == AssemblySpecificationStatus.ACTIVE # Default
|
|
158
|
+
assert assembly.version == "0.1.0" # Default
|
|
159
|
+
else:
|
|
160
|
+
# Should raise validation error
|
|
161
|
+
with pytest.raises(Exception): # Could be ValueError or ValidationError
|
|
162
|
+
AssemblySpecification(
|
|
163
|
+
assembly_specification_id=assembly_specification_id,
|
|
164
|
+
name=name,
|
|
165
|
+
applicability=applicability,
|
|
166
|
+
jsonschema=jsonschema,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TestAssemblyKnowledgeServiceQueriesValidation:
|
|
171
|
+
"""Test knowledge_service_queries field validation."""
|
|
172
|
+
|
|
173
|
+
@pytest.mark.parametrize(
|
|
174
|
+
"knowledge_service_queries,expected_success",
|
|
175
|
+
[
|
|
176
|
+
# Valid cases - empty dict
|
|
177
|
+
({}, True),
|
|
178
|
+
# Valid cases - valid JSON pointers that exist in schema
|
|
179
|
+
({"/properties/test": "query-1"}, True),
|
|
180
|
+
({"": "root-query"}, True), # Empty string for root
|
|
181
|
+
({"/properties/test": "query-1", "": "root-query"}, True),
|
|
182
|
+
# Invalid cases - malformed pointers
|
|
183
|
+
({"invalid-pointer": "query-1"}, False),
|
|
184
|
+
({"test": "query-1"}, False), # Missing /properties/
|
|
185
|
+
# Invalid cases - pointers that don't exist in schema
|
|
186
|
+
({"/properties/nonexistent": "query-1"}, False),
|
|
187
|
+
# Invalid cases - wrong types
|
|
188
|
+
("not-a-dict", False),
|
|
189
|
+
(["/properties/test", "query-1"], False),
|
|
190
|
+
# Invalid cases - invalid query IDs
|
|
191
|
+
({"/properties/test": ""}, False), # Empty query ID
|
|
192
|
+
({"/properties/test": 123}, False), # Non-string query ID
|
|
193
|
+
],
|
|
194
|
+
)
|
|
195
|
+
def test_knowledge_service_queries_validation(
|
|
196
|
+
self,
|
|
197
|
+
knowledge_service_queries: Any,
|
|
198
|
+
expected_success: bool,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Test knowledge_service_queries field validation."""
|
|
201
|
+
if expected_success:
|
|
202
|
+
# Should create successfully
|
|
203
|
+
assembly = AssemblySpecification(
|
|
204
|
+
assembly_specification_id="test-id",
|
|
205
|
+
name="Test Assembly",
|
|
206
|
+
applicability="Test applicability",
|
|
207
|
+
jsonschema={
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": {"test": {"type": "string"}},
|
|
210
|
+
},
|
|
211
|
+
knowledge_service_queries=knowledge_service_queries,
|
|
212
|
+
)
|
|
213
|
+
assert assembly.knowledge_service_queries == knowledge_service_queries
|
|
214
|
+
else:
|
|
215
|
+
# Should raise validation error
|
|
216
|
+
with pytest.raises(Exception):
|
|
217
|
+
AssemblySpecification(
|
|
218
|
+
assembly_specification_id="test-id",
|
|
219
|
+
name="Test Assembly",
|
|
220
|
+
applicability="Test applicability",
|
|
221
|
+
jsonschema={
|
|
222
|
+
"type": "object",
|
|
223
|
+
"properties": {"test": {"type": "string"}},
|
|
224
|
+
},
|
|
225
|
+
knowledge_service_queries=knowledge_service_queries,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestAssemblyJsonSchemaValidation:
|
|
230
|
+
"""Test JSON Schema field validation."""
|
|
231
|
+
|
|
232
|
+
@pytest.mark.parametrize(
|
|
233
|
+
"jsonschema,error_message_contains",
|
|
234
|
+
[
|
|
235
|
+
# Valid JSON Schemas
|
|
236
|
+
(
|
|
237
|
+
{
|
|
238
|
+
"type": "object",
|
|
239
|
+
"properties": {"name": {"type": "string"}},
|
|
240
|
+
},
|
|
241
|
+
None,
|
|
242
|
+
),
|
|
243
|
+
(
|
|
244
|
+
{"type": "array", "items": {"type": "string"}, "minItems": 1},
|
|
245
|
+
None,
|
|
246
|
+
),
|
|
247
|
+
(
|
|
248
|
+
{"type": "string", "pattern": "^[A-Z]+$"},
|
|
249
|
+
None,
|
|
250
|
+
),
|
|
251
|
+
(
|
|
252
|
+
{
|
|
253
|
+
"type": "object",
|
|
254
|
+
"properties": {
|
|
255
|
+
"user": {
|
|
256
|
+
"type": "object",
|
|
257
|
+
"properties": {
|
|
258
|
+
"name": {"type": "string"},
|
|
259
|
+
"age": {"type": "integer", "minimum": 0},
|
|
260
|
+
},
|
|
261
|
+
"required": ["name"],
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"required": ["user"],
|
|
265
|
+
},
|
|
266
|
+
None,
|
|
267
|
+
),
|
|
268
|
+
# Invalid cases - not a dict
|
|
269
|
+
("not a dict", "Input should be a valid dictionary"),
|
|
270
|
+
(123, "Input should be a valid dictionary"),
|
|
271
|
+
([], "Input should be a valid dictionary"),
|
|
272
|
+
# Invalid cases - missing required fields
|
|
273
|
+
(
|
|
274
|
+
{"properties": {"name": {"type": "string"}}},
|
|
275
|
+
"JSON Schema must have a 'type' field",
|
|
276
|
+
),
|
|
277
|
+
({}, "JSON Schema must have a 'type' field"),
|
|
278
|
+
# Invalid JSON Schema structure
|
|
279
|
+
(
|
|
280
|
+
{"type": "invalid_type"},
|
|
281
|
+
"Invalid JSON Schema",
|
|
282
|
+
),
|
|
283
|
+
(
|
|
284
|
+
{
|
|
285
|
+
"type": "object",
|
|
286
|
+
"properties": {"invalid_prop": {"type": "invalid_type"}},
|
|
287
|
+
},
|
|
288
|
+
"Invalid JSON Schema",
|
|
289
|
+
),
|
|
290
|
+
(
|
|
291
|
+
{
|
|
292
|
+
"type": "object",
|
|
293
|
+
"additionalProperties": "invalid",
|
|
294
|
+
}, # additionalProperties must be boolean or object
|
|
295
|
+
"Invalid JSON Schema",
|
|
296
|
+
),
|
|
297
|
+
],
|
|
298
|
+
)
|
|
299
|
+
def test_jsonschema_validation(
|
|
300
|
+
self,
|
|
301
|
+
jsonschema: Any,
|
|
302
|
+
error_message_contains: str | None,
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Test JSON Schema field validation with various schemas."""
|
|
305
|
+
if error_message_contains is None:
|
|
306
|
+
# Should create successfully
|
|
307
|
+
assembly = AssemblySpecification(
|
|
308
|
+
assembly_specification_id="test-id",
|
|
309
|
+
name="Test Assembly",
|
|
310
|
+
applicability="Test applicability",
|
|
311
|
+
jsonschema=jsonschema,
|
|
312
|
+
)
|
|
313
|
+
assert assembly.jsonschema == jsonschema
|
|
314
|
+
else:
|
|
315
|
+
# Should raise validation error
|
|
316
|
+
with pytest.raises(Exception) as exc_info:
|
|
317
|
+
AssemblySpecification(
|
|
318
|
+
assembly_specification_id="test-id",
|
|
319
|
+
name="Test Assembly",
|
|
320
|
+
applicability="Test applicability",
|
|
321
|
+
jsonschema=jsonschema,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
assert error_message_contains in str(exc_info.value)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class TestAssemblySerialization:
|
|
328
|
+
"""Test AssemblySpecification JSON serialization behavior."""
|
|
329
|
+
|
|
330
|
+
def test_assembly_json_serialization(self) -> None:
|
|
331
|
+
"""Test that AssemblySpecification serializes to JSON correctly."""
|
|
332
|
+
complex_schema = {
|
|
333
|
+
"type": "object",
|
|
334
|
+
"properties": {
|
|
335
|
+
"meeting_info": {
|
|
336
|
+
"type": "object",
|
|
337
|
+
"properties": {
|
|
338
|
+
"title": {"type": "string"},
|
|
339
|
+
"date": {"type": "string", "format": "date"},
|
|
340
|
+
"participants": {
|
|
341
|
+
"type": "array",
|
|
342
|
+
"items": {"type": "string"},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
"required": ["title", "date"],
|
|
346
|
+
},
|
|
347
|
+
"action_items": {
|
|
348
|
+
"type": "array",
|
|
349
|
+
"items": {
|
|
350
|
+
"type": "object",
|
|
351
|
+
"properties": {
|
|
352
|
+
"description": {"type": "string"},
|
|
353
|
+
"assignee": {"type": "string"},
|
|
354
|
+
"due_date": {"type": "string", "format": "date"},
|
|
355
|
+
},
|
|
356
|
+
"required": ["description"],
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
"required": ["meeting_info"],
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
assembly = AssemblyFactory.build(
|
|
364
|
+
assembly_specification_id="meeting-minutes-v1",
|
|
365
|
+
name="Meeting Minutes",
|
|
366
|
+
applicability="Corporate meeting recordings",
|
|
367
|
+
jsonschema=complex_schema,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
json_str = assembly.model_dump_json()
|
|
371
|
+
json_data = json.loads(json_str)
|
|
372
|
+
|
|
373
|
+
# All fields should be present in JSON
|
|
374
|
+
assert (
|
|
375
|
+
json_data["assembly_specification_id"] == assembly.assembly_specification_id
|
|
376
|
+
)
|
|
377
|
+
assert json_data["name"] == assembly.name
|
|
378
|
+
assert json_data["applicability"] == assembly.applicability
|
|
379
|
+
assert json_data["status"] == assembly.status.value
|
|
380
|
+
assert json_data["version"] == assembly.version
|
|
381
|
+
|
|
382
|
+
# JSON Schema should be preserved as structured data
|
|
383
|
+
assert json_data["jsonschema"] == complex_schema
|
|
384
|
+
assert json_data["jsonschema"]["type"] == "object"
|
|
385
|
+
assert "meeting_info" in json_data["jsonschema"]["properties"]
|
|
386
|
+
assert "action_items" in json_data["jsonschema"]["properties"]
|
|
387
|
+
|
|
388
|
+
def test_assembly_json_roundtrip(self) -> None:
|
|
389
|
+
"""Test that AssemblySpecification can be serialized to JSON and
|
|
390
|
+
deserialized back."""
|
|
391
|
+
original_assembly = AssemblyFactory.build()
|
|
392
|
+
|
|
393
|
+
# Serialize to JSON
|
|
394
|
+
json_str = original_assembly.model_dump_json()
|
|
395
|
+
json_data = json.loads(json_str)
|
|
396
|
+
|
|
397
|
+
# Deserialize back to AssemblySpecification
|
|
398
|
+
reconstructed_assembly = AssemblySpecification(**json_data)
|
|
399
|
+
|
|
400
|
+
# Should be equivalent
|
|
401
|
+
assert (
|
|
402
|
+
reconstructed_assembly.assembly_specification_id
|
|
403
|
+
== original_assembly.assembly_specification_id
|
|
404
|
+
)
|
|
405
|
+
assert reconstructed_assembly.name == original_assembly.name
|
|
406
|
+
assert reconstructed_assembly.applicability == original_assembly.applicability
|
|
407
|
+
assert reconstructed_assembly.jsonschema == original_assembly.jsonschema
|
|
408
|
+
assert reconstructed_assembly.status == original_assembly.status
|
|
409
|
+
assert reconstructed_assembly.version == original_assembly.version
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class TestAssemblyDefaults:
|
|
413
|
+
"""Test AssemblySpecification default values and behavior."""
|
|
414
|
+
|
|
415
|
+
def test_assembly_default_values(self) -> None:
|
|
416
|
+
"""Test that AssemblySpecification has correct default values."""
|
|
417
|
+
minimal_assembly = AssemblySpecification(
|
|
418
|
+
assembly_specification_id="test-id",
|
|
419
|
+
name="Test Assembly",
|
|
420
|
+
applicability="Test applicability",
|
|
421
|
+
jsonschema={
|
|
422
|
+
"type": "object",
|
|
423
|
+
"properties": {"test": {"type": "string"}},
|
|
424
|
+
},
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
assert minimal_assembly.status == AssemblySpecificationStatus.ACTIVE
|
|
428
|
+
assert minimal_assembly.version == "0.1.0"
|
|
429
|
+
assert minimal_assembly.created_at is not None
|
|
430
|
+
assert minimal_assembly.updated_at is not None
|
|
431
|
+
|
|
432
|
+
def test_assembly_custom_values(self) -> None:
|
|
433
|
+
"""Test AssemblySpecification with custom non-default values."""
|
|
434
|
+
custom_assembly = AssemblySpecification(
|
|
435
|
+
assembly_specification_id="custom-id",
|
|
436
|
+
name="Custom Assembly",
|
|
437
|
+
applicability="Custom applicability",
|
|
438
|
+
jsonschema={
|
|
439
|
+
"type": "object",
|
|
440
|
+
"properties": {"custom": {"type": "string"}},
|
|
441
|
+
},
|
|
442
|
+
status=AssemblySpecificationStatus.DRAFT,
|
|
443
|
+
version="2.0.0",
|
|
444
|
+
knowledge_service_queries={"/properties/custom": "custom-query-1"},
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
assert custom_assembly.status == AssemblySpecificationStatus.DRAFT
|
|
448
|
+
assert custom_assembly.version == "2.0.0"
|
|
449
|
+
assert custom_assembly.knowledge_service_queries == {
|
|
450
|
+
"/properties/custom": "custom-query-1"
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
@pytest.mark.parametrize(
|
|
454
|
+
"status",
|
|
455
|
+
[
|
|
456
|
+
AssemblySpecificationStatus.ACTIVE,
|
|
457
|
+
AssemblySpecificationStatus.INACTIVE,
|
|
458
|
+
AssemblySpecificationStatus.DRAFT,
|
|
459
|
+
AssemblySpecificationStatus.DEPRECATED,
|
|
460
|
+
],
|
|
461
|
+
)
|
|
462
|
+
def test_assembly_status_values(self, status: AssemblySpecificationStatus) -> None:
|
|
463
|
+
"""Test AssemblySpecification with different status values."""
|
|
464
|
+
assembly = AssemblyFactory.build(status=status)
|
|
465
|
+
assert assembly.status == status
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class TestAssemblyVersionValidation:
|
|
469
|
+
"""Test AssemblySpecification version field validation."""
|
|
470
|
+
|
|
471
|
+
@pytest.mark.parametrize(
|
|
472
|
+
"version,expected_success",
|
|
473
|
+
[
|
|
474
|
+
("1.0.0", True),
|
|
475
|
+
("0.1.0", True),
|
|
476
|
+
("2.5.1-beta", True),
|
|
477
|
+
("v1.0", True),
|
|
478
|
+
("", False), # Empty version
|
|
479
|
+
(" ", False), # Whitespace only
|
|
480
|
+
],
|
|
481
|
+
)
|
|
482
|
+
def test_version_validation(self, version: str, expected_success: bool) -> None:
|
|
483
|
+
"""Test version field validation - we can add semver checks later, not
|
|
484
|
+
needed yet (if at all)."""
|
|
485
|
+
if expected_success:
|
|
486
|
+
assembly = AssemblyFactory.build(version=version)
|
|
487
|
+
assert assembly.version == version.strip()
|
|
488
|
+
else:
|
|
489
|
+
with pytest.raises(Exception):
|
|
490
|
+
AssemblyFactory.build(version=version)
|