Files
ss-tools/backend/tests/integration/test_task_manager_integration.py
busya 143f14d516 chore: remainder — backend test infra, agent config, docker, i18n, frontend ui
- Backend: alembic env, config manager/models, dependencies, translate plugin
- Backend tests: async_sync_regression, integration tests, git services, test_agent
- Docker: docker-compose.yml updates
- Agent: qa-tester.md update, semantics-testing SKILL.md update
- Frontend: TopNavbar, sidebarNavigation, FeaturesSettings, FeatureGate
- i18n: assistant.json en/ru locale updates
- New: frontend/src/lib/components/agent/ directory
2026-06-10 15:06:36 +03:00

300 lines
12 KiB
Python

# #region Test.Integration.TaskManager [C:4] [TYPE Module] [SEMANTICS test,integration,task,postgres,testcontainers]
# @BRIEF Integration tests for Task models (TaskRecord, TaskLogRecord) — JSONB params/result,
# FK CASCADE/SET NULL, ILIKE search, composite indexes.
# @RELATION BINDS_TO -> [TaskModels]
# @RELATION DEPENDS_ON -> [IntegrationTestConftest]
# @PRE Docker daemon running; testcontainers PostgresContainer can start.
# @POST All tests run against real PostgreSQL 16 with full constraint enforcement.
# @TEST_EDGE: task_record_crud -> Create, read, update, delete TaskRecord
# @TEST_EDGE: task_jsonb_params -> JSONB params and result persist correctly
# @TEST_EDGE: task_log_cascade -> Deleting task cascades to log records
# @TEST_EDGE: task_log_ilike -> ILIKE search on log message works
# @TEST_EDGE: task_environment_set_null -> Deleting environment sets FK to NULL
# @TEST_EDGE: task_status_filter -> Filtering tasks by status works
# @TEST_EDGE: task_composite_index_query -> Queries using composite indexes are efficient
# @TEST_EDGE: task_log_ordered_query -> Logs can be ordered by timestamp
import pytest
from datetime import UTC, datetime, timedelta
from sqlalchemy import text
from sqlalchemy.exc import IntegrityError
from src.models.task import TaskRecord, TaskLogRecord
class TestTaskRecord:
"""TaskRecord — CRUD, JSONB, FK SET NULL."""
# #region test_task_crud [C:2] [TYPE Function]
def test_task_crud(self, db_session):
"""Full create/read/update/delete cycle for TaskRecord."""
task = TaskRecord(
type="migration",
status="PENDING",
params={"source_env": "prod", "target_env": "staging", "dashboard_ids": [1, 2, 3]},
)
db_session.add(task)
db_session.commit()
# Read
fetched = db_session.query(TaskRecord).filter_by(type="migration").first()
assert fetched is not None
assert fetched.status == "PENDING"
assert fetched.params["source_env"] == "prod"
# Update
fetched.status = "RUNNING"
fetched.started_at = datetime.now(UTC)
db_session.commit()
updated = db_session.query(TaskRecord).filter_by(id=fetched.id).first()
assert updated.status == "RUNNING"
assert updated.started_at is not None
# #endregion test_task_crud
# #region test_task_jsonb_result [C:2] [TYPE Function]
def test_task_jsonb_result(self, db_session):
"""JSONB result with nested data persists correctly."""
task = TaskRecord(
type="backup",
status="SUCCESS",
params={"env": "prod"},
result={
"dashboards_count": 5,
"files": ["dash1.yaml", "dash2.yaml"],
"size_bytes": 1024000,
"checksum": {"algorithm": "sha256", "value": "abc123"},
},
)
db_session.add(task)
db_session.commit()
fetched = db_session.query(TaskRecord).filter_by(type="backup").first()
assert fetched.result["dashboards_count"] == 5
assert fetched.result["checksum"]["algorithm"] == "sha256"
assert len(fetched.result["files"]) == 2
# #endregion test_task_jsonb_result
# #region test_task_error_field [C:2] [TYPE Function]
def test_task_error_field(self, db_session):
"""Error field stores failure details."""
task = TaskRecord(
type="migration",
status="FAILED",
error="Connection timeout after 30s",
)
db_session.add(task)
db_session.commit()
fetched = db_session.query(TaskRecord).filter_by(status="FAILED").first()
assert "Connection timeout" in fetched.error
# #endregion test_task_error_field
# #region test_task_status_filter [C:2] [TYPE Function]
def test_task_status_filter(self, db_session):
"""Filter tasks by status."""
db_session.add_all([
TaskRecord(type="backup", status="SUCCESS"),
TaskRecord(type="backup", status="FAILED"),
TaskRecord(type="migration", status="RUNNING"),
TaskRecord(type="migration", status="PENDING"),
])
db_session.commit()
running = db_session.query(TaskRecord).filter_by(status="RUNNING").all()
assert len(running) == 1
completed = db_session.query(TaskRecord).filter(
TaskRecord.status.in_(["SUCCESS", "FAILED"])
).all()
assert len(completed) == 2
# #endregion test_task_status_filter
class TestTaskLogRecord:
"""TaskLogRecord — FK cascade, ILIKE, ordering."""
# #region test_task_log_create [C:2] [TYPE Function]
def test_task_log_create(self, db_session):
"""Create log entries for a task."""
task = TaskRecord(type="migration", status="RUNNING")
db_session.add(task)
db_session.commit()
now = datetime.now(UTC)
db_session.add_all([
TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="system",
message="Task started"),
TaskLogRecord(task_id=task.id, timestamp=now + timedelta(seconds=1),
level="INFO", source="plugin", message="Processing dashboard 1"),
TaskLogRecord(task_id=task.id, timestamp=now + timedelta(seconds=2),
level="ERROR", source="superset_api", message="API timeout"),
])
db_session.commit()
logs = db_session.query(TaskLogRecord).filter_by(task_id=task.id).all()
assert len(logs) == 3
# #endregion test_task_log_create
# #region test_task_log_cascade_delete [C:2] [TYPE Function]
def test_task_log_cascade_delete(self, db_session):
"""Deleting a task cascades to its log records."""
task = TaskRecord(type="migration", status="SUCCESS")
db_session.add(task)
db_session.commit()
db_session.add(TaskLogRecord(
task_id=task.id, timestamp=datetime.now(UTC),
level="INFO", source="system", message="Log entry",
))
db_session.commit()
assert db_session.query(TaskLogRecord).filter_by(task_id=task.id).count() == 1
db_session.delete(task)
db_session.commit()
assert db_session.query(TaskLogRecord).filter_by(task_id=task.id).count() == 0
# #endregion test_task_log_cascade_delete
# #region test_task_log_ilike_search [C:2] [TYPE Function]
def test_task_log_ilike_search(self, db_session):
"""ILIKE search on message field (PostgreSQL-specific)."""
task = TaskRecord(type="migration", status="RUNNING")
db_session.add(task)
db_session.commit()
now = datetime.now(UTC)
db_session.add_all([
TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="system",
message="Task completed successfully"),
TaskLogRecord(task_id=task.id, timestamp=now, level="ERROR", source="system",
message="Task failed with timeout"),
TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="plugin",
message="Processing dashboard Sales Report"),
])
db_session.commit()
# Search by keyword
result = db_session.query(TaskLogRecord).filter(
TaskLogRecord.message.ilike("%completed%")
).all()
assert len(result) == 1
assert "completed" in result[0].message
# Case-insensitive search
result = db_session.query(TaskLogRecord).filter(
TaskLogRecord.message.ilike("%SALES%")
).all()
assert len(result) == 1
# Multiple matches
result = db_session.query(TaskLogRecord).filter(
TaskLogRecord.message.ilike("%task%")
).all()
assert len(result) == 2
# #endregion test_task_log_ilike_search
# #region test_task_log_ordered_query [C:2] [TYPE Function]
def test_task_log_ordered_query(self, db_session):
"""Logs can be ordered by timestamp with composite index."""
task = TaskRecord(type="migration", status="RUNNING")
db_session.add(task)
db_session.commit()
base = datetime.now(UTC)
for i in range(5):
db_session.add(TaskLogRecord(
task_id=task.id, timestamp=base + timedelta(minutes=i),
level="INFO", source="system",
message=f"Step {i+1}",
))
db_session.commit()
logs = db_session.query(TaskLogRecord).filter_by(task_id=task.id)\
.order_by(TaskLogRecord.timestamp.asc()).all()
assert logs[0].message == "Step 1"
assert logs[-1].message == "Step 5"
# Descending
logs_desc = db_session.query(TaskLogRecord).filter_by(task_id=task.id)\
.order_by(TaskLogRecord.timestamp.desc()).all()
assert logs_desc[0].message == "Step 5"
# #endregion test_task_log_ordered_query
# #region test_task_log_source_filter [C:2] [TYPE Function]
def test_task_log_source_filter(self, db_session):
"""Filter logs by source."""
task = TaskRecord(type="migration", status="RUNNING")
db_session.add(task)
db_session.commit()
now = datetime.now(UTC)
db_session.add_all([
TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="system",
message="System log"),
TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="plugin",
message="Plugin log"),
TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="superset_api",
message="API log"),
])
db_session.commit()
plugin_logs = db_session.query(TaskLogRecord).filter(
TaskLogRecord.task_id == task.id,
TaskLogRecord.source == "plugin",
).all()
assert len(plugin_logs) == 1
assert plugin_logs[0].message == "Plugin log"
# #endregion test_task_log_source_filter
class TestTaskEnvironmentFK:
"""TaskRecord FK to environments — SET NULL behavior."""
# #region test_task_without_environment [C:2] [TYPE Function]
def test_task_without_environment(self, db_session):
"""Task can be created without environment_id (nullable FK)."""
task = TaskRecord(type="local", status="SUCCESS")
db_session.add(task)
db_session.commit()
assert task.environment_id is None
# #endregion test_task_without_environment
class TestTaskTimestampDefaults:
"""TaskRecord server_default and auto-timestamps."""
# #region test_task_created_at_default [C:2] [TYPE Function]
def test_task_created_at_default(self, db_session):
"""created_at is auto-set by server_default."""
task = TaskRecord(type="test", status="PENDING")
db_session.add(task)
db_session.commit()
assert task.created_at is not None
# #endregion test_task_created_at_default
# #region test_task_log_server_default [C:2] [TYPE Function]
def test_task_log_server_default(self, db_session):
"""TaskLogRecord without explicit timestamp gets server_default."""
task = TaskRecord(type="test", status="RUNNING")
db_session.add(task)
db_session.commit()
from datetime import UTC, datetime
now = datetime.now(UTC)
log = TaskLogRecord(
task_id=task.id,
timestamp=now,
level="INFO",
source="system",
message="Explicit timestamp",
)
db_session.add(log)
db_session.commit()
assert log.timestamp is not None
assert log.timestamp == now
# #endregion test_task_log_server_default
# #endregion Test.Integration.TaskManager