t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/Railway/SandboxCheckpoint.test.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

420 lines
14 KiB
TypeScript

import { Query } from "@distilled.cloud/core/query";
import {
Railway as RailwayApi,
type SandboxCheckpoint as RailwaySandboxCheckpoint,
} from "@distilled.cloud/railway";
import { adopt, OwnedBySomeoneElse } from "@/AdoptPolicy";
import * as Provider from "@/Provider";
import * as Railway from "@/Railway";
import { State, type UpdatingReourceState } from "@/State";
import * as Test from "@/Test/Alchemy";
import { expect } from "alchemy-test";
import * as Effect from "effect/Effect";
import * as Result from "effect/Result";
import { suitePartition } from "./suiteProject.ts";
const { test } = Test.make({ providers: Railway.providers() });
const Base = Effect.gen(function* () {
const { environment } = yield* suitePartition;
const box = yield* Railway.Sandbox("Source", {
environment,
idleTimeoutMinutes: 5,
});
return { environment, box };
});
const listLive = Query.fn((environmentId: string) =>
RailwayApi.sandboxCheckpoints({ environmentId }).pipe(
Query.map((checkpoint) => ({
id: checkpoint.id,
key: checkpoint.key,
createdAt: checkpoint.createdAt,
environmentId: checkpoint.environmentId,
})),
),
);
const checkpointFields = <E>(
checkpoint: Query<RailwaySandboxCheckpoint, E>,
) => ({
id: checkpoint.id,
key: checkpoint.key,
createdAt: checkpoint.createdAt,
});
const renameSandboxCheckpoint = Query.fn(
(input: { environmentId: string; id: string; name: string }) =>
checkpointFields(RailwayApi.sandboxCheckpointRename(input)),
);
const createSandboxCheckpoint = Query.fn(
(input: { environmentId: string; sandboxId: string; name: string }) =>
checkpointFields(RailwayApi.sandboxCheckpointCreate(input)),
);
const deleteSandboxCheckpoint = Query.fn(
(input: { environmentId: string; id: string }) =>
RailwayApi.sandboxCheckpointDelete(input),
);
const Snapshot = (name?: string, restore = false) =>
Effect.gen(function* () {
const { environment, box } = yield* Base;
const checkpoint = yield* Railway.SandboxCheckpoint("Snapshot", {
sandbox: box,
name,
});
const restored = restore
? yield* Railway.Sandbox("Restored", {
environment,
idleTimeoutMinutes: 5,
template: { name: checkpoint.key },
})
: undefined;
return { environment, box, checkpoint, restored };
});
test.provider(
"capture, list, rename, restore, and delete a sandbox checkpoint",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const base = yield* stack.deploy(Base);
const written = yield* Railway.execSandbox({
sandboxId: base.box.sandboxId,
environmentId: base.box.environmentId,
command: "echo checkpoint-content > /tmp/alchemy-checkpoint.txt",
timeoutSec: 10,
});
expect(written.exitCode).toBe(0);
const created = yield* stack.deploy(Snapshot());
const original = created.checkpoint;
expect(original.sandboxId).toBe(base.box.sandboxId);
expect(original.key).toBe(original.name);
expect(original.name.length).toBeGreaterThan(0);
const live = yield* listLive(base.box.environmentId);
expect(
live.find((item) => item.id === original.sandboxCheckpointId),
).toEqual({
id: original.sandboxCheckpointId,
key: original.key,
createdAt: original.createdAt,
environmentId: original.environmentId,
});
const provider = yield* Provider.findProvider(Railway.SandboxCheckpoint);
expect(
(yield* provider.list()).some(
(item) =>
item.sandboxCheckpointId === original.sandboxCheckpointId &&
item.environmentId === original.environmentId,
),
).toBe(true);
const unchanged = yield* stack.deploy(Snapshot());
expect(unchanged.checkpoint).toEqual(original);
const renamed = yield* stack.deploy(
Snapshot("restorable-checkpoint", true),
);
expect(renamed.checkpoint.name).toBe("restorable-checkpoint");
expect(renamed.checkpoint.createdAt).toBe(original.createdAt);
const renamedLive = yield* listLive(base.box.environmentId);
expect(renamedLive.some((item) => item.key === original.key)).toBe(false);
expect(
renamedLive.find((item) => item.key === renamed.checkpoint.key)?.id,
).toBe(renamed.checkpoint.sandboxCheckpointId);
expect(renamed.restored).toBeDefined();
const restored = yield* Railway.execSandbox({
sandboxId: renamed.restored!.sandboxId,
environmentId: base.box.environmentId,
command: "cat /tmp/alchemy-checkpoint.txt",
timeoutSec: 10,
});
expect(restored.exitCode).toBe(0);
expect(restored.stdout.trim()).toBe("checkpoint-content");
const resetName = yield* stack.deploy(Snapshot());
expect(resetName.checkpoint.key).toBe(original.key);
expect(resetName.checkpoint.createdAt).toBe(original.createdAt);
yield* stack.deploy(Base);
expect(yield* listLive(base.box.environmentId)).toEqual([]);
yield* stack.destroy();
}),
{
tags: [
"provider:railway",
"provider:railway:project",
"provider:railway:projectenvironment",
"provider:railway:sandbox",
"provider:railway:sandboxcheckpoint",
"live",
],
timeout: 120_000,
},
);
test.provider(
"replace a checkpoint when its source changes without removing either source",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const program = (second: boolean) =>
Effect.gen(function* () {
const { environment, box } = yield* Base;
const other = yield* Railway.Sandbox("Other", {
environment,
idleTimeoutMinutes: 5,
});
const checkpoint = yield* Railway.SandboxCheckpoint("Snapshot", {
sandbox: second ? other : box,
name: "replace-source-checkpoint",
});
return { box, other, checkpoint };
});
const before = yield* stack.deploy(program(false));
const after = yield* stack.deploy(program(true));
expect(after.box.sandboxId).toBe(before.box.sandboxId);
expect(after.other.sandboxId).toBe(before.other.sandboxId);
expect(after.checkpoint.sandboxId).toBe(after.other.sandboxId);
expect(after.checkpoint.createdAt).not.toBe(before.checkpoint.createdAt);
const live = yield* listLive(after.box.environmentId);
expect(live).toHaveLength(1);
expect(live[0]?.createdAt).toBe(after.checkpoint.createdAt);
yield* stack.deploy(Base);
expect(yield* listLive(after.box.environmentId)).toEqual([]);
yield* stack.destroy();
}),
{
tags: [
"provider:railway",
"provider:railway:project",
"provider:railway:projectenvironment",
"provider:railway:sandbox",
"provider:railway:sandboxcheckpoint",
"live",
],
timeout: 120_000,
},
);
test.provider(
"recover interrupted renames without adopting or deleting a foreign capture",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const state = yield* yield* State;
const provider = yield* Provider.findProvider(Railway.SandboxCheckpoint);
const key = { stack: stack.name, stage: stack.stage, fqn: "Snapshot" };
for (const recapture of [false, true]) {
const created = yield* stack.deploy(Snapshot("before-rename"));
const persisted = yield* state.get(key);
if (
persisted?.status !== "created" &&
persisted?.status !== "updated"
) {
return yield* Effect.fail(
new Error("Expected a stable checkpoint row"),
);
}
const attempted: Railway.SandboxCheckpointProps = {
sandbox: {
sandboxId: created.box.sandboxId,
environmentId: created.box.environmentId,
},
name: "attempted-rename",
};
// Apply commits attempted props before invoking the rename mutation.
yield* state.set({
...key,
value: {
...persisted,
status: "updating",
props: attempted,
old: persisted,
} satisfies UpdatingReourceState,
});
const renamed = yield* renameSandboxCheckpoint({
environmentId: created.box.environmentId,
id: created.checkpoint.sandboxCheckpointId,
name: attempted.name!,
});
expect(renamed.createdAt).toBe(created.checkpoint.createdAt);
let live = renamed;
if (recapture) {
yield* deleteSandboxCheckpoint({
environmentId: created.box.environmentId,
id: renamed.id,
});
live = yield* createSandboxCheckpoint({
environmentId: created.box.environmentId,
sandboxId: created.box.sandboxId,
name: attempted.name!,
});
expect(live.createdAt).not.toBe(created.checkpoint.createdAt);
}
// The successful rename's returned attributes were never persisted.
const read = yield* Effect.result(
provider.read!({
id: "Snapshot",
fqn: "Snapshot",
instanceId: persisted.instanceId,
olds: attempted,
output: created.checkpoint,
}),
);
if (recapture) {
expect(Result.isFailure(read)).toBe(true);
if (Result.isFailure(read)) {
expect(read.failure).toBeInstanceOf(OwnedBySomeoneElse);
}
} else {
expect(Result.isSuccess(read)).toBe(true);
if (Result.isSuccess(read)) {
expect(read.success).toMatchObject({
sandboxCheckpointId: renamed.id,
key: renamed.key,
createdAt: created.checkpoint.createdAt,
});
}
}
// Deletion must recover directly from attempted props and stale attrs.
yield* stack.deploy(Base);
const remaining = yield* listLive(created.box.environmentId);
if (recapture) {
expect(remaining).toHaveLength(1);
expect(remaining[0]?.id).toBe(live.id);
expect(remaining[0]?.createdAt).toBe(live.createdAt);
yield* deleteSandboxCheckpoint({
environmentId: created.box.environmentId,
id: live.id,
});
} else {
expect(remaining).toEqual([]);
}
expect(yield* listLive(created.box.environmentId)).toEqual([]);
}
yield* stack.destroy();
}),
{
tags: [
"provider:railway",
"provider:railway:project",
"provider:railway:projectenvironment",
"provider:railway:sandbox",
"provider:railway:sandboxcheckpoint",
"live",
],
timeout: 120_000,
},
);
test.provider(
"require adoption and preserve a foreign recapture with the same checkpoint name",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const base = yield* stack.deploy(Base);
const foreign = yield* createSandboxCheckpoint({
environmentId: base.box.environmentId,
sandboxId: base.box.sandboxId,
name: "foreign-checkpoint",
});
const program = (allowAdoption: boolean, name = "foreign-checkpoint") =>
Effect.gen(function* () {
const { box } = yield* Base;
return yield* Railway.SandboxCheckpoint("Snapshot", {
sandbox: box,
name,
}).pipe(adopt(allowAdoption));
});
const denied = yield* Effect.result(stack.deploy(program(false)));
expect(Result.isFailure(denied)).toBe(true);
if (Result.isFailure(denied)) {
expect(denied.failure).toBeInstanceOf(OwnedBySomeoneElse);
}
expect((yield* listLive(base.box.environmentId))[0]?.createdAt).toBe(
foreign.createdAt,
);
const adopted = yield* stack.deploy(program(true));
expect(adopted.sandboxCheckpointId).toBe(foreign.id);
expect(adopted.createdAt).toBe(foreign.createdAt);
const occupied = yield* createSandboxCheckpoint({
environmentId: base.box.environmentId,
sandboxId: base.box.sandboxId,
name: "occupied-checkpoint",
});
const collision = yield* Effect.result(
stack.deploy(program(false, "occupied-checkpoint")),
);
expect(Result.isFailure(collision)).toBe(true);
const afterCollision = yield* listLive(base.box.environmentId);
expect(afterCollision).toHaveLength(2);
expect(
afterCollision.find((item) => item.id === occupied.id)?.createdAt,
).toBe(occupied.createdAt);
expect(
afterCollision.find((item) => item.id === foreign.id)?.createdAt,
).toBe(foreign.createdAt);
yield* deleteSandboxCheckpoint({
environmentId: base.box.environmentId,
id: occupied.id,
});
yield* stack.deploy(program(false));
yield* deleteSandboxCheckpoint({
environmentId: base.box.environmentId,
id: foreign.id,
});
const replacement = yield* createSandboxCheckpoint({
environmentId: base.box.environmentId,
sandboxId: base.box.sandboxId,
name: "foreign-checkpoint",
});
expect(replacement.createdAt).not.toBe(adopted.createdAt);
const provider = yield* Provider.findProvider(Railway.SandboxCheckpoint);
const read = yield* Effect.result(
provider.read!({
id: "Snapshot",
fqn: "Snapshot",
instanceId: "unused",
olds: {
sandbox: {
sandboxId: base.box.sandboxId,
environmentId: base.box.environmentId,
},
name: "foreign-checkpoint",
},
output: adopted,
}),
);
expect(Result.isFailure(read)).toBe(true);
if (Result.isFailure(read)) {
expect(read.failure).toBeInstanceOf(OwnedBySomeoneElse);
}
yield* stack.deploy(Base);
const preserved = yield* listLive(base.box.environmentId);
expect(preserved).toHaveLength(1);
expect(preserved[0]?.id).toBe(replacement.id);
expect(preserved[0]?.createdAt).toBe(replacement.createdAt);
yield* deleteSandboxCheckpoint({
environmentId: base.box.environmentId,
id: replacement.id,
});
expect(yield* listLive(base.box.environmentId)).toEqual([]);
yield* stack.destroy();
}),
{
tags: [
"provider:railway",
"provider:railway:project",
"provider:railway:projectenvironment",
"provider:railway:sandbox",
"provider:railway:sandboxcheckpoint",
"live",
],
timeout: 120_000,
},
);