Skip to content

Commit 2017dc1

Browse files
authored
test: migrate negative tokens regression to Effect runner (anomalyco#27141)
1 parent dc9d6a0 commit 2017dc1

1 file changed

Lines changed: 54 additions & 70 deletions

File tree

packages/opencode/test/server/negative-tokens-regression.test.ts

Lines changed: 54 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,77 @@
55
// negative. The pre-fix `safe()` clamp only guarded against non-finite. The
66
// strict `NonNegativeInt` schema then made every load of the message list
77
// fail to encode, killing Desktop boot for every user with such a row.
8-
import { afterEach, describe, expect } from "bun:test"
8+
import { describe, expect } from "bun:test"
99
import { Effect } from "effect"
1010
import { eq } from "drizzle-orm"
1111
import { ModelID, ProviderID } from "../../src/provider/schema"
12-
import { WithInstance } from "../../src/project/with-instance"
1312
import { Server } from "../../src/server/server"
1413
import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
1514
import { Session } from "@/session/session"
1615
import { MessageID, PartID } from "../../src/session/schema"
1716
import * as Database from "@/storage/db"
1817
import { PartTable } from "@/session/session.sql"
1918
import { resetDatabase } from "../fixture/db"
20-
import { disposeAllInstances, tmpdir } from "../fixture/fixture"
21-
import { it } from "../lib/effect"
19+
import { TestInstance } from "../fixture/fixture"
20+
import { testEffect } from "../lib/effect"
2221

23-
afterEach(async () => {
24-
await disposeAllInstances()
25-
await resetDatabase()
26-
})
22+
const it = testEffect(Session.defaultLayer)
2723

28-
function seedNegativeTokenSession(directory: string) {
29-
return Effect.promise(async () =>
30-
WithInstance.provide({
31-
directory,
32-
fn: () =>
33-
Effect.runPromise(
34-
Effect.gen(function* () {
35-
const session = yield* Session.Service
36-
const info = yield* session.create({})
37-
const message = yield* session.updateMessage({
38-
id: MessageID.ascending(),
39-
role: "user",
40-
sessionID: info.id,
41-
agent: "build",
42-
model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") },
43-
time: { created: Date.now() },
44-
})
45-
const partID = PartID.ascending()
46-
yield* session.updatePart({
47-
id: partID,
48-
sessionID: info.id,
49-
messageID: message.id,
50-
type: "step-finish",
51-
reason: "stop",
52-
cost: 0,
53-
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
54-
})
24+
function seedNegativeTokenSession() {
25+
return Effect.gen(function* () {
26+
const session = yield* Session.Service
27+
const info = yield* session.create({})
28+
const message = yield* session.updateMessage({
29+
id: MessageID.ascending(),
30+
role: "user",
31+
sessionID: info.id,
32+
agent: "build",
33+
model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") },
34+
time: { created: Date.now() },
35+
})
36+
const partID = PartID.ascending()
37+
yield* session.updatePart({
38+
id: partID,
39+
sessionID: info.id,
40+
messageID: message.id,
41+
type: "step-finish",
42+
reason: "stop",
43+
cost: 0,
44+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
45+
})
5546

56-
// Bypass the schema with a direct SQL update to install the
57-
// negative `output` value we want to test loading.
58-
Database.use((db) =>
59-
db
60-
.update(PartTable)
61-
.set({
62-
data: {
63-
type: "step-finish",
64-
reason: "stop",
65-
cost: 0,
66-
tokens: { input: 0, output: -42, reasoning: 0, cache: { read: 0, write: 0 } },
67-
} as never,
68-
})
69-
.where(eq(PartTable.id, partID))
70-
.run(),
71-
)
47+
// Bypass the schema with a direct SQL update to install the
48+
// negative `output` value we want to test loading.
49+
Database.use((db) =>
50+
db
51+
.update(PartTable)
52+
.set({
53+
data: {
54+
type: "step-finish",
55+
reason: "stop",
56+
cost: 0,
57+
tokens: { input: 0, output: -42, reasoning: 0, cache: { read: 0, write: 0 } },
58+
} as never,
59+
})
60+
.where(eq(PartTable.id, partID))
61+
.run(),
62+
)
7263

73-
return info.id
74-
}).pipe(Effect.provide(Session.defaultLayer)),
75-
),
76-
}),
77-
)
64+
return info.id
65+
})
7866
}
7967

8068
describe("messages endpoint tolerates legacy negative token counts", () => {
81-
it.live(
69+
it.instance(
8270
"returns 200 even when a step-finish part has tokens.output < 0",
83-
Effect.acquireRelease(
84-
Effect.promise(() => tmpdir({ config: { formatter: false, lsp: false } })),
85-
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
86-
).pipe(
87-
Effect.flatMap((tmp) =>
88-
Effect.gen(function* () {
89-
const sessionID = yield* seedNegativeTokenSession(tmp.path)
90-
const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(tmp.path)}`
91-
const res = yield* Effect.promise(async () => Server.Default().app.request(url))
92-
expect(res.status, "messages endpoint 400'd on legacy negative tokens").not.toBe(400)
93-
}),
94-
),
95-
),
71+
Effect.gen(function* () {
72+
yield* Effect.addFinalizer(() => Effect.promise(() => resetDatabase()))
73+
const test = yield* TestInstance
74+
const sessionID = yield* seedNegativeTokenSession()
75+
const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(test.directory)}`
76+
const res = yield* Effect.promise(async () => Server.Default().app.request(url))
77+
expect(res.status, "messages endpoint 400'd on legacy negative tokens").not.toBe(400)
78+
}),
79+
{ git: true, config: { formatter: false, lsp: false } },
9680
)
9781
})

0 commit comments

Comments
 (0)