f512d94c74
git-subtree-dir: clients/ds-app git-subtree-mainline:a24e3e6f22git-subtree-split:5206cf3b0c
67 lines
3.2 KiB
Swift
67 lines
3.2 KiB
Swift
import XCTest
|
|
@testable import AIFabric
|
|
|
|
/// S2-Fa(config 단일소스) + S2-Fb(컴포지션 루트) + S2-Fe(타임아웃/취소 전파).
|
|
final class CompositionTests: XCTestCase {
|
|
|
|
// MARK: S2-Fa — 엔드포인트 단일소스 config
|
|
|
|
func testConfigDefaults() {
|
|
let c = AIProviderConfiguration.resolved(environment: [:])
|
|
XCTAssertEqual(c.localMLXBaseURL.absoluteString, "http://100.76.254.116:8890")
|
|
XCTAssertEqual(c.localMLXModel, "mac-mini-default")
|
|
XCTAssertEqual(c.dsBaseURL.absoluteString, "https://document.hyungi.net/api")
|
|
XCTAssertEqual(c.probeTimeout, 2)
|
|
}
|
|
|
|
func testConfigEnvOverride() {
|
|
let c = AIProviderConfiguration.resolved(environment: [
|
|
"AIFABRIC_LOCALMLX_URL": "http://127.0.0.1:9999",
|
|
"AIFABRIC_LOCALMLX_MODEL": "test-model",
|
|
"AIFABRIC_DS_URL": "http://100.110.63.63:8000/api",
|
|
])
|
|
XCTAssertEqual(c.localMLXBaseURL.absoluteString, "http://127.0.0.1:9999")
|
|
XCTAssertEqual(c.localMLXModel, "test-model")
|
|
XCTAssertEqual(c.dsBaseURL.absoluteString, "http://100.110.63.63:8000/api")
|
|
}
|
|
|
|
// MARK: S2-Fb — 컴포지션 루트 (4 provider 전부 등록)
|
|
|
|
func testMakeDefaultRouterRegistersAllFour() async throws {
|
|
let client = MockDSAskClient(response: try Fixture.decode(AskResponse.self, from: "ask.json"))
|
|
let router = makeDefaultRouter(client: client, session: MockURLProtocol.session(), log: { _ in })
|
|
XCTAssertEqual(Set(router.providers.keys), Set(AIProviderID.allCases))
|
|
// corpusAsk 는 와이어링된 RemoteDS 로 흘러 citations 매핑.
|
|
let resp = try await router.route(AICompletionRequest(task: .corpusAsk, prompt: "p"))
|
|
XCTAssertEqual(resp.providerUsed, .remoteDS)
|
|
XCTAssertEqual(resp.citations.count, 1)
|
|
}
|
|
|
|
func testMakeDefaultRouterVisionFallbackVisible() async throws {
|
|
let client = MockDSAskClient(response: try Fixture.decode(AskResponse.self, from: "ask.json"))
|
|
let sink = LogSink()
|
|
let router = makeDefaultRouter(client: client, session: MockURLProtocol.session(), log: { sink.append($0) })
|
|
// specialized scaffold(불가) → onDevice. (onDevice 가용 여부는 머신 의존 — 최소한 specialized 불가 log 는 떠야)
|
|
_ = try? await router.route(AICompletionRequest(task: .vision, prompt: "p"))
|
|
XCTAssertTrue(sink.lines.contains { $0.contains("specialized") && $0.contains("unavailable") },
|
|
"specialized 불가가 침묵 아닌 log 로 가시화")
|
|
}
|
|
|
|
// MARK: S2-Fe — 취소 전파 (URLSession 경로 아날로그)
|
|
|
|
func testCancellationPropagatesThroughRouter() async throws {
|
|
let router = AIRouter(providers: [.localMLX: SleepingProvider(id: .localMLX)])
|
|
let task = Task {
|
|
try await router.route(AICompletionRequest(task: .classify, prompt: "p"))
|
|
}
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
task.cancel()
|
|
do {
|
|
_ = try await task.value
|
|
XCTFail("취소된 생성은 CancellationError 전파")
|
|
} catch is CancellationError {
|
|
// 기대: URLSession async/Task.sleep 둘 다 취소 자동 honor. OnDevice respond() 도 협조적(S2-3a).
|
|
}
|
|
}
|
|
}
|