import { describe, expect, test } from "bun:test"
type Store = { input: string }
type SubmitResult = { sessionID: string; text: string }
type Harness = {
store: Store
submissions: SubmitResult[]
createSession(): Promise<string>
sendPrompt(sessionID: string, text: string): Promise<void>
}
function createHarness(opts: { sessionCreateDelayMs: number }): Harness {
let sessionCounter = 0
const submissions: SubmitResult[] = []
return {
store: { input: "" },
submissions,
async createSession() {
sessionCounter += 1
const id = `ses_${sessionCounter}`
await Bun.sleep(opts.sessionCreateDelayMs)
return id
},
async sendPrompt(sessionID, text) {
submissions.push({ sessionID, text })
},
}
}
function createSubmit() {
let submitting = false
return async function submit(h: Harness) {
if (submitting) return false
submitting = true
try {
if (!h.store.input) return false
const sessionID = await h.createSession()
const inputText = h.store.input
await h.sendPrompt(sessionID, inputText)
h.store.input = ""
return true
} finally {
submitting = false
}
}
}
describe("Prompt.submit race", () => {
test("concurrent submits must not lose the user's text", async () => {
const submit = createSubmit()
const h = createHarness({ sessionCreateDelayMs: 5 })
h.store.input = "Hello there."
await Promise.all([submit(h), submit(h)])
expect(h.submissions.every((s) => s.text === "Hello there.")).toBe(true)
expect(h.submissions.some((s) => s.text === "")).toBe(false)
})
test("a sequential second submit after clear is a no-op, not a phantom session", async () => {
const submit = createSubmit()
const h = createHarness({ sessionCreateDelayMs: 1 })
h.store.input = "Hello there."
await submit(h)
await submit(h)
expect(h.submissions).toHaveLength(1)
expect(h.submissions[0].text).toBe("Hello there.")
})
})