from __future__ import annotations import sqlite3 import tempfile import unittest from contextlib import closing from pathlib import Path from types import SimpleNamespace from unittest.mock import patch from app.api import apps as apps_api from app.api import tasks as tasks_api from app.storage.database import initialize_database from app.storage.repository import Repository SERVICE_CHECK = { "serviceName": "postgresql.service", "loadState": "loaded", "activeState": "active", "subState": "running", "unitFileState": "enabled", "active": True, "enabled": True, "healthy": True, "status": "active", "readinessStatus": "ready", "checkedAt": "2026-07-20T00:00:00Z", "errorMessage": None, } class ServiceCheckPersistenceTests(unittest.TestCase): def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() root = Path(self.temp_dir.name) fake_settings = SimpleNamespace( db_path=root / "agent.db", cache_dir=root / "cache", log_dir=root / "logs", ) self.settings_patch = patch("app.storage.database.settings", fake_settings) self.settings_patch.start() initialize_database() self.db_path = fake_settings.db_path self.repository = Repository() def tearDown(self) -> None: self.settings_patch.stop() self.temp_dir.cleanup() def test_task_component_api_round_trips_structured_service_checks(self) -> None: self.repository.create_task("task-1", "install", "postgresql", "PostgreSQL") self.repository.create_task_component("task-1", "postgresql", "postgresql", "apt", 10) self.repository.update_task_component( "task-1", "postgresql", service_check_status="healthy", service_checks=[SERVICE_CHECK], ) with patch.object(tasks_api, "repository", self.repository): response = tasks_api.get_task_components("task-1") component = response["components"][0] self.assertEqual(component["serviceCheckStatus"], "healthy") self.assertEqual(component["serviceChecks"], [SERVICE_CHECK]) def test_initialize_database_migrates_existing_component_tables(self) -> None: with closing(sqlite3.connect(self.db_path)) as connection: connection.execute("DROP TABLE task_components") connection.execute("DROP TABLE installed_components") connection.executescript( """ CREATE TABLE installed_components ( id INTEGER PRIMARY KEY AUTOINCREMENT, app_id TEXT NOT NULL, component_id TEXT NOT NULL, type TEXT NOT NULL, install_order INTEGER NOT NULL, status TEXT NOT NULL, package_name TEXT, package_version TEXT, service_name TEXT, docker_image TEXT, docker_digest TEXT, container_name TEXT, compose_project_name TEXT, installed_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(app_id, component_id) ); CREATE TABLE task_components ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, app_id TEXT NOT NULL, component_id TEXT NOT NULL, type TEXT NOT NULL, install_order INTEGER NOT NULL, status TEXT NOT NULL, progress INTEGER DEFAULT 0, current_step TEXT, error_message TEXT, started_at TEXT, finished_at TEXT ); """ ) connection.commit() initialize_database() with closing(sqlite3.connect(self.db_path)) as connection: installed_columns = { row[1] for row in connection.execute("PRAGMA table_info(installed_components)") } task_columns = { row[1] for row in connection.execute("PRAGMA table_info(task_components)") } self.assertTrue({"service_check_status", "service_checks_json"} <= installed_columns) self.assertTrue({"service_check_status", "service_checks_json"} <= task_columns) def test_installed_apps_include_persisted_service_health(self) -> None: self.repository.upsert_installed_app( "postgresql", "PostgreSQL", "16", "manifest-hash", ) self.repository.upsert_installed_component( "postgresql", { "componentId": "postgresql", "type": "apt", "packageName": "postgresql", "version": "16+257build1", "serviceName": "postgresql.service", "serviceCheckStatus": "healthy", "serviceChecks": [SERVICE_CHECK], }, ) manager = SimpleNamespace(get_service_status=lambda _name: dict(SERVICE_CHECK)) with ( patch.object(apps_api, "repository", self.repository), patch.object(apps_api, "ServiceManager", return_value=manager), ): response = apps_api.installed_apps() self.assertEqual(response[0]["serviceCheckStatus"], "healthy") self.assertEqual(response[0]["serviceChecks"][0]["serviceName"], "postgresql.service") self.assertEqual(response[0]["serviceChecks"][0]["componentId"], "postgresql") def test_installed_apps_refresh_postgresql_readiness(self) -> None: stored_check = { **SERVICE_CHECK, "readinessType": "postgresql", "readinessStatus": "ready", } self.repository.upsert_installed_app( "postgresql", "PostgreSQL", "16", "manifest-hash", ) self.repository.upsert_installed_component( "postgresql", { "componentId": "postgresql", "type": "apt", "packageName": "postgresql", "version": "16+257build1", "serviceName": "postgresql.service", "serviceCheckStatus": "healthy", "serviceChecks": [stored_check], }, ) service_manager = SimpleNamespace(get_service_status=lambda _name: dict(SERVICE_CHECK)) apt_installer = SimpleNamespace(is_postgresql_ready=lambda: False) with ( patch.object(apps_api, "repository", self.repository), patch.object(apps_api, "ServiceManager", return_value=service_manager), patch.object(apps_api, "AptInstaller", return_value=apt_installer), ): response = apps_api.installed_apps() self.assertEqual(response[0]["serviceCheckStatus"], "unhealthy") self.assertEqual(response[0]["serviceChecks"][0]["readinessStatus"], "failed") self.assertFalse(response[0]["serviceChecks"][0]["healthy"]) if __name__ == "__main__": unittest.main()