diff --git a/packages/core/test/session-revert.test.ts b/packages/core/test/session-revert.test.ts index 2a166e34d93..f7d4d6ad74a 100644 --- a/packages/core/test/session-revert.test.ts +++ b/packages/core/test/session-revert.test.ts @@ -38,80 +38,84 @@ const it = testEffect( ) describe("Session.revert files", () => { - it.live("undoes and restores a file rename without losing either path", () => - Effect.gen(function* () { - const tmp = yield* Effect.acquireRelease( - Effect.promise(() => tmpdir()), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ) - const directory = path.join(tmp.path, "project") - const original = path.join(directory, "old name.txt") - const renamed = path.join(directory, "new name.txt") - yield* Effect.promise(async () => { - await fs.mkdir(directory) - await Bun.write(original, "Preserve this content.\n") - await Bun.write(path.join(directory, "unrelated.txt"), "Unrelated content.\n") - await $`git init -q`.cwd(directory).quiet() - await $`git -c core.fsmonitor=false add .`.cwd(directory).quiet() - }) - - const session = yield* Session.Service - const database = yield* Database.Service - const bus = yield* Bus.Service - const created = yield* session.create({ location: { directory: AbsolutePath.make(directory) } }) - const prompt = yield* session.prompt({ sessionID: created.id, text: "Rename the file", resume: false }) - yield* SessionInbox.promote(database.db, bus, created.id, "steer") - - yield* Effect.gen(function* () { - const plugins = yield* PluginSupervisor.Service - yield* plugins.flush - const snapshot = yield* Snapshot.Service - const before = yield* snapshot.capture() - if (!before) throw new Error("Initial snapshot missing") - const assistantMessageID = SessionMessage.ID.create() - yield* bus.publish(SessionEvent.Step.Started, { - sessionID: created.id, - assistantMessageID, - agent: Agent.defaultID, - model: { id: Model.ID.make("test-model"), providerID: Provider.ID.make("test-provider") }, - snapshot: before, - }) - yield* Effect.promise(() => fs.rename(original, renamed)) - const after = yield* snapshot.capture() - if (!after) throw new Error("Renamed snapshot missing") - yield* bus.publish(SessionEvent.Step.Ended, { - sessionID: created.id, - assistantMessageID, - finish: "stop", - cost: Money.USD.zero, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - snapshot: after, - files: yield* snapshot.files({ from: before, to: after }), + it.live( + "undoes and restores a file rename without losing either path", + () => + Effect.gen(function* () { + const tmp = yield* Effect.acquireRelease( + Effect.promise(() => tmpdir()), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ) + const directory = path.join(tmp.path, "project") + const original = path.join(directory, "old name.txt") + const renamed = path.join(directory, "new name.txt") + yield* Effect.promise(async () => { + await fs.mkdir(directory) + await Bun.write(original, "Preserve this content.\n") + await Bun.write(path.join(directory, "unrelated.txt"), "Unrelated content.\n") + await $`git init -q`.cwd(directory).quiet() + await $`git -c core.fsmonitor=false add .`.cwd(directory).quiet() }) - yield* Effect.promise(() => Bun.write(path.join(directory, "unrelated.txt"), "Keep this later edit.\n")) - const reverted = yield* session.revert.stage({ sessionID: created.id, messageID: prompt.id }) - expect({ - original: yield* Effect.promise(() => Bun.file(original).exists()), - renamed: yield* Effect.promise(() => Bun.file(renamed).exists()), - }).toEqual({ original: true, renamed: false }) - expect(yield* Effect.promise(() => Bun.file(original).text())).toBe("Preserve this content.\n") - expect(reverted.files?.map((file) => [file.file, file.status])).toEqual([ - ["new name.txt", "deleted"], - ["old name.txt", "added"], - ]) - expect(yield* Effect.promise(() => Bun.file(path.join(directory, "unrelated.txt")).text())).toBe( - "Keep this later edit.\n", - ) + const session = yield* Session.Service + const database = yield* Database.Service + const bus = yield* Bus.Service + const created = yield* session.create({ location: { directory: AbsolutePath.make(directory) } }) + const prompt = yield* session.prompt({ sessionID: created.id, text: "Rename the file", resume: false }) + yield* SessionInbox.promote(database.db, bus, created.id, "steer") - yield* session.revert.clear(created.id) - expect(yield* Effect.promise(() => Bun.file(original).exists())).toBe(false) - expect(yield* Effect.promise(() => Bun.file(renamed).text())).toBe("Preserve this content.\n") - expect(yield* Effect.promise(() => Bun.file(path.join(directory, "unrelated.txt")).text())).toBe( - "Keep this later edit.\n", - ) - expect((yield* session.get(created.id)).revert).toBeUndefined() - }).pipe(Effect.provide(LocationServiceMap.Service.get(created.location))) - }), + yield* Effect.gen(function* () { + const plugins = yield* PluginSupervisor.Service + yield* plugins.flush + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.capture() + if (!before) throw new Error("Initial snapshot missing") + const assistantMessageID = SessionMessage.ID.create() + yield* bus.publish(SessionEvent.Step.Started, { + sessionID: created.id, + assistantMessageID, + agent: Agent.defaultID, + model: { id: Model.ID.make("test-model"), providerID: Provider.ID.make("test-provider") }, + snapshot: before, + }) + yield* Effect.promise(() => fs.rename(original, renamed)) + const after = yield* snapshot.capture() + if (!after) throw new Error("Renamed snapshot missing") + yield* bus.publish(SessionEvent.Step.Ended, { + sessionID: created.id, + assistantMessageID, + finish: "stop", + cost: Money.USD.zero, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + snapshot: after, + files: yield* snapshot.files({ from: before, to: after }), + }) + + yield* Effect.promise(() => Bun.write(path.join(directory, "unrelated.txt"), "Keep this later edit.\n")) + const reverted = yield* session.revert.stage({ sessionID: created.id, messageID: prompt.id }) + expect({ + original: yield* Effect.promise(() => Bun.file(original).exists()), + renamed: yield* Effect.promise(() => Bun.file(renamed).exists()), + }).toEqual({ original: true, renamed: false }) + expect(yield* Effect.promise(() => Bun.file(original).text())).toBe("Preserve this content.\n") + expect(reverted.files?.map((file) => [file.file, file.status])).toEqual([ + ["new name.txt", "deleted"], + ["old name.txt", "added"], + ]) + expect(yield* Effect.promise(() => Bun.file(path.join(directory, "unrelated.txt")).text())).toBe( + "Keep this later edit.\n", + ) + + yield* session.revert.clear(created.id) + expect(yield* Effect.promise(() => Bun.file(original).exists())).toBe(false) + expect(yield* Effect.promise(() => Bun.file(renamed).text())).toBe("Preserve this content.\n") + expect(yield* Effect.promise(() => Bun.file(path.join(directory, "unrelated.txt")).text())).toBe( + "Keep this later edit.\n", + ) + expect((yield* session.get(created.id)).revert).toBeUndefined() + }).pipe(Effect.provide(LocationServiceMap.Service.get(created.location))) + }), + // Real Location/plugin startup and Git snapshots can exceed five seconds under CI load. + { timeout: 15_000 }, ) })