From 333b337a3b81a73d4fe89b4874d0a929fb8c5e56 Mon Sep 17 00:00:00 2001 From: ori0o0p Date: Tue, 17 Feb 2026 22:12:53 +0900 Subject: [PATCH] refactor(orchestrator): log git install failures during startup --- .../Zero/Services/ContainerOrchestrator.swift | 12 +++++- .../ContainerOrchestratorTests.swift | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Sources/Zero/Services/ContainerOrchestrator.swift b/Sources/Zero/Services/ContainerOrchestrator.swift index 62157c8..b32fd2d 100644 --- a/Sources/Zero/Services/ContainerOrchestrator.swift +++ b/Sources/Zero/Services/ContainerOrchestrator.swift @@ -25,10 +25,18 @@ class ContainerOrchestrator { // 3-1. Git 설치 (Alpine 기반 이미지에 git이 없을 경우) if image.contains("alpine") { - _ = try? dockerService.executeShell(container: containerName, script: "apk add --no-cache git") + do { + _ = try dockerService.executeShell(container: containerName, script: "apk add --no-cache git") + } catch { + AppLogStore.shared.append("ContainerOrchestrator git install failed (apk): \(error.localizedDescription)") + } } else if image.contains("openjdk") || image.contains("temurin") || image.contains("corretto") { // JDK 이미지들은 보통 Debian/Ubuntu 기반이므로 apt 사용 - _ = try? dockerService.executeShell(container: containerName, script: "apt-get update && apt-get install -y git") + do { + _ = try dockerService.executeShell(container: containerName, script: "apt-get update && apt-get install -y git") + } catch { + AppLogStore.shared.append("ContainerOrchestrator git install failed (apt): \(error.localizedDescription)") + } } // 3. Git Clone (토큰 주입) diff --git a/Tests/ZeroTests/ContainerOrchestratorTests.swift b/Tests/ZeroTests/ContainerOrchestratorTests.swift index 1a7c094..bfcc817 100644 --- a/Tests/ZeroTests/ContainerOrchestratorTests.swift +++ b/Tests/ZeroTests/ContainerOrchestratorTests.swift @@ -7,6 +7,7 @@ class MockDockerService: DockerServiceProtocol { var lastContainerName: String? var lastImageName: String? var executedScripts: [String] = [] + var shellErrorsByCommandSubstring: [String: Error] = [:] // 호환성을 위한 계산 속성 var executedScript: String? { @@ -20,11 +21,17 @@ class MockDockerService: DockerServiceProtocol { } func executeShell(container: String, script: String) throws -> String { + if let match = shellErrorsByCommandSubstring.first(where: { script.contains($0.key) }) { + throw match.value + } executedScripts.append(script) return "mock shell output" } func executeShellStreaming(container: String, script: String, onOutput: @escaping (String) -> Void) throws -> String { + if let match = shellErrorsByCommandSubstring.first(where: { script.contains($0.key) }) { + throw match.value + } executedScripts.append(script) let output = "mock shell output" onOutput(output) @@ -57,9 +64,11 @@ final class ContainerOrchestratorTests: XCTestCase { override func setUp() { super.setUp() testStoreURL = FileManager.default.temporaryDirectory.appendingPathComponent("orchestrator_test.json") + AppLogStore.shared.clear() } override func tearDown() { + AppLogStore.shared.clear() try? FileManager.default.removeItem(at: testStoreURL) super.tearDown() } @@ -100,4 +109,38 @@ final class ContainerOrchestratorTests: XCTestCase { let sessions = try sessionManager.loadSessions() XCTAssertEqual(sessions.count, 1) } + + func testStartSessionLogsGitInstallFailureAndContinues() async throws { + // Given + let mockDocker = MockDockerService() + mockDocker.shellErrorsByCommandSubstring["apk add --no-cache git"] = NSError( + domain: "docker", + code: 1, + userInfo: [NSLocalizedDescriptionKey: "apk failed: temporary network error"] + ) + let sessionManager = SessionManager(storeURL: testStoreURL) + let orchestrator = ContainerOrchestrator( + dockerService: mockDocker, + sessionManager: sessionManager + ) + + let repo = Repository( + id: 1, + name: "test-repo", + fullName: "user/test-repo", + isPrivate: false, + htmlURL: URL(string: "https://github.com/user/test-repo")!, + cloneURL: URL(string: "https://github.com/user/test-repo.git")! + ) + + // When + let session = try await orchestrator.startSession(repo: repo, token: "ghp_test_token") + + // Then + XCTAssertEqual(session.repoURL, repo.cloneURL) + let logs = AppLogStore.shared.recentEntries() + XCTAssertTrue(logs.contains { entry in + entry.contains("ContainerOrchestrator git install failed") && entry.contains("apk failed") + }) + } }