Files
hyungi_document_server/clients/ds-app/Tests/AITests/AIRouterSmokeTests.swift
T

85 lines
3.8 KiB
Swift

import XCTest
@testable import AIFabric
/// S2-Fc AI-ROUTING.md §3 mock provider( net 0) .
/// ( + enum + ). latency .
final class AIRouterSmokeTests: XCTestCase {
private func askFixture() throws -> AskResponse {
try Fixture.decode(AskResponse.self, from: "ask.json")
}
/// 1 .
private func healthyRouter(log: @escaping @Sendable (String) -> Void = { _ in }) throws -> AIRouter {
AIRouter(providers: [
.onDevice: EchoProvider(id: .onDevice),
.localMLX: EchoProvider(id: .localMLX),
.remoteDS: RemoteDSProvider(client: MockDSAskClient(response: try askFixture())),
.specialized: SpecializedProvider(), // scaffold
], log: log)
}
func testQuickSummarizeToOnDevice() async throws {
let resp = try await healthyRouter().route(AICompletionRequest(task: .quickSummarize, prompt: "p"))
XCTAssertEqual(resp.providerUsed, .onDevice)
XCTAssertNil(resp.routingNote)
}
func testCorpusAskToRemoteDSWithCitations() async throws {
let resp = try await healthyRouter().route(AICompletionRequest(task: .corpusAsk, prompt: "p"))
XCTAssertEqual(resp.providerUsed, .remoteDS)
XCTAssertEqual(resp.citations.count, 1)
}
func testClassifyToLocalMLX() async throws {
let resp = try await healthyRouter().route(AICompletionRequest(task: .classify, prompt: "p"))
XCTAssertEqual(resp.providerUsed, .localMLX)
XCTAssertNil(resp.routingNote)
}
func testVisionSpecializedUnavailableFallsToOnDeviceVisibly() async throws {
let sink = LogSink()
let resp = try await healthyRouter(log: { sink.append($0) })
.route(AICompletionRequest(task: .vision, prompt: "p"))
XCTAssertEqual(resp.providerUsed, .onDevice)
XCTAssertEqual(resp.routingNote, "fallback from specialized → onDevice")
XCTAssertTrue(sink.lines.contains { $0.contains("specialized") && $0.contains("unavailable") })
}
func testExplicitOnDeviceUnavailableErrorsNoFallback() async throws {
let router = AIRouter(providers: [
.onDevice: EchoProvider(id: .onDevice, available: false),
.localMLX: EchoProvider(id: .localMLX, available: true),
])
do {
_ = try await router.route(AICompletionRequest(task: .quickSummarize, prompt: "p", explicitProvider: .onDevice))
XCTFail("explicit onDevice 불가 → 에러(자동 fallback X)")
} catch let AIRoutingError.explicitProviderUnavailable(id) {
XCTAssertEqual(id, .onDevice)
}
}
func testRuleFallbackOnDeviceDownToLocalMLXWithNote() async throws {
let router = AIRouter(providers: [
.onDevice: EchoProvider(id: .onDevice, available: false),
.localMLX: EchoProvider(id: .localMLX, available: true),
])
let resp = try await router.route(AICompletionRequest(task: .quickSummarize, prompt: "p"))
XCTAssertEqual(resp.providerUsed, .localMLX)
XCTAssertEqual(resp.routingNote, "fallback from onDevice → localMLX")
}
func testAllUnavailableYieldsNoProviderAvailable() async throws {
let router = AIRouter(providers: [
.onDevice: EchoProvider(id: .onDevice, available: false),
.localMLX: EchoProvider(id: .localMLX, available: false),
])
do {
_ = try await router.route(AICompletionRequest(task: .quickSummarize, prompt: "p"))
XCTFail("전부 불가 → noProviderAvailable")
} catch let AIRoutingError.noProviderAvailable(task) {
XCTAssertEqual(task, .quickSummarize)
}
}
}