CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
162 lines
5.6 KiB
Python
162 lines
5.6 KiB
Python
"""EPIC R10-T05: End-to-End Release Smoke Test Suite.
|
|
|
|
Runs headless E2E smoke tests covering the 5 core runtime subsystems before release:
|
|
Scenario 1: Application Composition Root & MainWindow Bootstrap
|
|
Scenario 2: Chat Turn Lifecycle & AgentEvent Stream
|
|
Scenario 3: Task Scheduling, Calculation & Dispatch
|
|
Scenario 4: Workspace Isolation & File Operations
|
|
Scenario 5: Configuration & Secrets Persistence Round-trip
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
# Ensure Qt runs offscreen in headless environments
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from cowork_local.application.conversations.conversation_application_service import (
|
|
ConversationApplicationService,
|
|
)
|
|
from cowork_local.application.scheduling.task_application_service import (
|
|
TaskApplicationService,
|
|
)
|
|
from cowork_local.application.workspaces.file_workspace_service import (
|
|
FileWorkspaceService,
|
|
)
|
|
from cowork_local.domain.agents.conversation_execution_request import (
|
|
ConversationExecutionRequest,
|
|
)
|
|
from cowork_local.domain.workspaces.workspace_session import WorkspaceSession
|
|
from cowork_local.infrastructure.config.json_config_repository import (
|
|
JsonConfigRepository,
|
|
)
|
|
from cowork_local.infrastructure.filesystem.execution_workspace import (
|
|
ExecutionWorkspace,
|
|
)
|
|
from cowork_local.infrastructure.persistence.json.task_repository_impl import (
|
|
TaskRepository,
|
|
)
|
|
from cowork_local.presentation.shell.bootstrap import build_config, build_context
|
|
from cowork_local.presentation.shell.main_window import MainWindow
|
|
from cowork_local.state import AppContext
|
|
from cowork_local.tests.fakes.turn_runtime_fakes import (
|
|
FakeModelCall,
|
|
FakeReply,
|
|
FakeToolRuntime,
|
|
make_request,
|
|
run_turn,
|
|
)
|
|
|
|
pytest.importorskip("PySide6", reason="PySide6 required for E2E GUI smoke tests")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def qt_app():
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
return QApplication.instance() or QApplication([])
|
|
|
|
|
|
def test_scenario_1_bootstrap_and_main_window(qt_app, tmp_path):
|
|
"""Scenario 1: Test Composition Root and MainWindow initialization."""
|
|
config_path = tmp_path / "config.json"
|
|
repo = build_config(config_path)
|
|
assert repo is not None
|
|
|
|
ctx = build_context(config_path)
|
|
assert isinstance(ctx, AppContext)
|
|
|
|
# Instantiate MainWindow
|
|
window = MainWindow(ctx)
|
|
assert window is not None
|
|
assert window.ctx is ctx
|
|
assert hasattr(window, "pages")
|
|
assert hasattr(window, "sidebar")
|
|
assert hasattr(window, "workspace")
|
|
window.close()
|
|
|
|
|
|
def test_scenario_2_chat_turn_lifecycle(tmp_path):
|
|
"""Scenario 2: Test Chat turn execution with pure Python service and FakeModelCall."""
|
|
model = FakeModelCall([FakeReply(content="Hello from release smoke test!", chunks=["Hello from ", "release smoke test!"])])
|
|
service = ConversationApplicationService(model, FakeToolRuntime())
|
|
|
|
req = make_request(prompt="Run release smoke test")
|
|
result, events = run_turn(service, request=req)
|
|
|
|
assert result.ok is True
|
|
assert result.final_text == "Hello from release smoke test!"
|
|
assert len(events) >= 1
|
|
|
|
|
|
def test_scenario_3_task_scheduling_and_dispatch(tmp_path):
|
|
"""Scenario 3: Test task repository and application service dispatch."""
|
|
repo = TaskRepository(directory=tmp_path)
|
|
|
|
task_payload = {
|
|
"task_id": "smoke_task_1",
|
|
"title": "Release Smoke Task",
|
|
"status": "backlog",
|
|
"task_type": "cowork",
|
|
"enabled": True,
|
|
"run_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
repo.save(task_payload)
|
|
|
|
# Verify task retrieval
|
|
retrieved = repo.get("smoke_task_1")
|
|
assert retrieved is not None
|
|
assert retrieved["title"] == "Release Smoke Task"
|
|
|
|
# Test TaskApplicationService operations
|
|
fake_scheduler = MagicMock()
|
|
fake_scheduler.run_task_now.return_value = True
|
|
|
|
service = TaskApplicationService(repository=repo, run_now=fake_scheduler.run_task_now)
|
|
result = service.run_now("smoke_task_1")
|
|
assert result.ok is True
|
|
fake_scheduler.run_task_now.assert_called_once_with("smoke_task_1")
|
|
|
|
|
|
def test_scenario_4_workspace_isolation_and_files(tmp_path):
|
|
"""Scenario 4: Test file workspace isolation and directory containment."""
|
|
ws_root = tmp_path / "smoke_workspace"
|
|
ws_root.mkdir()
|
|
|
|
session = WorkspaceSession.unscoped(ws_root)
|
|
assert session.is_allowed(ws_root / "output.txt") is True
|
|
assert session.is_allowed(tmp_path / "outside.txt") is False
|
|
|
|
exec_ws = ExecutionWorkspace(session=session, turn_id="turn-smoke")
|
|
exec_ws.ensure_dirs()
|
|
assert (ws_root / ".scratch").is_dir()
|
|
|
|
# FileWorkspaceService operations
|
|
service = FileWorkspaceService(session)
|
|
write_res = service.write_file("smoke_note.txt", "Smoke test content")
|
|
assert (ws_root / "smoke_note.txt").exists()
|
|
|
|
read_res = service.read_preview("smoke_note.txt")
|
|
assert "Smoke test content" in str(read_res)
|
|
|
|
|
|
def test_scenario_5_config_and_secrets_persistence(tmp_path):
|
|
"""Scenario 5: Test JsonConfigRepository persistence with atomic write."""
|
|
config_file = tmp_path / "config.json"
|
|
repo = JsonConfigRepository.load(config_file, secrets=None)
|
|
|
|
# Set and persist values
|
|
repo.data["appearance"] = {"theme": "dark"}
|
|
repo.data["general"] = {"language": "vi"}
|
|
repo.save()
|
|
|
|
# Reload from disk and verify
|
|
reloaded = JsonConfigRepository.load(config_file, secrets=None)
|
|
assert reloaded.data.get("appearance", {}).get("theme") == "dark"
|
|
assert reloaded.data.get("general", {}).get("language") == "vi"
|