test: expand talk config contract fixtures

This commit is contained in:
Peter Steinberger 2026-03-08 16:28:06 +00:00
parent cee2f3e8b4
commit 371c53b282
4 changed files with 124 additions and 5 deletions

View File

@ -14,6 +14,7 @@ import org.junit.Test
@Serializable
private data class TalkConfigContractFixture(
@SerialName("selectionCases") val selectionCases: List<SelectionCase>,
@SerialName("timeoutCases") val timeoutCases: List<TimeoutCase>,
) {
@Serializable
data class SelectionCase(
@ -29,6 +30,15 @@ private data class TalkConfigContractFixture(
val provider: String,
val normalizedPayload: Boolean,
val voiceId: String? = null,
val apiKey: String? = null,
)
@Serializable
data class TimeoutCase(
val id: String,
val fallback: Long,
val expectedTimeoutMs: Long,
val talk: JsonObject,
)
}
@ -52,10 +62,24 @@ class TalkModeConfigContractTest {
expected.voiceId,
(selection?.config?.get("voiceId") as? JsonPrimitive)?.content,
)
assertEquals(
fixture.id,
expected.apiKey,
(selection?.config?.get("apiKey") as? JsonPrimitive)?.content,
)
assertEquals(fixture.id, true, fixture.payloadValid)
}
}
@Test
fun timeoutFixtures() {
for (fixture in loadFixtures().timeoutCases) {
val timeout = TalkModeGatewayConfigParser.resolvedSilenceTimeoutMs(fixture.talk)
assertEquals(fixture.id, fixture.expectedTimeoutMs, timeout)
assertEquals(fixture.id, TalkDefaults.defaultSilenceTimeoutMs, fixture.fallback)
}
}
private fun loadFixtures(): TalkConfigContractFixture {
val fixturePath = findFixtureFile()
return json.decodeFromString(File(fixturePath).readText())

View File

@ -4,6 +4,7 @@ import Testing
private struct TalkConfigContractFixture: Decodable {
let selectionCases: [SelectionCase]
let timeoutCases: [TimeoutCase]
struct SelectionCase: Decodable {
let id: String
@ -17,6 +18,14 @@ private struct TalkConfigContractFixture: Decodable {
let provider: String
let normalizedPayload: Bool
let voiceId: String?
let apiKey: String?
}
struct TimeoutCase: Decodable {
let id: String
let fallback: Int
let expectedTimeoutMs: Int
let talk: [String: AnyCodable]
}
}
@ -51,10 +60,21 @@ struct TalkConfigContractTests {
#expect(selection?.provider == expected.provider)
#expect(selection?.normalizedPayload == expected.normalizedPayload)
#expect(selection?.config["voiceId"]?.stringValue == expected.voiceId)
#expect(selection?.config["apiKey"]?.stringValue == expected.apiKey)
} else {
#expect(selection == nil)
}
#expect(fixture.payloadValid == (selection != nil))
}
}
@Test func timeoutFixtures() throws {
for fixture in try TalkConfigContractFixtureLoader.load().timeoutCases {
#expect(
TalkConfigParsing.resolvedSilenceTimeoutMs(
fixture.talk,
fallback: fixture.fallback) == fixture.expectedTimeoutMs,
"\(fixture.id)")
}
}
}

View File

@ -1,11 +1,13 @@
import fs from "node:fs";
import { describe, expect, it } from "vitest";
import { buildTalkConfigResponse } from "../../config/talk.js";
import { validateTalkConfigResult } from "./index.js";
type ExpectedSelection = {
provider: string;
normalizedPayload: boolean;
voiceId?: string;
apiKey?: string;
};
type SelectionContractCase = {
@ -16,8 +18,16 @@ type SelectionContractCase = {
talk: Record<string, unknown>;
};
type TimeoutContractCase = {
id: string;
fallback: number;
expectedTimeoutMs: number;
talk: Record<string, unknown>;
};
type TalkConfigContractFixture = {
selectionCases: SelectionContractCase[];
timeoutCases: TimeoutContractCase[];
};
const fixturePath = new URL("../../../test-fixtures/talk-config-contract.json", import.meta.url);
@ -42,9 +52,11 @@ describe("talk.config contract fixtures", () => {
provider?: string;
config?: {
voiceId?: string;
apiKey?: string;
};
};
voiceId?: string;
apiKey?: string;
};
expect(talk.resolved?.provider ?? fixture.defaultProvider).toBe(
fixture.expectedSelection.provider,
@ -52,6 +64,14 @@ describe("talk.config contract fixtures", () => {
expect(talk.resolved?.config?.voiceId ?? talk.voiceId).toBe(
fixture.expectedSelection.voiceId,
);
expect(talk.resolved?.config?.apiKey ?? talk.apiKey).toBe(fixture.expectedSelection.apiKey);
});
}
for (const fixture of fixtures.timeoutCases) {
it(`timeout:${fixture.id}`, () => {
const payload = buildTalkConfigResponse(fixture.talk);
expect(payload?.silenceTimeoutMs ?? fixture.fallback).toBe(fixture.expectedTimeoutMs);
});
}
});

View File

@ -7,22 +7,26 @@
"expectedSelection": {
"provider": "elevenlabs",
"normalizedPayload": true,
"voiceId": "voice-resolved"
"voiceId": "voice-resolved",
"apiKey": "resolved-key"
},
"talk": {
"resolved": {
"provider": "elevenlabs",
"config": {
"voiceId": "voice-resolved"
"voiceId": "voice-resolved",
"apiKey": "resolved-key"
}
},
"provider": "elevenlabs",
"providers": {
"elevenlabs": {
"voiceId": "voice-normalized"
"voiceId": "voice-normalized",
"apiKey": "normalized-key"
}
},
"voiceId": "voice-legacy"
"voiceId": "voice-legacy",
"apiKey": "legacy-key"
}
},
{
@ -77,12 +81,63 @@
"expectedSelection": {
"provider": "elevenlabs",
"normalizedPayload": false,
"voiceId": "voice-legacy"
"voiceId": "voice-legacy",
"apiKey": "legacy-key"
},
"talk": {
"voiceId": "voice-legacy",
"apiKey": "xxxxx"
}
}
],
"timeoutCases": [
{
"id": "integer_timeout_kept",
"fallback": 700,
"expectedTimeoutMs": 1500,
"talk": {
"silenceTimeoutMs": 1500
}
},
{
"id": "integer_like_double_timeout_kept",
"fallback": 700,
"expectedTimeoutMs": 1500,
"talk": {
"silenceTimeoutMs": 1500.0
}
},
{
"id": "zero_timeout_falls_back",
"fallback": 700,
"expectedTimeoutMs": 700,
"talk": {
"silenceTimeoutMs": 0
}
},
{
"id": "boolean_timeout_falls_back",
"fallback": 700,
"expectedTimeoutMs": 700,
"talk": {
"silenceTimeoutMs": true
}
},
{
"id": "string_timeout_falls_back",
"fallback": 700,
"expectedTimeoutMs": 700,
"talk": {
"silenceTimeoutMs": "1500"
}
},
{
"id": "fractional_timeout_falls_back",
"fallback": 700,
"expectedTimeoutMs": 700,
"talk": {
"silenceTimeoutMs": 1500.5
}
}
]
}