test: migrate negative tokens regression to Effect runner (#27141)

This commit is contained in:
Kit Langton 2026-05-12 16:32:23 -04:00 committed by GitHub
commit 2017dc165c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -5,11 +5,10 @@
// negative. The pre-fix `safe()` clamp only guarded against non-finite. The // negative. The pre-fix `safe()` clamp only guarded against non-finite. The
// strict `NonNegativeInt` schema then made every load of the message list // strict `NonNegativeInt` schema then made every load of the message list
// fail to encode, killing Desktop boot for every user with such a row. // fail to encode, killing Desktop boot for every user with such a row.
import { afterEach, describe, expect } from "bun:test" import { describe, expect } from "bun:test"
import { Effect } from "effect" import { Effect } from "effect"
import { eq } from "drizzle-orm" import { eq } from "drizzle-orm"
import { ModelID, ProviderID } from "../../src/provider/schema" import { ModelID, ProviderID } from "../../src/provider/schema"
import { WithInstance } from "../../src/project/with-instance"
import { Server } from "../../src/server/server" import { Server } from "../../src/server/server"
import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session" import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
import { Session } from "@/session/session" import { Session } from "@/session/session"
@ -17,81 +16,66 @@ import { MessageID, PartID } from "../../src/session/schema"
import * as Database from "@/storage/db" import * as Database from "@/storage/db"
import { PartTable } from "@/session/session.sql" import { PartTable } from "@/session/session.sql"
import { resetDatabase } from "../fixture/db" import { resetDatabase } from "../fixture/db"
import { disposeAllInstances, tmpdir } from "../fixture/fixture" import { TestInstance } from "../fixture/fixture"
import { it } from "../lib/effect" import { testEffect } from "../lib/effect"
afterEach(async () => { const it = testEffect(Session.defaultLayer)
await disposeAllInstances()
await resetDatabase()
})
function seedNegativeTokenSession(directory: string) { function seedNegativeTokenSession() {
return Effect.promise(async () => return Effect.gen(function* () {
WithInstance.provide({ const session = yield* Session.Service
directory, const info = yield* session.create({})
fn: () => const message = yield* session.updateMessage({
Effect.runPromise( id: MessageID.ascending(),
Effect.gen(function* () { role: "user",
const session = yield* Session.Service sessionID: info.id,
const info = yield* session.create({}) agent: "build",
const message = yield* session.updateMessage({ model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") },
id: MessageID.ascending(), time: { created: Date.now() },
role: "user", })
sessionID: info.id, const partID = PartID.ascending()
agent: "build", yield* session.updatePart({
model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, id: partID,
time: { created: Date.now() }, sessionID: info.id,
}) messageID: message.id,
const partID = PartID.ascending() type: "step-finish",
yield* session.updatePart({ reason: "stop",
id: partID, cost: 0,
sessionID: info.id, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
messageID: message.id, })
type: "step-finish",
reason: "stop",
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
})
// Bypass the schema with a direct SQL update to install the // Bypass the schema with a direct SQL update to install the
// negative `output` value we want to test loading. // negative `output` value we want to test loading.
Database.use((db) => Database.use((db) =>
db db
.update(PartTable) .update(PartTable)
.set({ .set({
data: { data: {
type: "step-finish", type: "step-finish",
reason: "stop", reason: "stop",
cost: 0, cost: 0,
tokens: { input: 0, output: -42, reasoning: 0, cache: { read: 0, write: 0 } }, tokens: { input: 0, output: -42, reasoning: 0, cache: { read: 0, write: 0 } },
} as never, } as never,
}) })
.where(eq(PartTable.id, partID)) .where(eq(PartTable.id, partID))
.run(), .run(),
) )
return info.id return info.id
}).pipe(Effect.provide(Session.defaultLayer)), })
),
}),
)
} }
describe("messages endpoint tolerates legacy negative token counts", () => { describe("messages endpoint tolerates legacy negative token counts", () => {
it.live( it.instance(
"returns 200 even when a step-finish part has tokens.output < 0", "returns 200 even when a step-finish part has tokens.output < 0",
Effect.acquireRelease( Effect.gen(function* () {
Effect.promise(() => tmpdir({ config: { formatter: false, lsp: false } })), yield* Effect.addFinalizer(() => Effect.promise(() => resetDatabase()))
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), const test = yield* TestInstance
).pipe( const sessionID = yield* seedNegativeTokenSession()
Effect.flatMap((tmp) => const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(test.directory)}`
Effect.gen(function* () { const res = yield* Effect.promise(async () => Server.Default().app.request(url))
const sessionID = yield* seedNegativeTokenSession(tmp.path) expect(res.status, "messages endpoint 400'd on legacy negative tokens").not.toBe(400)
const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(tmp.path)}` }),
const res = yield* Effect.promise(async () => Server.Default().app.request(url)) { git: true, config: { formatter: false, lsp: false } },
expect(res.status, "messages endpoint 400'd on legacy negative tokens").not.toBe(400)
}),
),
),
) )
}) })