from __future__ import annotations import unittest from unittest.mock import patch from core.config import HARBOR_PROJECT, HARBOR_REGISTRY, load_hosts from monitors.yumi.service_release import build_update_targets_payload, deploy_updated_services_payload from monitors.yumi.service_ops import service_records_by_name class ServiceReleaseTargetsTests(unittest.TestCase): def test_runtime_topology_keeps_singapore_cron_out_of_golang_release(self) -> None: hosts = load_hosts() cron_hosts = [host for host in hosts if host["host"] == "cron-sg-1"] records = service_records_by_name().get("golang") or [] self.assertEqual(len(cron_hosts), 1) self.assertEqual(cron_hosts[0]["ip"], "10.3.1.14") self.assertEqual(cron_hosts[0]["instanceId"], "ins-nt6bsay0") self.assertEqual(cron_hosts[0]["group"], "cron") self.assertEqual(cron_hosts[0]["services"], []) self.assertFalse(any(record["host"] == "cron-sg-1" for record in records)) @patch("monitors.yumi.service_release.gateway_clb_configuration_error", return_value="gateway CLB protection requires .env") @patch("monitors.yumi.service_release.frontend_release_enabled", return_value=False) @patch("monitors.yumi.service_release.service_repository_ref", side_effect=lambda service: f"repo/{service}") @patch("monitors.yumi.service_release.buildable_service_names", return_value=["gateway"]) @patch("monitors.yumi.service_release.service_records_by_name") @patch("monitors.yumi.service_release.load_local_host_compose_model") def test_build_update_targets_payload_adds_gateway_clb_warning( self, load_local_host_compose_model_mock, service_records_by_name_mock, buildable_service_names_mock, service_repository_ref_mock, frontend_release_enabled_mock, gateway_clb_configuration_error_mock, ) -> None: del buildable_service_names_mock del service_repository_ref_mock del frontend_release_enabled_mock del gateway_clb_configuration_error_mock service_records_by_name_mock.return_value = { "gateway": [ { "host": "gateway-1", "ip": "10.0.0.1", "instanceId": "ins-gateway-1", "port": 8080, "path": "/health", } ] } load_local_host_compose_model_mock.return_value = { "services": { "gateway": {"image": "registry/gateway:main"}, } } payload = build_update_targets_payload() self.assertTrue(payload["ok"]) self.assertEqual(payload["items"][0]["warnings"], ["gateway CLB protection requires .env"]) @patch("monitors.yumi.service_release.frontend_release_enabled", return_value=False) @patch("monitors.yumi.service_release.service_repository_ref", side_effect=lambda service: f"repo/{service}") @patch("monitors.yumi.service_release.buildable_service_names", return_value=["auth", "gateway"]) @patch("monitors.yumi.service_release.service_records_by_name") @patch("monitors.yumi.service_release.load_local_host_compose_model") def test_build_update_targets_payload_reuses_host_compose_model( self, load_local_host_compose_model_mock, service_records_by_name_mock, buildable_service_names_mock, service_repository_ref_mock, frontend_release_enabled_mock, ) -> None: del buildable_service_names_mock, service_repository_ref_mock, frontend_release_enabled_mock service_records_by_name_mock.return_value = { "auth": [ { "host": "app-1", "ip": "10.0.0.1", "instanceId": "ins-app-1", "port": 8080, "path": "/auth", } ], "gateway": [ { "host": "app-1", "ip": "10.0.0.1", "instanceId": "ins-app-1", "port": 9000, "path": "/gateway", } ], } load_local_host_compose_model_mock.return_value = { "services": { "auth": {"image": "registry/auth:main"}, "gateway": {"image": "registry/gateway:main"}, } } payload = build_update_targets_payload() self.assertTrue(payload["ok"]) self.assertEqual(load_local_host_compose_model_mock.call_count, 1) self.assertEqual([item["service"] for item in payload["items"]], ["auth", "gateway"]) self.assertEqual(payload["items"][0]["currentImages"], ["registry/auth:main"]) self.assertEqual(payload["items"][1]["currentImages"], ["registry/gateway:main"]) @patch("monitors.yumi.service_release.frontend_release_enabled", return_value=False) @patch("monitors.yumi.service_release.service_repository_ref", side_effect=lambda service: f"repo/{service}") @patch("monitors.yumi.service_release.buildable_service_names", return_value=["auth"]) @patch("monitors.yumi.service_release.service_records_by_name") @patch("monitors.yumi.service_release.load_local_host_compose_model", side_effect=RuntimeError("broken compose")) def test_build_update_targets_payload_keeps_items_when_compose_unavailable( self, load_local_host_compose_model_mock, service_records_by_name_mock, buildable_service_names_mock, service_repository_ref_mock, frontend_release_enabled_mock, ) -> None: del load_local_host_compose_model_mock del buildable_service_names_mock, service_repository_ref_mock, frontend_release_enabled_mock service_records_by_name_mock.return_value = { "auth": [ { "host": "app-1", "ip": "10.0.0.1", "instanceId": "ins-app-1", "port": 8080, "path": "/auth", } ] } payload = build_update_targets_payload() self.assertTrue(payload["ok"]) self.assertEqual(len(payload["items"]), 1) self.assertEqual(payload["items"][0]["currentImages"], []) self.assertEqual(payload["items"][0]["warnings"], ["app-1: broken compose"]) @patch("monitors.yumi.service_release.frontend_release_enabled", return_value=False) @patch("monitors.yumi.service_release.buildable_service_names", return_value=["auth"]) @patch("monitors.yumi.service_release.service_records_by_name") @patch("monitors.yumi.service_release.load_local_host_compose_model", side_effect=RuntimeError("broken compose")) def test_build_update_targets_payload_falls_back_repository_when_compose_unavailable( self, load_local_host_compose_model_mock, service_records_by_name_mock, buildable_service_names_mock, frontend_release_enabled_mock, ) -> None: del load_local_host_compose_model_mock del buildable_service_names_mock, frontend_release_enabled_mock service_records_by_name_mock.return_value = { "auth": [ { "host": "app-1", "ip": "10.0.0.1", "instanceId": "ins-app-1", "port": 8080, "path": "/auth", } ] } payload = build_update_targets_payload() self.assertTrue(payload["ok"]) self.assertEqual(len(payload["items"]), 1) self.assertEqual(payload["items"][0]["repository"], f"{HARBOR_REGISTRY}/{HARBOR_PROJECT}/java/chatapp3-auth") @patch("monitors.yumi.service_release.ADMIN_FRONTEND_INSTANCE_ID", "ins-admin") @patch("monitors.yumi.service_release.ADMIN_FRONTEND_IP", "10.2.1.2") @patch("monitors.yumi.service_release.ADMIN_FRONTEND_HOST", "admin-platform") @patch("monitors.yumi.service_release.ADMIN_FRONTEND_REPO_URL", "git@example.com:hy/chatapp3-admin.git") @patch("monitors.yumi.service_release.ADMIN_FRONTEND_SITE_DIR", "/opt/yumi-admin/frontend-admin/site/yumiadmin-acc") @patch("monitors.yumi.service_release.frontend_release_enabled", return_value=True) @patch( "monitors.yumi.service_release.frontend_release_display", return_value={ "currentLabel": "main@abc123", "currentTitle": "abc123", "detailLabel": "abc123", "detailTitle": "abc123", "copyValue": "abc123", }, ) @patch("monitors.yumi.service_release.buildable_service_names", return_value=["admin"]) @patch("monitors.yumi.service_release.service_records_by_name", return_value={}) def test_build_update_targets_payload_adds_admin_frontend_target( self, service_records_by_name_mock, buildable_service_names_mock, frontend_release_display_mock, frontend_release_enabled_mock, ) -> None: del service_records_by_name_mock, buildable_service_names_mock del frontend_release_display_mock, frontend_release_enabled_mock payload = build_update_targets_payload() self.assertTrue(payload["ok"]) self.assertEqual(len(payload["items"]), 1) admin = payload["items"][0] self.assertEqual(admin["service"], "admin") self.assertEqual(admin["kind"], "frontend") self.assertEqual(admin["repository"], "git@example.com:hy/chatapp3-admin.git") self.assertEqual(admin["hosts"][0]["host"], "admin-platform") self.assertEqual(admin["hosts"][0]["ip"], "10.2.1.2") @patch("monitors.yumi.service_release.frontend_release_enabled", return_value=True) @patch("monitors.yumi.service_release.service_records_by_name", return_value={}) @patch( "monitors.yumi.service_release.deploy_frontend_site", return_value={ "ok": True, "updatedAt": "2026-04-24T00:00:00+00:00", "services": ["admin"], "build": [{"kind": "frontend", "gitRef": "release/admin", "commit": "abc123"}], "templates": [], "preparedHosts": [], "steps": [{"service": "admin", "host": "admin-platform", "status": "success"}], "message": "service update complete", }, ) def test_deploy_updated_services_payload_dispatches_admin_frontend( self, deploy_frontend_site_mock, service_records_by_name_mock, frontend_release_enabled_mock, ) -> None: del service_records_by_name_mock, frontend_release_enabled_mock payload = deploy_updated_services_payload( ["admin"], java_git_ref="main", go_git_ref="main", admin_git_ref="release/admin", image_tag="", skip_maven_build=False, ) deploy_frontend_site_mock.assert_called_once_with("admin", git_ref="release/admin", progress=None) self.assertTrue(payload["ok"]) self.assertEqual(payload["services"], ["admin"]) self.assertEqual(payload["build"][0]["kind"], "frontend") self.assertEqual(payload["steps"][0]["service"], "admin") if __name__ == "__main__": unittest.main()