atlas-chat 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.
- atlas/__init__.py +40 -0
- atlas/application/__init__.py +7 -0
- atlas/application/chat/__init__.py +7 -0
- atlas/application/chat/agent/__init__.py +10 -0
- atlas/application/chat/agent/act_loop.py +179 -0
- atlas/application/chat/agent/factory.py +142 -0
- atlas/application/chat/agent/protocols.py +46 -0
- atlas/application/chat/agent/react_loop.py +338 -0
- atlas/application/chat/agent/think_act_loop.py +171 -0
- atlas/application/chat/approval_manager.py +151 -0
- atlas/application/chat/elicitation_manager.py +191 -0
- atlas/application/chat/events/__init__.py +1 -0
- atlas/application/chat/events/agent_event_relay.py +112 -0
- atlas/application/chat/modes/__init__.py +1 -0
- atlas/application/chat/modes/agent.py +125 -0
- atlas/application/chat/modes/plain.py +74 -0
- atlas/application/chat/modes/rag.py +81 -0
- atlas/application/chat/modes/tools.py +179 -0
- atlas/application/chat/orchestrator.py +213 -0
- atlas/application/chat/policies/__init__.py +1 -0
- atlas/application/chat/policies/tool_authorization.py +99 -0
- atlas/application/chat/preprocessors/__init__.py +1 -0
- atlas/application/chat/preprocessors/message_builder.py +92 -0
- atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
- atlas/application/chat/service.py +454 -0
- atlas/application/chat/utilities/__init__.py +6 -0
- atlas/application/chat/utilities/error_handler.py +367 -0
- atlas/application/chat/utilities/event_notifier.py +546 -0
- atlas/application/chat/utilities/file_processor.py +613 -0
- atlas/application/chat/utilities/tool_executor.py +789 -0
- atlas/atlas_chat_cli.py +347 -0
- atlas/atlas_client.py +238 -0
- atlas/core/__init__.py +0 -0
- atlas/core/auth.py +205 -0
- atlas/core/authorization_manager.py +27 -0
- atlas/core/capabilities.py +123 -0
- atlas/core/compliance.py +215 -0
- atlas/core/domain_whitelist.py +147 -0
- atlas/core/domain_whitelist_middleware.py +82 -0
- atlas/core/http_client.py +28 -0
- atlas/core/log_sanitizer.py +102 -0
- atlas/core/metrics_logger.py +59 -0
- atlas/core/middleware.py +131 -0
- atlas/core/otel_config.py +242 -0
- atlas/core/prompt_risk.py +200 -0
- atlas/core/rate_limit.py +0 -0
- atlas/core/rate_limit_middleware.py +64 -0
- atlas/core/security_headers_middleware.py +51 -0
- atlas/domain/__init__.py +37 -0
- atlas/domain/chat/__init__.py +1 -0
- atlas/domain/chat/dtos.py +85 -0
- atlas/domain/errors.py +96 -0
- atlas/domain/messages/__init__.py +12 -0
- atlas/domain/messages/models.py +160 -0
- atlas/domain/rag_mcp_service.py +664 -0
- atlas/domain/sessions/__init__.py +7 -0
- atlas/domain/sessions/models.py +36 -0
- atlas/domain/unified_rag_service.py +371 -0
- atlas/infrastructure/__init__.py +10 -0
- atlas/infrastructure/app_factory.py +135 -0
- atlas/infrastructure/events/__init__.py +1 -0
- atlas/infrastructure/events/cli_event_publisher.py +140 -0
- atlas/infrastructure/events/websocket_publisher.py +140 -0
- atlas/infrastructure/sessions/in_memory_repository.py +56 -0
- atlas/infrastructure/transport/__init__.py +7 -0
- atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
- atlas/init_cli.py +226 -0
- atlas/interfaces/__init__.py +15 -0
- atlas/interfaces/events.py +134 -0
- atlas/interfaces/llm.py +54 -0
- atlas/interfaces/rag.py +40 -0
- atlas/interfaces/sessions.py +75 -0
- atlas/interfaces/tools.py +57 -0
- atlas/interfaces/transport.py +24 -0
- atlas/main.py +564 -0
- atlas/mcp/api_key_demo/README.md +76 -0
- atlas/mcp/api_key_demo/main.py +172 -0
- atlas/mcp/api_key_demo/run.sh +56 -0
- atlas/mcp/basictable/main.py +147 -0
- atlas/mcp/calculator/main.py +149 -0
- atlas/mcp/code-executor/execution_engine.py +98 -0
- atlas/mcp/code-executor/execution_environment.py +95 -0
- atlas/mcp/code-executor/main.py +528 -0
- atlas/mcp/code-executor/result_processing.py +276 -0
- atlas/mcp/code-executor/script_generation.py +195 -0
- atlas/mcp/code-executor/security_checker.py +140 -0
- atlas/mcp/corporate_cars/main.py +437 -0
- atlas/mcp/csv_reporter/main.py +545 -0
- atlas/mcp/duckduckgo/main.py +182 -0
- atlas/mcp/elicitation_demo/README.md +171 -0
- atlas/mcp/elicitation_demo/main.py +262 -0
- atlas/mcp/env-demo/README.md +158 -0
- atlas/mcp/env-demo/main.py +199 -0
- atlas/mcp/file_size_test/main.py +284 -0
- atlas/mcp/filesystem/main.py +348 -0
- atlas/mcp/image_demo/main.py +113 -0
- atlas/mcp/image_demo/requirements.txt +4 -0
- atlas/mcp/logging_demo/README.md +72 -0
- atlas/mcp/logging_demo/main.py +103 -0
- atlas/mcp/many_tools_demo/main.py +50 -0
- atlas/mcp/order_database/__init__.py +0 -0
- atlas/mcp/order_database/main.py +369 -0
- atlas/mcp/order_database/signal_data.csv +1001 -0
- atlas/mcp/pdfbasic/main.py +394 -0
- atlas/mcp/pptx_generator/main.py +760 -0
- atlas/mcp/pptx_generator/requirements.txt +13 -0
- atlas/mcp/pptx_generator/run_test.sh +1 -0
- atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
- atlas/mcp/progress_demo/main.py +167 -0
- atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
- atlas/mcp/progress_updates_demo/README.md +120 -0
- atlas/mcp/progress_updates_demo/main.py +497 -0
- atlas/mcp/prompts/main.py +222 -0
- atlas/mcp/public_demo/main.py +189 -0
- atlas/mcp/sampling_demo/README.md +169 -0
- atlas/mcp/sampling_demo/main.py +234 -0
- atlas/mcp/thinking/main.py +77 -0
- atlas/mcp/tool_planner/main.py +240 -0
- atlas/mcp/ui-demo/badmesh.png +0 -0
- atlas/mcp/ui-demo/main.py +383 -0
- atlas/mcp/ui-demo/templates/button_demo.html +32 -0
- atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
- atlas/mcp/ui-demo/templates/form_demo.html +28 -0
- atlas/mcp/username-override-demo/README.md +320 -0
- atlas/mcp/username-override-demo/main.py +308 -0
- atlas/modules/__init__.py +0 -0
- atlas/modules/config/__init__.py +34 -0
- atlas/modules/config/cli.py +231 -0
- atlas/modules/config/config_manager.py +1096 -0
- atlas/modules/file_storage/__init__.py +22 -0
- atlas/modules/file_storage/cli.py +330 -0
- atlas/modules/file_storage/content_extractor.py +290 -0
- atlas/modules/file_storage/manager.py +295 -0
- atlas/modules/file_storage/mock_s3_client.py +402 -0
- atlas/modules/file_storage/s3_client.py +417 -0
- atlas/modules/llm/__init__.py +19 -0
- atlas/modules/llm/caller.py +287 -0
- atlas/modules/llm/litellm_caller.py +675 -0
- atlas/modules/llm/models.py +19 -0
- atlas/modules/mcp_tools/__init__.py +17 -0
- atlas/modules/mcp_tools/client.py +2123 -0
- atlas/modules/mcp_tools/token_storage.py +556 -0
- atlas/modules/prompts/prompt_provider.py +130 -0
- atlas/modules/rag/__init__.py +24 -0
- atlas/modules/rag/atlas_rag_client.py +336 -0
- atlas/modules/rag/client.py +129 -0
- atlas/routes/admin_routes.py +865 -0
- atlas/routes/config_routes.py +484 -0
- atlas/routes/feedback_routes.py +361 -0
- atlas/routes/files_routes.py +274 -0
- atlas/routes/health_routes.py +40 -0
- atlas/routes/mcp_auth_routes.py +223 -0
- atlas/server_cli.py +164 -0
- atlas/tests/conftest.py +20 -0
- atlas/tests/integration/test_mcp_auth_integration.py +152 -0
- atlas/tests/manual_test_sampling.py +87 -0
- atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
- atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
- atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
- atlas/tests/test_agent_roa.py +135 -0
- atlas/tests/test_app_factory_smoke.py +47 -0
- atlas/tests/test_approval_manager.py +439 -0
- atlas/tests/test_atlas_client.py +188 -0
- atlas/tests/test_atlas_rag_client.py +447 -0
- atlas/tests/test_atlas_rag_integration.py +224 -0
- atlas/tests/test_attach_file_flow.py +287 -0
- atlas/tests/test_auth_utils.py +165 -0
- atlas/tests/test_backend_public_url.py +185 -0
- atlas/tests/test_banner_logging.py +287 -0
- atlas/tests/test_capability_tokens_and_injection.py +203 -0
- atlas/tests/test_compliance_level.py +54 -0
- atlas/tests/test_compliance_manager.py +253 -0
- atlas/tests/test_config_manager.py +617 -0
- atlas/tests/test_config_manager_paths.py +12 -0
- atlas/tests/test_core_auth.py +18 -0
- atlas/tests/test_core_utils.py +190 -0
- atlas/tests/test_docker_env_sync.py +202 -0
- atlas/tests/test_domain_errors.py +329 -0
- atlas/tests/test_domain_whitelist.py +359 -0
- atlas/tests/test_elicitation_manager.py +408 -0
- atlas/tests/test_elicitation_routing.py +296 -0
- atlas/tests/test_env_demo_server.py +88 -0
- atlas/tests/test_error_classification.py +113 -0
- atlas/tests/test_error_flow_integration.py +116 -0
- atlas/tests/test_feedback_routes.py +333 -0
- atlas/tests/test_file_content_extraction.py +1134 -0
- atlas/tests/test_file_extraction_routes.py +158 -0
- atlas/tests/test_file_library.py +107 -0
- atlas/tests/test_file_manager_unit.py +18 -0
- atlas/tests/test_health_route.py +49 -0
- atlas/tests/test_http_client_stub.py +8 -0
- atlas/tests/test_imports_smoke.py +30 -0
- atlas/tests/test_interfaces_llm_response.py +9 -0
- atlas/tests/test_issue_access_denied_fix.py +136 -0
- atlas/tests/test_llm_env_expansion.py +836 -0
- atlas/tests/test_log_level_sensitive_data.py +285 -0
- atlas/tests/test_mcp_auth_routes.py +341 -0
- atlas/tests/test_mcp_client_auth.py +331 -0
- atlas/tests/test_mcp_data_injection.py +270 -0
- atlas/tests/test_mcp_get_authorized_servers.py +95 -0
- atlas/tests/test_mcp_hot_reload.py +512 -0
- atlas/tests/test_mcp_image_content.py +424 -0
- atlas/tests/test_mcp_logging.py +172 -0
- atlas/tests/test_mcp_progress_updates.py +313 -0
- atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
- atlas/tests/test_mcp_prompts_server.py +39 -0
- atlas/tests/test_mcp_tool_result_parsing.py +296 -0
- atlas/tests/test_metrics_logger.py +56 -0
- atlas/tests/test_middleware_auth.py +379 -0
- atlas/tests/test_prompt_risk_and_acl.py +141 -0
- atlas/tests/test_rag_mcp_aggregator.py +204 -0
- atlas/tests/test_rag_mcp_service.py +224 -0
- atlas/tests/test_rate_limit_middleware.py +45 -0
- atlas/tests/test_routes_config_smoke.py +60 -0
- atlas/tests/test_routes_files_download_token.py +41 -0
- atlas/tests/test_routes_files_health.py +18 -0
- atlas/tests/test_runtime_imports.py +53 -0
- atlas/tests/test_sampling_integration.py +482 -0
- atlas/tests/test_security_admin_routes.py +61 -0
- atlas/tests/test_security_capability_tokens.py +65 -0
- atlas/tests/test_security_file_stats_scope.py +21 -0
- atlas/tests/test_security_header_injection.py +191 -0
- atlas/tests/test_security_headers_and_filename.py +63 -0
- atlas/tests/test_shared_session_repository.py +101 -0
- atlas/tests/test_system_prompt_loading.py +181 -0
- atlas/tests/test_token_storage.py +505 -0
- atlas/tests/test_tool_approval_config.py +93 -0
- atlas/tests/test_tool_approval_utils.py +356 -0
- atlas/tests/test_tool_authorization_group_filtering.py +223 -0
- atlas/tests/test_tool_details_in_config.py +108 -0
- atlas/tests/test_tool_planner.py +300 -0
- atlas/tests/test_unified_rag_service.py +398 -0
- atlas/tests/test_username_override_in_approval.py +258 -0
- atlas/tests/test_websocket_auth_header.py +168 -0
- atlas/version.py +6 -0
- atlas_chat-0.1.0.data/data/.env.example +253 -0
- atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
- atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
- atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
- atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
- atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
- atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
- atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
- atlas_chat-0.1.0.dist-info/METADATA +236 -0
- atlas_chat-0.1.0.dist-info/RECORD +250 -0
- atlas_chat-0.1.0.dist-info/WHEEL +5 -0
- atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
- atlas_chat-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Tests for file extraction config exposure in /api/config endpoint."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from main import app
|
|
6
|
+
from starlette.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
from atlas.infrastructure.app_factory import app_factory
|
|
9
|
+
from atlas.modules.config.config_manager import FileExtractorConfig, FileExtractorsConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_config_endpoint_includes_file_extraction_feature_flag():
|
|
13
|
+
"""Config endpoint should include file_content_extraction in features."""
|
|
14
|
+
client = TestClient(app)
|
|
15
|
+
resp = client.get("/api/config", headers={"X-User-Email": "test@test.com"})
|
|
16
|
+
|
|
17
|
+
assert resp.status_code == 200
|
|
18
|
+
data = resp.json()
|
|
19
|
+
assert "features" in data
|
|
20
|
+
assert "file_content_extraction" in data["features"]
|
|
21
|
+
assert isinstance(data["features"]["file_content_extraction"], bool)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_config_endpoint_includes_file_extraction_config():
|
|
25
|
+
"""Config endpoint should include file_extraction configuration."""
|
|
26
|
+
client = TestClient(app)
|
|
27
|
+
resp = client.get("/api/config", headers={"X-User-Email": "test@test.com"})
|
|
28
|
+
|
|
29
|
+
assert resp.status_code == 200
|
|
30
|
+
data = resp.json()
|
|
31
|
+
assert "file_extraction" in data
|
|
32
|
+
file_extraction = data["file_extraction"]
|
|
33
|
+
assert "enabled" in file_extraction
|
|
34
|
+
assert "default_behavior" in file_extraction
|
|
35
|
+
assert "supported_extensions" in file_extraction
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_file_extraction_disabled_when_feature_flag_off():
|
|
39
|
+
"""File extraction should be disabled when feature flag is off."""
|
|
40
|
+
config_manager = app_factory.get_config_manager()
|
|
41
|
+
original_setting = config_manager.app_settings.feature_file_content_extraction_enabled
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = False
|
|
45
|
+
|
|
46
|
+
client = TestClient(app)
|
|
47
|
+
resp = client.get("/api/config", headers={"X-User-Email": "test@test.com"})
|
|
48
|
+
|
|
49
|
+
assert resp.status_code == 200
|
|
50
|
+
data = resp.json()
|
|
51
|
+
assert data["features"]["file_content_extraction"] is False
|
|
52
|
+
assert data["file_extraction"]["enabled"] is False
|
|
53
|
+
assert data["file_extraction"]["default_behavior"] == "none"
|
|
54
|
+
assert data["file_extraction"]["supported_extensions"] == []
|
|
55
|
+
finally:
|
|
56
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = original_setting
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_file_extraction_enabled_with_correct_extensions():
|
|
60
|
+
"""File extraction should show supported extensions when enabled."""
|
|
61
|
+
config_manager = app_factory.get_config_manager()
|
|
62
|
+
original_feature = config_manager.app_settings.feature_file_content_extraction_enabled
|
|
63
|
+
original_extractors = config_manager._file_extractors_config
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Enable feature and set up test config
|
|
67
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = True
|
|
68
|
+
config_manager._file_extractors_config = FileExtractorsConfig(
|
|
69
|
+
enabled=True,
|
|
70
|
+
default_behavior="extract",
|
|
71
|
+
extractors={
|
|
72
|
+
"pdf-text": FileExtractorConfig(url="http://localhost/pdf", enabled=True),
|
|
73
|
+
"image-vision": FileExtractorConfig(url="http://localhost/img", enabled=False)
|
|
74
|
+
},
|
|
75
|
+
extension_mapping={
|
|
76
|
+
".pdf": "pdf-text",
|
|
77
|
+
".png": "image-vision"
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
client = TestClient(app)
|
|
82
|
+
resp = client.get("/api/config", headers={"X-User-Email": "test@test.com"})
|
|
83
|
+
|
|
84
|
+
assert resp.status_code == 200
|
|
85
|
+
data = resp.json()
|
|
86
|
+
assert data["features"]["file_content_extraction"] is True
|
|
87
|
+
assert data["file_extraction"]["enabled"] is True
|
|
88
|
+
assert data["file_extraction"]["default_behavior"] == "full"
|
|
89
|
+
# Only .pdf should be in supported_extensions since image-vision is disabled
|
|
90
|
+
assert ".pdf" in data["file_extraction"]["supported_extensions"]
|
|
91
|
+
assert ".png" not in data["file_extraction"]["supported_extensions"]
|
|
92
|
+
finally:
|
|
93
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = original_feature
|
|
94
|
+
config_manager._file_extractors_config = original_extractors
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_file_extraction_handles_config_errors_gracefully():
|
|
98
|
+
"""File extraction config should handle errors gracefully."""
|
|
99
|
+
config_manager = app_factory.get_config_manager()
|
|
100
|
+
original_feature = config_manager.app_settings.feature_file_content_extraction_enabled
|
|
101
|
+
original_extractors = config_manager._file_extractors_config
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = True
|
|
105
|
+
|
|
106
|
+
# Force an error by setting invalid config that will cause exception
|
|
107
|
+
config_manager._file_extractors_config = None
|
|
108
|
+
|
|
109
|
+
# Patch the property to raise an exception
|
|
110
|
+
with patch.object(
|
|
111
|
+
type(config_manager),
|
|
112
|
+
'file_extractors_config',
|
|
113
|
+
property(lambda self: (_ for _ in ()).throw(Exception("Config error")))
|
|
114
|
+
):
|
|
115
|
+
client = TestClient(app)
|
|
116
|
+
resp = client.get("/api/config", headers={"X-User-Email": "test@test.com"})
|
|
117
|
+
|
|
118
|
+
assert resp.status_code == 200
|
|
119
|
+
data = resp.json()
|
|
120
|
+
# Should return safe defaults on error
|
|
121
|
+
assert data["file_extraction"]["enabled"] is False
|
|
122
|
+
assert data["file_extraction"]["default_behavior"] == "none"
|
|
123
|
+
assert data["file_extraction"]["supported_extensions"] == []
|
|
124
|
+
finally:
|
|
125
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = original_feature
|
|
126
|
+
config_manager._file_extractors_config = original_extractors
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_file_extraction_extensions_sorted():
|
|
130
|
+
"""File extraction supported extensions should be sorted."""
|
|
131
|
+
config_manager = app_factory.get_config_manager()
|
|
132
|
+
original_feature = config_manager.app_settings.feature_file_content_extraction_enabled
|
|
133
|
+
original_extractors = config_manager._file_extractors_config
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = True
|
|
137
|
+
config_manager._file_extractors_config = FileExtractorsConfig(
|
|
138
|
+
enabled=True,
|
|
139
|
+
extractors={
|
|
140
|
+
"pdf-text": FileExtractorConfig(url="http://localhost/pdf", enabled=True)
|
|
141
|
+
},
|
|
142
|
+
extension_mapping={
|
|
143
|
+
".pdf": "pdf-text",
|
|
144
|
+
".doc": "pdf-text",
|
|
145
|
+
".txt": "pdf-text"
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
client = TestClient(app)
|
|
150
|
+
resp = client.get("/api/config", headers={"X-User-Email": "test@test.com"})
|
|
151
|
+
|
|
152
|
+
assert resp.status_code == 200
|
|
153
|
+
data = resp.json()
|
|
154
|
+
extensions = data["file_extraction"]["supported_extensions"]
|
|
155
|
+
assert extensions == sorted(extensions)
|
|
156
|
+
finally:
|
|
157
|
+
config_manager.app_settings.feature_file_content_extraction_enabled = original_feature
|
|
158
|
+
config_manager._file_extractors_config = original_extractors
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for File Library implementation.
|
|
4
|
+
Tests the new file library feature including:
|
|
5
|
+
- AllFilesView component functionality
|
|
6
|
+
- SessionFilesView component
|
|
7
|
+
- FileManagerPanel tab switching
|
|
8
|
+
- Backend attach_file endpoint
|
|
9
|
+
- WebSocket attach_file message handling
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Test the backend attach_file functionality
|
|
15
|
+
class TestAttachFileBackend:
|
|
16
|
+
def test_handle_attach_file_success(self):
|
|
17
|
+
"""Test successful file attachment to session"""
|
|
18
|
+
# This would be a full integration test when backend is running
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def test_handle_attach_file_file_not_found(self):
|
|
22
|
+
"""Test handling of file not found error"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def test_handle_attach_file_unauthorized(self):
|
|
26
|
+
"""Test handling of unauthorized access"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# Frontend component tests would go here
|
|
30
|
+
# These would typically use a testing framework like Jest or Vitest
|
|
31
|
+
|
|
32
|
+
class TestAllFilesView:
|
|
33
|
+
def test_fetch_all_files(self):
|
|
34
|
+
"""Test fetching all user files"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def test_search_filter(self):
|
|
38
|
+
"""Test file search functionality"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def test_sort_functionality(self):
|
|
42
|
+
"""Test file sorting by different criteria"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def test_type_filter(self):
|
|
46
|
+
"""Test filtering by file type (uploaded vs generated)"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def test_load_to_session(self):
|
|
50
|
+
"""Test loading file to current session"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def test_download_file(self):
|
|
54
|
+
"""Test file download functionality"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def test_delete_file(self):
|
|
58
|
+
"""Test file deletion"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
class TestSessionFilesView:
|
|
62
|
+
def test_display_session_files(self):
|
|
63
|
+
"""Test displaying files in current session"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def test_file_actions(self):
|
|
67
|
+
"""Test download, delete, and tagging actions"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
class TestFileManagerPanel:
|
|
71
|
+
def test_tab_switching(self):
|
|
72
|
+
"""Test switching between Session Files and File Library tabs"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def test_initial_tab_state(self):
|
|
76
|
+
"""Test that panel opens on Session Files tab by default"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Integration test scenarios
|
|
80
|
+
class TestFileLibraryIntegration:
|
|
81
|
+
def test_end_to_end_workflow(self):
|
|
82
|
+
"""
|
|
83
|
+
Test end-to-end workflow:
|
|
84
|
+
1. Upload file in session A
|
|
85
|
+
2. Start new session B
|
|
86
|
+
3. Open File Library tab
|
|
87
|
+
4. Search for and find file from session A
|
|
88
|
+
5. Load file into session B
|
|
89
|
+
6. Verify file appears in Session Files
|
|
90
|
+
"""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
print("File Library unit tests")
|
|
95
|
+
print("Note: Most testing should be done manually through the UI")
|
|
96
|
+
print("because the functionality primarily involves user interaction.")
|
|
97
|
+
print("")
|
|
98
|
+
print("Manual testing checklist:")
|
|
99
|
+
print("- Open File Manager panel")
|
|
100
|
+
print("- Switch between 'Session Files' and 'File Library' tabs")
|
|
101
|
+
print("- Verify files are displayed correctly in each tab")
|
|
102
|
+
print("- Search, filter, and sort files in File Library")
|
|
103
|
+
print("- Download files from File Library")
|
|
104
|
+
print("- Delete files from File Library")
|
|
105
|
+
print("- Load files from File Library to current session")
|
|
106
|
+
print("- Verify loaded files appear in Session Files tab")
|
|
107
|
+
print("- Test error handling for failed operations")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
from atlas.modules.file_storage.manager import FileManager
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_file_manager_content_type_and_category():
|
|
6
|
+
fm = FileManager(s3_client=None) # will construct default
|
|
7
|
+
|
|
8
|
+
assert fm.get_content_type("report.pdf") == "application/pdf"
|
|
9
|
+
assert fm.get_content_type("diagram.png") == "image/png"
|
|
10
|
+
assert fm.get_content_type("unknown.bin") == "application/octet-stream"
|
|
11
|
+
|
|
12
|
+
assert fm.categorize_file_type("main.py") == "code"
|
|
13
|
+
assert fm.categorize_file_type("photo.jpg") == "image"
|
|
14
|
+
assert fm.categorize_file_type("data.csv") == "data"
|
|
15
|
+
assert fm.categorize_file_type("notes.txt") == "document"
|
|
16
|
+
|
|
17
|
+
assert fm.should_display_in_canvas("plot.png") is True
|
|
18
|
+
assert fm.should_display_in_canvas("archive.zip") is False
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Unit tests for health check endpoint."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from main import app
|
|
6
|
+
from starlette.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_health_endpoint_returns_200():
|
|
10
|
+
"""Test that health endpoint returns 200 status."""
|
|
11
|
+
client = TestClient(app)
|
|
12
|
+
resp = client.get("/api/health")
|
|
13
|
+
assert resp.status_code == 200
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_health_endpoint_no_auth_required():
|
|
17
|
+
"""Test that health endpoint works without authentication."""
|
|
18
|
+
client = TestClient(app)
|
|
19
|
+
# No X-User-Email header provided
|
|
20
|
+
resp = client.get("/api/health")
|
|
21
|
+
assert resp.status_code == 200
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_health_endpoint_response_structure():
|
|
25
|
+
"""Test that health endpoint returns correct response structure."""
|
|
26
|
+
client = TestClient(app)
|
|
27
|
+
resp = client.get("/api/health")
|
|
28
|
+
assert resp.status_code == 200
|
|
29
|
+
|
|
30
|
+
data = resp.json()
|
|
31
|
+
|
|
32
|
+
# Verify all required fields are present
|
|
33
|
+
assert "status" in data
|
|
34
|
+
assert "service" in data
|
|
35
|
+
assert "version" in data
|
|
36
|
+
assert "timestamp" in data
|
|
37
|
+
|
|
38
|
+
# Verify field values
|
|
39
|
+
assert data["status"] == "healthy"
|
|
40
|
+
assert data["service"] == "atlas-ui-3-backend"
|
|
41
|
+
|
|
42
|
+
# This version number can change, so just check it's a non-empty string
|
|
43
|
+
assert isinstance(data["version"], str) and len(data["version"]) > 0
|
|
44
|
+
|
|
45
|
+
# Verify timestamp is valid ISO-8601 format
|
|
46
|
+
try:
|
|
47
|
+
datetime.fromisoformat(data["timestamp"])
|
|
48
|
+
except ValueError:
|
|
49
|
+
assert False, f"Invalid timestamp format: {data['timestamp']}"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import pkgutil
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Import all backend python modules to catch import-time errors quickly.
|
|
6
|
+
# Skip heavy runtime side effects by not executing app.run, etc.
|
|
7
|
+
|
|
8
|
+
def iter_backend_modules():
|
|
9
|
+
backend_root = Path(__file__).resolve().parents[1]
|
|
10
|
+
sys.path.insert(0, str(backend_root))
|
|
11
|
+
|
|
12
|
+
for module in pkgutil.walk_packages([str(backend_root)], prefix=""):
|
|
13
|
+
name = module.name
|
|
14
|
+
# Skip private and test packages
|
|
15
|
+
if name.startswith("tests"):
|
|
16
|
+
continue
|
|
17
|
+
# Skip MCP servers as they may require external binaries
|
|
18
|
+
if name.startswith("mcp.") or name.startswith("mcp/"):
|
|
19
|
+
continue
|
|
20
|
+
yield name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_import_all_backend_modules():
|
|
24
|
+
failed = []
|
|
25
|
+
for name in iter_backend_modules():
|
|
26
|
+
try:
|
|
27
|
+
__import__(name)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
failed.append((name, str(e)))
|
|
30
|
+
assert not failed, f"Import failures: {failed[:5]} (and {max(0, len(failed)-5)} more)"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from atlas.interfaces.llm import LLMResponse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_llm_response_has_tool_calls():
|
|
5
|
+
r1 = LLMResponse(content="hello", tool_calls=None)
|
|
6
|
+
assert r1.has_tool_calls() is False
|
|
7
|
+
|
|
8
|
+
r2 = LLMResponse(content="hi", tool_calls=[{"type": "function", "function": {"name": "t"}}])
|
|
9
|
+
assert r2.has_tool_calls() is True
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration test demonstrating the fix for the access denied issue.
|
|
3
|
+
|
|
4
|
+
This test simulates the exact scenario from the issue:
|
|
5
|
+
- A file belongs to user 'agarlan@sandia.gov'
|
|
6
|
+
- WebSocket connection is authenticated as 'agarlan@sandia.gov' via X-User-Email header
|
|
7
|
+
- Attaching the file should succeed (not fail with "Access denied")
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
from fastapi.testclient import TestClient
|
|
15
|
+
from main import app
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mock_components():
|
|
20
|
+
"""Mock all components needed for the test."""
|
|
21
|
+
with patch('main.app_factory') as mock_factory:
|
|
22
|
+
# Mock config
|
|
23
|
+
mock_config = MagicMock()
|
|
24
|
+
mock_config.app_settings.test_user = 'test@test.com'
|
|
25
|
+
mock_config.app_settings.auth_user_header = 'X-User-Email'
|
|
26
|
+
mock_factory.get_config_manager.return_value = mock_config
|
|
27
|
+
|
|
28
|
+
# Mock file manager with S3 client
|
|
29
|
+
mock_file_manager = MagicMock()
|
|
30
|
+
mock_s3_client = MagicMock()
|
|
31
|
+
|
|
32
|
+
# Simulate a file that belongs to agarlan@sandia.gov
|
|
33
|
+
async def mock_get_file(user_email, s3_key):
|
|
34
|
+
"""Mock S3 get_file that enforces user prefix check."""
|
|
35
|
+
# This is the actual check from s3_client.py line 185
|
|
36
|
+
if not s3_key.startswith(f"users/{user_email}/"):
|
|
37
|
+
raise Exception("Access denied to file")
|
|
38
|
+
|
|
39
|
+
# If user matches, return file metadata
|
|
40
|
+
return {
|
|
41
|
+
"key": s3_key,
|
|
42
|
+
"filename": "mypdf.pdf",
|
|
43
|
+
"content_base64": base64.b64encode(b"test content").decode(),
|
|
44
|
+
"content_type": "application/pdf",
|
|
45
|
+
"size": 100,
|
|
46
|
+
"etag": "test-etag"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mock_s3_client.get_file = AsyncMock(side_effect=mock_get_file)
|
|
50
|
+
mock_file_manager.s3_client = mock_s3_client
|
|
51
|
+
|
|
52
|
+
# Mock chat service
|
|
53
|
+
mock_chat_service = MagicMock()
|
|
54
|
+
mock_chat_service.handle_attach_file = AsyncMock(return_value={
|
|
55
|
+
'type': 'file_attach',
|
|
56
|
+
'success': True,
|
|
57
|
+
'filename': 'mypdf.pdf'
|
|
58
|
+
})
|
|
59
|
+
mock_chat_service.end_session = MagicMock()
|
|
60
|
+
mock_factory.create_chat_service.return_value = mock_chat_service
|
|
61
|
+
|
|
62
|
+
yield {
|
|
63
|
+
'factory': mock_factory,
|
|
64
|
+
'config': mock_config,
|
|
65
|
+
'file_manager': mock_file_manager,
|
|
66
|
+
'chat_service': mock_chat_service
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_issue_scenario_fixed_with_correct_user(mock_components):
|
|
71
|
+
"""
|
|
72
|
+
Test the exact scenario from the issue, demonstrating the fix.
|
|
73
|
+
|
|
74
|
+
Before fix:
|
|
75
|
+
- WebSocket would use test@test.com (from fallback)
|
|
76
|
+
- Attempting to access users/agarlan@sandia.gov/generated/file.pdf would fail
|
|
77
|
+
- Error: "Access denied: test@test.com attempted to access users/agarlan@sandia.gov/..."
|
|
78
|
+
|
|
79
|
+
After fix:
|
|
80
|
+
- WebSocket uses agarlan@sandia.gov (from X-User-Email header)
|
|
81
|
+
- Accessing users/agarlan@sandia.gov/generated/file.pdf succeeds
|
|
82
|
+
"""
|
|
83
|
+
client = TestClient(app)
|
|
84
|
+
|
|
85
|
+
# Simulate the production scenario: reverse proxy sets X-User-Email header
|
|
86
|
+
actual_user = "agarlan@sandia.gov"
|
|
87
|
+
|
|
88
|
+
# Connect with X-User-Email header (as set by reverse proxy)
|
|
89
|
+
with client.websocket_connect("/ws", headers={"X-User-Email": actual_user}):
|
|
90
|
+
# Verify the connection was created with the correct user
|
|
91
|
+
call_args = mock_components['factory'].create_chat_service.call_args
|
|
92
|
+
connection_adapter = call_args[0][0]
|
|
93
|
+
|
|
94
|
+
# This should be the actual user, not test@test.com
|
|
95
|
+
assert connection_adapter.user_email == actual_user, (
|
|
96
|
+
f"Expected user to be {actual_user}, but got {connection_adapter.user_email}. "
|
|
97
|
+
"This would cause 'Access denied' errors when accessing user's files."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_issue_scenario_would_fail_without_header():
|
|
102
|
+
"""
|
|
103
|
+
Demonstrate that without the header, the old behavior (test user fallback) occurs.
|
|
104
|
+
This test shows why the issue existed in the first place.
|
|
105
|
+
"""
|
|
106
|
+
with patch('main.app_factory') as mock_factory:
|
|
107
|
+
# Mock config
|
|
108
|
+
mock_config = MagicMock()
|
|
109
|
+
mock_config.app_settings.test_user = 'test@test.com'
|
|
110
|
+
mock_config.app_settings.auth_user_header = 'X-User-Email'
|
|
111
|
+
mock_factory.get_config_manager.return_value = mock_config
|
|
112
|
+
|
|
113
|
+
# Mock chat service
|
|
114
|
+
mock_chat_service = MagicMock()
|
|
115
|
+
mock_chat_service.end_session = MagicMock()
|
|
116
|
+
mock_factory.create_chat_service.return_value = mock_chat_service
|
|
117
|
+
|
|
118
|
+
client = TestClient(app)
|
|
119
|
+
|
|
120
|
+
# Connect WITHOUT X-User-Email header (simulating old behavior or dev mode)
|
|
121
|
+
with client.websocket_connect("/ws"):
|
|
122
|
+
call_args = mock_factory.create_chat_service.call_args
|
|
123
|
+
connection_adapter = call_args[0][0]
|
|
124
|
+
|
|
125
|
+
# Without header, it falls back to test user
|
|
126
|
+
assert connection_adapter.user_email == 'test@test.com', (
|
|
127
|
+
"Without X-User-Email header, should fall back to test user"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# This would cause access denied when trying to access:
|
|
131
|
+
# users/agarlan@sandia.gov/generated/file.pdf
|
|
132
|
+
# because connection is authenticated as test@test.com
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
pytest.main([__file__, "-v"])
|