Open History to see the last commit
1import { describe, expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4
5import * as RelayDb from "../db.ts";
6import { relayManagedEndpointAllocations } from "../persistence/schema.ts";
7import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts";
8
9const layerWithDb = (db: RelayDb.RelayDb["Service"]) =>
10 ManagedEndpointAllocations.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, db)));
11
12describe("ManagedEndpointAllocations", () => {
13 it.effect("returns recovery support with tunnel allocation lookups", () => {
14 const base = {
15 userId: "user-1",
16 hostname: "environment.example.test",
17 tunnelName: "managed-tunnel",
18 dnsRecordId: "dns-1",
19 readyAt: "2026-08-25T12:00:00.000Z",
20 updatedAt: "2026-08-25T12:00:00.000Z",
21 generation: 1,
22 };
23 const fakeDb = {
24 select: () => ({
25 from: (table: unknown) => {
26 expect(table).toBe(relayManagedEndpointAllocations);
27 return {
28 leftJoin: () => ({
29 where: () =>
30 Effect.succeed([
31 {
32 ...base,
33 environmentId: "environment-1",
34 tunnelId: "tunnel-1",
35 recoveryEnabledAt: "2026-08-25T12:00:00.000Z",
36 recoveryEnvironmentPublicKey: "current-key",
37 linkedEnvironmentPublicKey: "current-key",
38 },
39 {
40 ...base,
41 environmentId: "environment-2",
42 tunnelId: "tunnel-2",
43 recoveryEnabledAt: null,
44 recoveryEnvironmentPublicKey: null,
45 linkedEnvironmentPublicKey: "current-key",
46 },
47 {
48 ...base,
49 environmentId: "environment-3",
50 tunnelId: "tunnel-3",
51 recoveryEnabledAt: "2026-08-25T12:00:00.000Z",
52 recoveryEnvironmentPublicKey: "old-key",
53 linkedEnvironmentPublicKey: "new-key",
54 },
55 ]),
56 }),
57 };
58 },
59 }),
60 } as unknown as RelayDb.RelayDb["Service"];
61
62 return Effect.gen(function* () {
63 const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations;
64 const result = yield* allocations.listByTunnelNames([
65 "first-tunnel",
66 "second-tunnel",
67 "third-tunnel",
68 ]);
69
70 expect(result.map((entry) => [entry.tunnelId, entry.recoveryEnabled])).toEqual([
71 ["tunnel-1", true],
72 ["tunnel-2", false],
73 ["tunnel-3", false],
74 ]);
75 }).pipe(Effect.provide(layerWithDb(fakeDb)));
76 });
77
78 it.effect("teardown claims leave updatedAt to provisioning writes", () => {
79 // The tunnel limit compares link and allocation updatedAt; a claim that bumps it makes a
80 // failed revoke cleanup look like a reprovision and keeps holding the user's slot.
81 const writes: Array<Record<string, unknown>> = [];
82 const fakeDb = {
83 update: () => ({
84 set: (values: Record<string, unknown>) => {
85 writes.push(values);
86 return {
87 where: () => ({ returning: () => Effect.succeed([{ generation: 2 }]) }),
88 };
89 },
90 }),
91 } as unknown as RelayDb.RelayDb["Service"];
92
93 return Effect.gen(function* () {
94 const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations;
95 const key = { userId: "user-1", environmentId: "environment-1", generation: 1 };
96 yield* allocations.claimDeprovision(key);
97 yield* allocations.claimRelease({ ...key, tunnelId: "tunnel-1" });
98 yield* allocations.claimRelease({ ...key, tunnelId: "tunnel-1", markReleased: true });
99
100 expect(writes.map((values) => "updatedAt" in values)).toEqual([false, false, true]);
101 expect(writes[2]?.["updatedAt"]).toBe(writes[2]?.["tunnelReleasedAt"]);
102 }).pipe(Effect.provide(layerWithDb(fakeDb)));
103 });
104});