Files
OSGKeyboard/OSGKeyboardTests/CloudASRStreamingHelpersTests.swift
Rocky 498f407585 feat(account): add managed credits and cloud gateway
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
2026-08-20 11:43:21 +08:00

145 lines
5.7 KiB
Swift

// CloudASRStreamingHelpersTests.swift
// OSGKeyboardTests
//
// Hermetic fixtures for Volcengine/Bailian streaming helpers and PCM encode.
@testable import OSGKeyboardHostSupport
@testable import OSGKeyboardShared
import XCTest
final class CloudASRStreamingHelpersTests: XCTestCase {
func testPCM16LEClipsAndEncodesLittleEndian() {
let data = CloudASRStreamingPCM.pcm16LE(samples: [0, 1.5, -2.0, 0.5])
XCTAssertEqual(data.count, 8)
// 0 → 0
XCTAssertEqual(data[0], 0)
XCTAssertEqual(data[1], 0)
// 1.5 clipped to Int16.max = 32767 → LE 0xFF 0x7F
XCTAssertEqual(data[2], 0xFF)
XCTAssertEqual(data[3], 0x7F)
// -2.0 clipped to Int16.min = -32768 → LE 0x00 0x80
XCTAssertEqual(data[4], 0x00)
XCTAssertEqual(data[5], 0x80)
}
func testUpsample16kTo24kEmptyInput() {
XCTAssertEqual(CloudASRStreamingPCM.upsample16kTo24k([]), [])
}
func testVolcengineDisplayTextJoinsUtterances() throws {
let payload = Data(#"""
{"result":{"utterances":[{"text":"你好"},{"text":"世界"}]}}
"""#.utf8)
XCTAssertEqual(VolcengineCloudASRClient.displayText(from: payload), "你好世界")
}
func testVolcengineCommittedTextPrefersDefiniteUtterances() throws {
let payload = Data(#"""
{"result":{"utterances":[
{"text":"你好","definite":false},
{"text":"你好世界","definite":true}
]}}
"""#.utf8)
XCTAssertEqual(VolcengineCloudASRClient.committedText(from: payload), "你好世界")
XCTAssertEqual(VolcengineCloudASRClient.displayText(from: payload), "你好你好世界")
}
func testVolcengineCommittedTextEmptyWithoutDefinite() {
let payload = Data(#"""
{"result":{"utterances":[{"text":"临时","definite":false}]}}
"""#.utf8)
XCTAssertEqual(VolcengineCloudASRClient.committedText(from: payload), "")
}
func testVolcengineFirstFramePayloadIncludesNonstreamAndHotwords() throws {
let dict = PersonalDictionary(entries: [
PersonalDictionary.Entry(term: "OSGKeyboard", category: .productName, source: .manual),
PersonalDictionary.Entry(term: "Kubernetes", category: .technical, source: .manual)
])
let data = try VolcengineCloudASRClient.firstFramePayload(
connectID: "conn-1",
dictionary: dict
)
let json = try XCTUnwrap(
JSONSerialization.jsonObject(with: data) as? [String: Any]
)
let request = try XCTUnwrap(json["request"] as? [String: Any])
XCTAssertEqual(request["enable_nonstream"] as? Bool, true)
XCTAssertEqual(request["show_utterances"] as? Bool, true)
let context = try XCTUnwrap(request["context"] as? String)
XCTAssertTrue(context.contains("OSGKeyboard"))
XCTAssertTrue(context.contains("Kubernetes"))
let audio = try XCTUnwrap(json["audio"] as? [String: Any])
XCTAssertEqual(audio["rate"] as? Int, 16_000)
}
func testVolcengineFirstFrameHotwordCapAtEighty() throws {
let entries = (0..<100).map { index in
PersonalDictionary.Entry(
term: "word\(index)",
category: .technical,
source: .manual
)
}
let data = try VolcengineCloudASRClient.firstFramePayload(
connectID: "conn-cap",
dictionary: PersonalDictionary(entries: entries)
)
let json = try XCTUnwrap(
JSONSerialization.jsonObject(with: data) as? [String: Any]
)
let request = try XCTUnwrap(json["request"] as? [String: Any])
let context = try XCTUnwrap(request["context"] as? String)
let contextData = try XCTUnwrap(context.data(using: .utf8))
let contextJSON = try XCTUnwrap(
JSONSerialization.jsonObject(with: contextData) as? [String: Any]
)
let hotwords = try XCTUnwrap(contextJSON["hotwords"] as? [[String: Any]])
XCTAssertEqual(hotwords.count, 80)
}
func testBailianMergeSegmentsNoOverlapAndFullOverlap() {
XCTAssertEqual(
BailianRealtimeASRClient.mergeSegments(["你好", "世界"]),
"你好世界"
)
XCTAssertEqual(
BailianRealtimeASRClient.mergeSegments(["你好世界", "你好世界"]),
"你好世界"
)
// Overlap length 1 is ignored (maxOverlap >= 2 required).
XCTAssertEqual(
BailianRealtimeASRClient.mergeSegments(["你好", "好"]),
"你好好"
)
}
func testBailianFinishTaskMessageStructure() throws {
let json = BailianRealtimeASRClient.finishTaskMessage(taskID: "task-9")
let data = try XCTUnwrap(json.data(using: .utf8))
let body = try XCTUnwrap(
JSONSerialization.jsonObject(with: data) as? [String: Any]
)
let header = try XCTUnwrap(body["header"] as? [String: Any])
XCTAssertEqual(header["action"] as? String, "finish-task")
XCTAssertEqual(header["task_id"] as? String, "task-9")
XCTAssertEqual(header["streaming"] as? String, "duplex")
}
func testBailianRunTaskMessageIncludesVocabularyID() throws {
let json = BailianRealtimeASRClient.runTaskMessage(
taskID: "task-v",
model: "fun-asr-realtime",
vocabularyID: "vocab-123"
)
let data = try XCTUnwrap(json.data(using: .utf8))
let body = try XCTUnwrap(
JSONSerialization.jsonObject(with: data) as? [String: Any]
)
let payload = try XCTUnwrap(body["payload"] as? [String: Any])
let parameters = try XCTUnwrap(payload["parameters"] as? [String: Any])
XCTAssertEqual(parameters["vocabulary_id"] as? String, "vocab-123")
}
}