import { assert, assertEquals } from "@std/assert";
import {
Cause,
Clock as EffectClock,
Deferred,
Effect,
Exit,
Fiber,
Option,
Schema,
} from "effect";
import { TestClock } from "effect/testing";
import { CatalogFailure, InvalidOrder, StoreFailure } from "./contracts.ts";
import { makeDriver, sqlFailure } from "./driver.ts";
import { submit } from "./workflow.ts";
const request = { sku: "book", quantity: 2 };
const receipt = { sku: "book", quantity: 2, total: 24 };
const run = (effect: Effect.Effect) =>
Effect.runPromise(effect.pipe(Effect.timeout("5 seconds")));
const failure = (exit: Exit.Exit): E => {
assert(Exit.isFailure(exit));
const error = Cause.findErrorOption(exit.cause);
assert(Option.isSome(error));
return error.value;
};
Deno.test("service workflow returns a validated receipt and commits a parameterized statement", async () => {
const driver = makeDriver();
const operation = submit(request).pipe(Effect.provide(driver.layer));
assertEquals(driver.events, []);
assertEquals(await run(operation), receipt);
assertEquals(driver.committed, [receipt]);
assertEquals(driver.events, [
"quote:book",
"connect",
"lease:1",
"leased:1",
"begin:1",
"write:1",
"commit:1",
"release:1",
"released:1",
]);
assertEquals(driver.statements[0]?.params, ["book", 2, 24]);
assertEquals(driver.statements[0]?.sql.includes("'book'"), false);
assertEquals([driver.active.size, driver.staged.size], [0, 0]);
});
Deno.test("invalid requests fail before any service or provider is invoked", async () => {
const driver = makeDriver();
for (
const input of [{}, { sku: "book", quantity: "2" }, {
sku: "book",
quantity: 1.5,
}]
) {
const error = failure(
await run(Effect.exit(submit(input).pipe(Effect.provide(driver.layer)))),
);
assert(error instanceof Schema.SchemaError);
}
for (
const input of [{ sku: "", quantity: 2 }, { sku: "book", quantity: 0 }, {
sku: "book",
quantity: 101,
}]
) {
const error = failure(
await run(Effect.exit(submit(input).pipe(Effect.provide(driver.layer)))),
);
assert(error instanceof InvalidOrder && error.reason === "request");
}
assertEquals(driver.events, []);
});
Deno.test("catalog rejection and invalid prices start no transaction", async () => {
for (const value of [-1, 100001, 1.5, undefined]) {
const driver = makeDriver({
price: (sku) =>
value === undefined
? Effect.fail(new CatalogFailure({ sku }))
: Effect.succeed(value),
});
const error = failure(
await run(
Effect.exit(submit(request).pipe(Effect.provide(driver.layer))),
),
);
assert(
value === -1 || value === 100001
? error instanceof InvalidOrder
: error instanceof CatalogFailure,
);
assertEquals(driver.events, ["quote:book"]);
}
});
Deno.test("transient writes retry at five and ten milliseconds and replay gets a fresh budget", async () => {
await run(
Effect.gen(function* () {
const first = yield* Deferred.make();
const times: number[] = [];
const driver = makeDriver({
execute: (phase, id) =>
Effect.gen(function* () {
if (phase === "write") {
times.push(yield* EffectClock.currentTimeMillis);
yield* Deferred.succeed(first, undefined);
if (id % 3 !== 0) {
return yield* Effect.fail(sqlFailure(true, "write"));
}
}
}),
});
const program = submit(request).pipe(Effect.provide(driver.layer));
for (let replay = 0; replay < 2; replay++) {
const fiber = yield* Effect.forkChild(program);
yield* Deferred.await(first);
yield* TestClock.adjust(100);
assertEquals(yield* Fiber.join(fiber), receipt);
}
assertEquals(times, [0, 5, 15, 100, 105, 115]);
assertEquals(driver.committed, [receipt, receipt]);
assertEquals(
driver.events.filter((event) => event === "quote:book").length,
2,
);
assertEquals(
driver.events.filter((event) => event === "connect").length,
2,
);
assertEquals(
driver.events.filter((event) => event.startsWith("rollback:")).length,
4,
);
for (let id = 1; id < 6; id++) {
assert(
driver.events.indexOf(`released:${id}`) <
driver.events.indexOf(`lease:${id + 1}`),
);
}
assertEquals([driver.leases, driver.active.size, driver.staged.size], [
6,
0,
0,
]);
}).pipe(Effect.provide(TestClock.layer())),
);
});
Deno.test("retry exhaustion and permanent write errors roll back and release without another catalog call", async () => {
for (const retryable of [true, false]) {
const driver = makeDriver({
execute: (phase) =>
phase === "write"
? Effect.fail(sqlFailure(retryable, "write"))
: Effect.void,
});
const error = failure(
await run(
Effect.exit(submit(request).pipe(Effect.provide(driver.layer))),
),
);
assert(error instanceof StoreFailure);
assertEquals(error.kind, retryable ? "Transient" : "Permanent");
assertEquals(driver.leases, retryable ? 3 : 1);
assertEquals(driver.committed, []);
assertEquals(
driver.events.filter((event) => event === "quote:book").length,
1,
);
assertEquals(
driver.events.filter((event) => event.startsWith("released:")).length,
driver.leases,
);
assertEquals([driver.active.size, driver.staged.size], [0, 0]);
}
});
Deno.test("provider and lease failures remain permanent and release no absent connection", async () => {
const provider = makeDriver({
connect: () =>
Effect.fail(new StoreFailure({ kind: "Transient", stage: "driver" })),
});
const providerError = failure(
await run(
Effect.exit(submit(request).pipe(Effect.provide(provider.layer))),
),
);
assert(
providerError instanceof StoreFailure &&
providerError.kind === "Permanent" && providerError.stage === "provider",
);
assertEquals(provider.events, ["quote:book", "connect"]);
const lease = makeDriver({
lease: () => Effect.fail(sqlFailure(true, "lease")),
});
const leaseError = failure(
await run(Effect.exit(submit(request).pipe(Effect.provide(lease.layer)))),
);
assert(leaseError instanceof StoreFailure && leaseError.kind === "Permanent");
assertEquals(lease.events, ["quote:book", "connect", "lease:1"]);
assertEquals(lease.active.size, 0);
});
Deno.test("BEGIN failures release their connection without running the write", async () => {
const driver = makeDriver({
execute: (phase) =>
phase === "begin" ? Effect.fail(sqlFailure(true, "begin")) : Effect.void,
});
const error = failure(
await run(Effect.exit(submit(request).pipe(Effect.provide(driver.layer)))),
);
assert(error instanceof StoreFailure && error.kind === "Permanent");
assertEquals(driver.statements, []);
assertEquals(driver.events.slice(2), [
"lease:1",
"leased:1",
"begin:1",
"release:1",
"released:1",
]);
assertEquals(driver.active.size, 0);
});
Deno.test("malformed receipt rows roll back staged writes", async () => {
for (const rows of [[], [{ ...receipt, total: "24" }]]) {
const driver = makeDriver({ rows: () => rows });
const error = failure(
await run(
Effect.exit(submit(request).pipe(Effect.provide(driver.layer))),
),
);
assert(
error instanceof StoreFailure && error.kind === "Permanent" &&
error.stage === "row",
);
assertEquals(driver.events.slice(-3), [
"rollback:1",
"release:1",
"released:1",
]);
assertEquals(driver.committed, []);
assertEquals([driver.active.size, driver.staged.size], [0, 0]);
}
});
Deno.test("commit errors cannot become transient write retries even with misleading metadata", async () => {
const driver = makeDriver({
execute: (phase) =>
phase === "commit" ? Effect.fail(sqlFailure(true, "write")) : Effect.void,
});
const error = failure(
await run(Effect.exit(submit(request).pipe(Effect.provide(driver.layer)))),
);
assert(error instanceof StoreFailure && error.kind === "Permanent");
assertEquals(driver.leases, 1);
assertEquals(driver.events.slice(-3), [
"commit:1",
"release:1",
"released:1",
]);
assertEquals(driver.committed, []);
assertEquals([driver.active.size, driver.staged.size], [0, 0]);
});
Deno.test("execution defects roll back and bypass typed retries", async () => {
const driver = makeDriver({
execute: (phase) =>
phase === "write" ? Effect.die("write defect") : Effect.void,
});
const exit = await run(
Effect.exit(submit(request).pipe(Effect.provide(driver.layer))),
);
assert(
Exit.isFailure(exit) && Cause.hasDies(exit.cause) &&
Option.isNone(Cause.findErrorOption(exit.cause)),
);
assertEquals(driver.leases, 1);
assertEquals(driver.events.slice(-3), [
"rollback:1",
"release:1",
"released:1",
]);
assertEquals(driver.committed, []);
});
Deno.test("cleanup defects preserve a simultaneous transient body failure and prevent retry", async () => {
for (const broken of ["rollback", "release"]) {
const driver = makeDriver({
execute: (phase) =>
phase === "write"
? Effect.fail(sqlFailure(true, "write"))
: phase === broken
? Effect.fail(sqlFailure(false, "rollback"))
: Effect.void,
release: () =>
broken === "release" ? Effect.die("release defect") : Effect.void,
});
const exit = await run(
Effect.exit(submit(request).pipe(Effect.provide(driver.layer))),
);
assert(Exit.isFailure(exit) && Cause.hasDies(exit.cause));
const error = failure(exit);
assert(error instanceof StoreFailure && error.kind === "Transient");
assertEquals(driver.leases, 1);
assertEquals(driver.committed, []);
assertEquals([driver.active.size, driver.staged.size], [0, 0]);
}
});
Deno.test("body cancellation waits for blocked rollback or release and commits no staged rows", async () => {
for (const blocked of ["rollback", "release"]) {
const finish = Deferred.makeUnsafe();
await run(
Effect.gen(function* () {
const entered = yield* Deferred.make();
const cleaning = yield* Deferred.make();
let interrupted = false;
const cleanup = Deferred.succeed(cleaning, undefined).pipe(
Effect.andThen(Deferred.await(finish)),
);
const driver = makeDriver({
execute: (phase) =>
phase === "write"
? Deferred.succeed(entered, undefined).pipe(
Effect.andThen(Effect.never),
)
: phase === blocked
? cleanup
: Effect.void,
release: () => blocked === "release" ? cleanup : Effect.void,
});
const fiber = yield* Effect.forkChild(
submit(request).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(entered);
const stagedBeforeShutdown = driver.staged.size;
const closing = yield* Effect.forkChild(
Fiber.interrupt(fiber).pipe(Effect.andThen(Effect.sync(() => {
interrupted = true;
}))),
);
yield* Deferred.await(cleaning);
yield* Effect.yieldNow;
yield* Effect.sync(() => {
assertEquals(stagedBeforeShutdown, 1);
assertEquals([interrupted, driver.active.size, driver.staged.size], [
false,
1,
blocked === "rollback" ? 1 : 0,
]);
assertEquals(driver.committed, []);
}).pipe(Effect.ensuring(Deferred.succeed(finish, undefined)));
yield* Fiber.join(closing);
const exit = yield* Fiber.await(fiber);
assert(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause));
assertEquals([driver.active.size, driver.staged.size, driver.leases], [
0,
0,
1,
]);
assertEquals(driver.events.slice(-3), [
"rollback:1",
"release:1",
"released:1",
]);
assertEquals(driver.committed, []);
}).pipe(Effect.ensuring(Deferred.succeed(finish, undefined))),
);
}
});
Deno.test("cancellation during either retry delay prevents a later transaction", async () => {
for (const afterAttempt of [1, 2]) {
await run(
Effect.gen(function* () {
const first = yield* Deferred.make();
const driver = makeDriver({
execute: (phase) =>
phase === "write"
? Effect.fail(sqlFailure(true, "write"))
: Effect.void,
release: (id) =>
id === 1
? Deferred.succeed(first, undefined).pipe(Effect.asVoid)
: Effect.void,
});
const fiber = yield* Effect.forkChild(
submit(request).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(first);
yield* TestClock.adjust(afterAttempt === 1 ? 0 : 5);
assertEquals([driver.leases, driver.active.size, driver.staged.size], [
afterAttempt,
0,
0,
]);
yield* Fiber.interrupt(fiber);
yield* TestClock.adjust(1000);
const exit = yield* Fiber.await(fiber);
assert(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause));
assertEquals(driver.leases, afterAttempt);
assertEquals(driver.committed, []);
}).pipe(Effect.provide(TestClock.layer())),
);
}
});
Deno.test("interruption after commit begins waits for commit and release without promising rollback", async () => {
const finish = Deferred.makeUnsafe();
await run(
Effect.gen(function* () {
const committing = yield* Deferred.make();
let interrupted = false;
const driver = makeDriver({
execute: (phase) =>
phase === "commit"
? Deferred.succeed(committing, undefined).pipe(
Effect.andThen(Deferred.await(finish)),
)
: Effect.void,
});
const fiber = yield* Effect.forkChild(
submit(request).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(committing);
const closing = yield* Effect.forkChild(
Fiber.interrupt(fiber).pipe(Effect.andThen(Effect.sync(() => {
interrupted = true;
}))),
);
yield* Effect.yieldNow;
yield* Effect.sync(() => {
assertEquals([interrupted, driver.active.size], [false, 1]);
assertEquals(driver.committed, []);
}).pipe(Effect.ensuring(Deferred.succeed(finish, undefined)));
yield* Fiber.join(closing);
const exit = yield* Fiber.await(fiber);
assert(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause));
assertEquals(driver.committed, [receipt]);
assertEquals(driver.active.size, 0);
assert(!driver.events.some((event) => event.startsWith("rollback:")));
}).pipe(Effect.ensuring(Deferred.succeed(finish, undefined))),
);
});
Deno.test("workflow test assertion failures unblock cleanup before joining children", async () => {
for (const blocked of ["rollback", "release", "commit"]) {
for (const stage of ["started", "cleaning"]) {
const finish = Deferred.makeUnsafe();
const entered = Deferred.makeUnsafe();
const cleaning = Deferred.makeUnsafe();
const cleanup = Deferred.succeed(cleaning, undefined).pipe(
Effect.andThen(Deferred.await(finish)),
);
const driver = makeDriver({
execute: (phase) =>
phase === "write" && blocked !== "commit"
? Deferred.succeed(entered, undefined).pipe(
Effect.andThen(Effect.never),
)
: phase === "commit" && blocked === "commit"
? Deferred.succeed(entered, undefined).pipe(Effect.andThen(cleanup))
: phase === blocked
? cleanup
: Effect.void,
release: () => blocked === "release" ? cleanup : Effect.void,
});
let timer: ReturnType | undefined;
try {
const exit = await Promise.race([
Effect.runPromiseExit(
Effect.gen(function* () {
const fiber = yield* Effect.forkChild(
submit(request).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(entered);
if (stage === "started") assert(false, "injected assertion");
yield* Effect.forkChild(Fiber.interrupt(fiber));
yield* Deferred.await(cleaning);
assert(false, "injected assertion");
}).pipe(Effect.ensuring(Deferred.succeed(finish, undefined))),
),
new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error("cleanup hung")), 1000);
}),
]);
assert(Exit.isFailure(exit) && Cause.hasDies(exit.cause));
assert(Cause.pretty(exit.cause).includes("injected assertion"));
assertEquals([driver.active.size, driver.staged.size], [0, 0]);
assertEquals(driver.events.at(-1), "released:1");
assertEquals(driver.committed, blocked === "commit" ? [receipt] : []);
} finally {
clearTimeout(timer);
await Effect.runPromise(Deferred.succeed(finish, undefined));
}
}
}
});