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 @Serializable
private data class TalkConfigContractFixture( private data class TalkConfigContractFixture(
@SerialName("selectionCases") val selectionCases: List<SelectionCase>, @SerialName("selectionCases") val selectionCases: List<SelectionCase>,
@SerialName("timeoutCases") val timeoutCases: List<TimeoutCase>,
) { ) {
@Serializable @Serializable
data class SelectionCase( data class SelectionCase(
@@ -29,6 +30,15 @@ private data class TalkConfigContractFixture(
val provider: String, val provider: String,
val normalizedPayload: Boolean, val normalizedPayload: Boolean,
val voiceId: String? = null, 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, expected.voiceId,
(selection?.config?.get("voiceId") as? JsonPrimitive)?.content, (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) 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 { private fun loadFixtures(): TalkConfigContractFixture {
val fixturePath = findFixtureFile() val fixturePath = findFixtureFile()
return json.decodeFromString(File(fixturePath).readText()) return json.decodeFromString(File(fixturePath).readText())

View File

@@ -4,6 +4,7 @@ import Testing
private struct TalkConfigContractFixture: Decodable { private struct TalkConfigContractFixture: Decodable {
let selectionCases: [SelectionCase] let selectionCases: [SelectionCase]
let timeoutCases: [TimeoutCase]
struct SelectionCase: Decodable { struct SelectionCase: Decodable {
let id: String let id: String
@@ -17,6 +18,14 @@ private struct TalkConfigContractFixture: Decodable {
let provider: String let provider: String
let normalizedPayload: Bool let normalizedPayload: Bool
let voiceId: String? 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?.provider == expected.provider)
#expect(selection?.normalizedPayload == expected.normalizedPayload) #expect(selection?.normalizedPayload == expected.normalizedPayload)
#expect(selection?.config["voiceId"]?.stringValue == expected.voiceId) #expect(selection?.config["voiceId"]?.stringValue == expected.voiceId)
#expect(selection?.config["apiKey"]?.stringValue == expected.apiKey)
} else { } else {
#expect(selection == nil) #expect(selection == nil)
} }
#expect(fixture.payloadValid == (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 fs from "node:fs";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { buildTalkConfigResponse } from "../../config/talk.js";
import { validateTalkConfigResult } from "./index.js"; import { validateTalkConfigResult } from "./index.js";
type ExpectedSelection = { type ExpectedSelection = {
provider: string; provider: string;
normalizedPayload: boolean; normalizedPayload: boolean;
voiceId?: string; voiceId?: string;
apiKey?: string;
}; };
type SelectionContractCase = { type SelectionContractCase = {
@@ -16,8 +18,16 @@ type SelectionContractCase = {
talk: Record<string, unknown>; talk: Record<string, unknown>;
}; };
type TimeoutContractCase = {
id: string;
fallback: number;
expectedTimeoutMs: number;
talk: Record<string, unknown>;
};
type TalkConfigContractFixture = { type TalkConfigContractFixture = {
selectionCases: SelectionContractCase[]; selectionCases: SelectionContractCase[];
timeoutCases: TimeoutContractCase[];
}; };
const fixturePath = new URL("../../../test-fixtures/talk-config-contract.json", import.meta.url); const fixturePath = new URL("../../../test-fixtures/talk-config-contract.json", import.meta.url);
@@ -42,9 +52,11 @@ describe("talk.config contract fixtures", () => {
provider?: string; provider?: string;
config?: { config?: {
voiceId?: string; voiceId?: string;
apiKey?: string;
}; };
}; };
voiceId?: string; voiceId?: string;
apiKey?: string;
}; };
expect(talk.resolved?.provider ?? fixture.defaultProvider).toBe( expect(talk.resolved?.provider ?? fixture.defaultProvider).toBe(
fixture.expectedSelection.provider, fixture.expectedSelection.provider,
@@ -52,6 +64,14 @@ describe("talk.config contract fixtures", () => {
expect(talk.resolved?.config?.voiceId ?? talk.voiceId).toBe( expect(talk.resolved?.config?.voiceId ?? talk.voiceId).toBe(
fixture.expectedSelection.voiceId, 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": { "expectedSelection": {
"provider": "elevenlabs", "provider": "elevenlabs",
"normalizedPayload": true, "normalizedPayload": true,
"voiceId": "voice-resolved" "voiceId": "voice-resolved",
"apiKey": "resolved-key"
}, },
"talk": { "talk": {
"resolved": { "resolved": {
"provider": "elevenlabs", "provider": "elevenlabs",
"config": { "config": {
"voiceId": "voice-resolved" "voiceId": "voice-resolved",
"apiKey": "resolved-key"
} }
}, },
"provider": "elevenlabs", "provider": "elevenlabs",
"providers": { "providers": {
"elevenlabs": { "elevenlabs": {
"voiceId": "voice-normalized" "voiceId": "voice-normalized",
"apiKey": "normalized-key"
} }
}, },
"voiceId": "voice-legacy" "voiceId": "voice-legacy",
"apiKey": "legacy-key"
} }
}, },
{ {
@@ -77,12 +81,63 @@
"expectedSelection": { "expectedSelection": {
"provider": "elevenlabs", "provider": "elevenlabs",
"normalizedPayload": false, "normalizedPayload": false,
"voiceId": "voice-legacy" "voiceId": "voice-legacy",
"apiKey": "legacy-key"
}, },
"talk": { "talk": {
"voiceId": "voice-legacy", "voiceId": "voice-legacy",
"apiKey": "xxxxx" "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
}
}
] ]
} }