chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
#if os(macOS)
|
||||
|
||||
import Foundation
|
||||
import SQLite3
|
||||
import Testing
|
||||
@testable import CodexBarCore
|
||||
|
||||
struct OpenCodeGoLocalUsageReaderTests {
|
||||
@Test
|
||||
func `reads local OpenCode Go history into usage windows`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
try Self.writeAuth(to: env.authURL)
|
||||
try Self.createDatabase(at: env.databaseURL)
|
||||
try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-03-06T11:00:00.000Z"),
|
||||
cost: 3.0)
|
||||
try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-03-05T12:00:00.000Z"),
|
||||
cost: 6.0)
|
||||
try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-02-25T07:53:16.000Z"),
|
||||
cost: 2.0)
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
let snapshot = try reader.fetch(now: Date(timeIntervalSince1970: 1_772_798_400))
|
||||
|
||||
#expect(snapshot.rollingUsagePercent == 25)
|
||||
#expect(snapshot.weeklyUsagePercent == 30)
|
||||
#expect(snapshot.monthlyUsagePercent == 18.3)
|
||||
#expect(snapshot.rollingResetInSec == 14400)
|
||||
#expect(snapshot.weeklyResetInSec == 216_000)
|
||||
#expect(snapshot.monthlyResetInSec == 1_626_796)
|
||||
}
|
||||
|
||||
@Test
|
||||
func `auth without history falls through to web strategy`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
try Self.writeAuth(to: env.authURL)
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
|
||||
#expect(throws: OpenCodeGoLocalUsageError.historyUnavailable("database not found")) {
|
||||
_ = try reader.fetch(now: Date(timeIntervalSince1970: 1_772_798_400))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func `auth with unreadable history falls through to web strategy`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
try Self.writeAuth(to: env.authURL)
|
||||
var db: OpaquePointer?
|
||||
guard sqlite3_open(env.databaseURL.path, &db) == SQLITE_OK else { throw SQLiteTestError.open }
|
||||
sqlite3_close(db)
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
|
||||
#expect(throws: OpenCodeGoLocalUsageError.self) {
|
||||
_ = try reader.fetch(now: Date(timeIntervalSince1970: 1_772_798_400))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func `monthly window keeps original anchor after shorter month clamp`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
try Self.writeAuth(to: env.authURL)
|
||||
try Self.createDatabase(at: env.databaseURL)
|
||||
try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-01-31T00:00:00.000Z"),
|
||||
cost: 1.0)
|
||||
try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-03-29T10:00:00.000Z"),
|
||||
cost: 6.0)
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
let now = Date(timeIntervalSince1970: TimeInterval(Self.ms("2026-03-29T12:00:00.000Z")) / 1000)
|
||||
let snapshot = try reader.fetch(now: now)
|
||||
|
||||
#expect(snapshot.monthlyUsagePercent == 10)
|
||||
#expect(snapshot.monthlyResetInSec == 129_600)
|
||||
}
|
||||
|
||||
@Test
|
||||
func `reads step finish parts when message only stores metadata`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
try Self.writeAuth(to: env.authURL)
|
||||
try Self.createDatabase(at: env.databaseURL)
|
||||
let messageID = try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-03-06T11:00:00.000Z"),
|
||||
cost: nil)
|
||||
try Self.insertStepFinishPart(
|
||||
databaseURL: env.databaseURL,
|
||||
messageID: messageID,
|
||||
createdMs: Self.ms("2026-03-06T11:00:00.000Z"),
|
||||
cost: 3.0)
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
let snapshot = try reader.fetch(now: Date(timeIntervalSince1970: 1_772_798_400))
|
||||
|
||||
#expect(snapshot.rollingUsagePercent == 25)
|
||||
#expect(snapshot.weeklyUsagePercent == 10)
|
||||
#expect(snapshot.monthlyUsagePercent == 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
func `does not double count step finish parts when message has cost`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
try Self.writeAuth(to: env.authURL)
|
||||
try Self.createDatabase(at: env.databaseURL)
|
||||
let messageID = try Self.insertMessage(
|
||||
databaseURL: env.databaseURL,
|
||||
createdMs: Self.ms("2026-03-06T11:00:00.000Z"),
|
||||
cost: 3.0)
|
||||
try Self.insertStepFinishPart(
|
||||
databaseURL: env.databaseURL,
|
||||
messageID: messageID,
|
||||
createdMs: Self.ms("2026-03-06T11:00:00.000Z"),
|
||||
cost: 3.0)
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
let snapshot = try reader.fetch(now: Date(timeIntervalSince1970: 1_772_798_400))
|
||||
|
||||
#expect(snapshot.rollingUsagePercent == 25)
|
||||
#expect(snapshot.weeklyUsagePercent == 10)
|
||||
#expect(snapshot.monthlyUsagePercent == 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
func `missing auth and history is not detected`() throws {
|
||||
let env = try Self.makeEnvironment()
|
||||
defer { try? FileManager.default.removeItem(at: env.root) }
|
||||
|
||||
let reader = OpenCodeGoLocalUsageReader(authURL: env.authURL, databaseURL: env.databaseURL)
|
||||
|
||||
#expect(throws: OpenCodeGoLocalUsageError.notDetected) {
|
||||
_ = try reader.fetch(now: Date(timeIntervalSince1970: 1_772_798_400))
|
||||
}
|
||||
}
|
||||
|
||||
private static func makeEnvironment() throws -> (root: URL, authURL: URL, databaseURL: URL) {
|
||||
let root = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("OpenCodeGoLocalUsageReaderTests-\(UUID().uuidString)", isDirectory: true)
|
||||
let directory = root
|
||||
.appendingPathComponent(".local", isDirectory: true)
|
||||
.appendingPathComponent("share", isDirectory: true)
|
||||
.appendingPathComponent("opencode", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
return (
|
||||
root,
|
||||
directory.appendingPathComponent("auth.json", isDirectory: false),
|
||||
directory.appendingPathComponent("opencode.db", isDirectory: false))
|
||||
}
|
||||
|
||||
private static func writeAuth(to url: URL) throws {
|
||||
let data = Data(#"{"opencode-go":{"type":"api-key","key":"go-key"}}"#.utf8)
|
||||
try data.write(to: url)
|
||||
}
|
||||
|
||||
private static func createDatabase(at url: URL) throws {
|
||||
var db: OpaquePointer?
|
||||
guard sqlite3_open(url.path, &db) == SQLITE_OK else { throw SQLiteTestError.open }
|
||||
defer { sqlite3_close(db) }
|
||||
try Self.exec(
|
||||
db: db,
|
||||
sql: """
|
||||
CREATE TABLE message (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
time_created INTEGER,
|
||||
time_updated INTEGER
|
||||
);
|
||||
CREATE TABLE part (
|
||||
id TEXT PRIMARY KEY,
|
||||
message_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
time_created INTEGER,
|
||||
time_updated INTEGER
|
||||
);
|
||||
""")
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private static func insertMessage(databaseURL: URL, createdMs: Int64, cost: Double?) throws -> String {
|
||||
var db: OpaquePointer?
|
||||
guard sqlite3_open(databaseURL.path, &db) == SQLITE_OK else { throw SQLiteTestError.open }
|
||||
defer { sqlite3_close(db) }
|
||||
|
||||
let messageID = UUID().uuidString
|
||||
var payload: [String: Any] = [
|
||||
"providerID": "opencode-go",
|
||||
"role": "assistant",
|
||||
"time": ["created": createdMs],
|
||||
]
|
||||
if let cost {
|
||||
payload["cost"] = cost
|
||||
}
|
||||
let data = try JSONSerialization.data(withJSONObject: payload)
|
||||
let json = String(data: data, encoding: .utf8) ?? "{}"
|
||||
|
||||
var stmt: OpaquePointer?
|
||||
guard sqlite3_prepare_v2(
|
||||
db,
|
||||
"INSERT INTO message (id, session_id, data, time_created, time_updated) VALUES (?, ?, ?, ?, ?)",
|
||||
-1,
|
||||
&stmt,
|
||||
nil) == SQLITE_OK
|
||||
else { throw SQLiteTestError.prepare }
|
||||
defer { sqlite3_finalize(stmt) }
|
||||
|
||||
let transient = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
||||
sqlite3_bind_text(stmt, 1, messageID, -1, transient)
|
||||
sqlite3_bind_text(stmt, 2, "session-1", -1, transient)
|
||||
sqlite3_bind_text(stmt, 3, json, -1, transient)
|
||||
sqlite3_bind_int64(stmt, 4, createdMs)
|
||||
sqlite3_bind_int64(stmt, 5, createdMs)
|
||||
guard sqlite3_step(stmt) == SQLITE_DONE else { throw SQLiteTestError.step }
|
||||
return messageID
|
||||
}
|
||||
|
||||
private static func insertStepFinishPart(
|
||||
databaseURL: URL,
|
||||
messageID: String,
|
||||
createdMs: Int64,
|
||||
cost: Double) throws
|
||||
{
|
||||
var db: OpaquePointer?
|
||||
guard sqlite3_open(databaseURL.path, &db) == SQLITE_OK else { throw SQLiteTestError.open }
|
||||
defer { sqlite3_close(db) }
|
||||
|
||||
let payload: [String: Any] = [
|
||||
"type": "step-finish",
|
||||
"cost": cost,
|
||||
"tokens": ["input": 1, "output": 1, "total": 2],
|
||||
]
|
||||
let data = try JSONSerialization.data(withJSONObject: payload)
|
||||
let json = String(data: data, encoding: .utf8) ?? "{}"
|
||||
|
||||
var stmt: OpaquePointer?
|
||||
guard sqlite3_prepare_v2(
|
||||
db,
|
||||
"INSERT INTO part (id, message_id, session_id, data, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
-1,
|
||||
&stmt,
|
||||
nil) == SQLITE_OK
|
||||
else { throw SQLiteTestError.prepare }
|
||||
defer { sqlite3_finalize(stmt) }
|
||||
|
||||
let transient = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
||||
sqlite3_bind_text(stmt, 1, UUID().uuidString, -1, transient)
|
||||
sqlite3_bind_text(stmt, 2, messageID, -1, transient)
|
||||
sqlite3_bind_text(stmt, 3, "session-1", -1, transient)
|
||||
sqlite3_bind_text(stmt, 4, json, -1, transient)
|
||||
sqlite3_bind_int64(stmt, 5, createdMs)
|
||||
sqlite3_bind_int64(stmt, 6, createdMs)
|
||||
guard sqlite3_step(stmt) == SQLITE_DONE else { throw SQLiteTestError.step }
|
||||
}
|
||||
|
||||
private static func exec(db: OpaquePointer?, sql: String) throws {
|
||||
var message: UnsafeMutablePointer<CChar>?
|
||||
guard sqlite3_exec(db, sql, nil, nil, &message) == SQLITE_OK else {
|
||||
sqlite3_free(message)
|
||||
throw SQLiteTestError.exec
|
||||
}
|
||||
}
|
||||
|
||||
private static func ms(_ iso: String) -> Int64 {
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
return Int64((formatter.date(from: iso)?.timeIntervalSince1970 ?? 0) * 1000)
|
||||
}
|
||||
|
||||
private enum SQLiteTestError: Error {
|
||||
case open
|
||||
case prepare
|
||||
case step
|
||||
case exec
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user