Files
2026-07-13 12:22:33 +08:00

253 lines
9.0 KiB
Swift

import Foundation
import SwiftUI
import Testing
@testable import CodexBar
@testable import CodexBarCore
@MainActor
struct DeepgramProviderTests {
@Test
func `deepgram field kinds and bindings`() throws {
let suite = "DeepgramProviderTests-field-kinds"
let defaults = try #require(UserDefaults(suiteName: suite))
defaults.removePersistentDomain(forName: suite)
let configStore = testConfigStore(suiteName: suite)
let settings = SettingsStore(
userDefaults: defaults,
configStore: configStore,
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore())
let store = UsageStore(
fetcher: UsageFetcher(environment: [:]),
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings)
let context = ProviderSettingsContext(
provider: .deepgram,
settings: settings,
store: store,
boolBinding: { keyPath in
Binding(
get: { settings[keyPath: keyPath] },
set: { settings[keyPath: keyPath] = $0 })
},
stringBinding: { keyPath in
Binding(
get: { settings[keyPath: keyPath] },
set: { settings[keyPath: keyPath] = $0 })
},
statusText: { _ in nil },
setStatusText: { _, _ in },
lastAppActiveRunAt: { _ in nil },
setLastAppActiveRunAt: { _, _ in },
requestConfirmation: { _ in },
runLoginFlow: {})
let implementation = DeepgramProviderImplementation()
let fields = implementation.settingsFields(context: context)
let apiField = try #require(fields.first(where: { $0.id == "deepgram-api-key" }))
let projectField = try #require(fields.first(where: { $0.id == "deepgram-project-id" }))
#expect(apiField.kind == .secure)
#expect(projectField.kind == .plain)
// Verify bindings update the SettingsStore
apiField.binding.wrappedValue = "dg_test_token"
#expect(settings.deepgramAPIKey == "dg_test_token")
projectField.binding.wrappedValue = "proj-1234"
#expect(settings.deepgramProjectID == "proj-1234")
}
@Test
nonisolated func `parses usage breakdown response into visible usage notes`() throws {
let body = #"""
{
"start": "2025-01-16",
"end": "2025-01-23",
"resolution": {
"units": "day",
"amount": 1
},
"results": [
{
"hours": 1619.7242069444444,
"total_hours": 1621.7395791666668,
"agent_hours": 41.33564388888889,
"tokens_in": 1200,
"tokens_out": 340,
"tts_characters": 9158866,
"requests": 373381,
"grouping": {
"start": "2025-01-16",
"end": "2025-01-16",
"endpoint": "listen"
}
},
{
"hours": 2.25,
"total_hours": 3.5,
"requests": 19,
"grouping": {
"start": "2025-01-17",
"end": "2025-01-17",
"endpoint": "speak"
}
}
]
}
"""#
let updatedAt = Date(timeIntervalSince1970: 123)
let snapshot = try DeepgramUsageFetcher._parseSnapshotForTesting(
Data(body.utf8),
projectID: "project-123",
updatedAt: updatedAt)
let usage = snapshot.toUsageSnapshot()
#expect(snapshot.requests == 373_400)
#expect(snapshot.hours == 1621.9742069444444)
#expect(snapshot.totalHours == 1625.2395791666668)
#expect(snapshot.agentHours == 41.33564388888889)
#expect(snapshot.tokensIn == 1200)
#expect(snapshot.tokensOut == 340)
#expect(snapshot.ttsCharacters == 9_158_866)
#expect(usage.deepgramUsage?.requests == 373_400)
#expect(usage.loginMethod(for: .deepgram) == "Project: project-123")
#expect(usage.deepgramUsage?.displayLines == [
"Requests: 373,400",
"1,622.0 audio hours · 1,625.2 billable hours",
"41.3 agent hours · 1,540 tokens · 9,158,866 TTS chars",
"Period: 2025-01-16 to 2025-01-23",
])
}
@Test
nonisolated func `fetch usage calls breakdown endpoint with token auth`() async throws {
let transport = ProviderHTTPTransportStub { request in
guard let url = request.url else { throw URLError(.badURL) }
#expect(url.path == "/v1/projects/project-123/usage/breakdown")
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
#expect(components?.queryItems?.contains(URLQueryItem(name: "start", value: "2025-01-16")) == true)
#expect(components?.queryItems?.contains(URLQueryItem(name: "end", value: "2025-01-23")) == true)
#expect(request.value(forHTTPHeaderField: "Authorization") == "Token dg-test")
#expect(request.value(forHTTPHeaderField: "Accept") == "application/json")
#expect(request.timeoutInterval == 15)
let body = #"""
{
"start": "2025-01-16",
"end": "2025-01-23",
"resolution": {
"units": "day",
"amount": 1
},
"results": [
{
"hours": 1.5,
"total_hours": 2,
"requests": 7
}
]
}
"""#
return Self.makeResponse(url: url, body: body)
}
let usage = try await DeepgramUsageFetcher.fetchUsage(
apiKey: " dg-test ",
projectID: " project-123 ",
query: DeepgramUsageQuery(start: "2025-01-16", end: "2025-01-23"),
environment: ["DEEPGRAM_API_URL": "https://deepgram.test/v1"],
transport: transport)
#expect(usage.projectID == "project-123")
#expect(usage.requests == 7)
#expect(usage.hours == 1.5)
#expect(usage.totalHours == 2)
let requests = await transport.requests()
#expect(requests.count == 1)
}
@Test
nonisolated func `fetch usage discovers projects when project id is omitted`() async throws {
let transport = ProviderHTTPTransportStub { request in
let url = try #require(request.url)
#expect(request.value(forHTTPHeaderField: "Authorization") == "Token dg-test")
switch url.path {
case "/v1/projects":
return Self.makeResponse(url: url, body: #"""
{
"projects": [
{ "project_id": "project-a", "name": "Alpha" },
{ "project_id": "project-b", "name": "Beta" }
]
}
"""#)
case "/v1/projects/project-a/usage/breakdown":
return Self.makeResponse(url: url, body: #"""
{
"start": "2025-01-16",
"end": "2025-01-23",
"results": [
{ "hours": 1, "total_hours": 2, "requests": 3 }
]
}
"""#)
case "/v1/projects/project-b/usage/breakdown":
return Self.makeResponse(url: url, body: #"""
{
"start": "2025-01-17",
"end": "2025-01-24",
"results": [
{ "hours": 4, "total_hours": 5, "requests": 6 }
]
}
"""#)
default:
throw URLError(.badURL)
}
}
let usage = try await DeepgramUsageFetcher.fetchUsage(
apiKey: "dg-test",
environment: ["DEEPGRAM_API_URL": "https://deepgram.test/v1"],
transport: transport)
#expect(usage.projectID == "all")
#expect(usage.projectCount == 2)
#expect(usage.requests == 9)
#expect(usage.hours == 5)
#expect(usage.totalHours == 7)
#expect(usage.start == "2025-01-16")
#expect(usage.end == "2025-01-24")
#expect(usage.toUsageSnapshot().loginMethod(for: .deepgram) == "2 projects")
let requests = await transport.requests()
#expect(requests.map { $0.url?.path } == [
"/v1/projects",
"/v1/projects/project-a/usage/breakdown",
"/v1/projects/project-b/usage/breakdown",
])
}
private nonisolated static func makeResponse(
url: URL,
body: String,
statusCode: Int = 200) -> (Data, URLResponse)
{
let response = HTTPURLResponse(
url: url,
statusCode: statusCode,
httpVersion: "HTTP/1.1",
headerFields: ["Content-Type": "application/json"])!
return (Data(body.utf8), response)
}
}