1import { describe, expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import { PgDialect } from "drizzle-orm/pg-core";
5
6import * as RelayDb from "../db.ts";
7import { relayEnvironmentLinks } from "../persistence/schema.ts";
8import * as EnvironmentLinks from "./EnvironmentLinks.ts";
9
10describe("EnvironmentLinks", () => {
11 it.effect("retains link lookup failures with user and environment identity", () => {
12 const cause = new Error("database unavailable");
13 const fakeDb = {
14 select: () => ({
15 from: (table: unknown) => {
16 expect(table).toBe(relayEnvironmentLinks);
17 return {
18 where: () => ({
19 limit: () => Effect.fail(cause),
20 }),
21 };
22 },
23 }),
24 } as unknown as RelayDb.RelayDb["Service"];
25
26 return Effect.gen(function* () {
27 const links = yield* EnvironmentLinks.EnvironmentLinks;
28 const error = yield* Effect.flip(
29 links.getForUser({ userId: "user-1", environmentId: "env-1" }),
30 );
31
32 expect(error).toMatchObject({
33 _tag: "EnvironmentLinkLookupPersistenceError",
34 userId: "user-1",
35 environmentId: "env-1",
36 });
37 expect(error.cause).toBe(cause);
38 }).pipe(
39 Effect.provide(
40 EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
41 ),
42 );
43 });
44
45 it.effect("identifies delivery-user list failures without retaining key material", () => {
46 const cause = new Error("database unavailable");
47 const fakeDb = {
48 select: () => ({
49 from: (table: unknown) => {
50 expect(table).toBe(relayEnvironmentLinks);
51 return {
52 where: () => Effect.fail(cause),
53 };
54 },
55 }),
56 } as unknown as RelayDb.RelayDb["Service"];
57
58 return Effect.gen(function* () {
59 const links = yield* EnvironmentLinks.EnvironmentLinks;
60 const error = yield* Effect.flip(
61 links.listDeliveryUsersForEnvironment({
62 environmentId: "env-1",
63 environmentPublicKey: "sensitive-public-key-material",
64 }),
65 );
66
67 expect(error).toMatchObject({
68 _tag: "EnvironmentLinkUserListPersistenceError",
69 message: "Environment link user query 'list-delivery-users' failed for environment 'env-1'",
70 environmentId: "env-1",
71 });
72 expect(error.cause).toBe(cause);
73 expect(error).not.toHaveProperty("environmentPublicKey");
74 }).pipe(
75 Effect.provide(
76 EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
77 ),
78 );
79 });
80
81 it.effect("selects notification or Live Activity users by environment key", () => {
82 const whereConditions: Array<unknown> = [];
83 const fakeDb = {
84 select: (selection: unknown) => {
85 expect(selection).toBeDefined();
86 return {
87 from: (table: unknown) => {
88 expect(table).toBe(relayEnvironmentLinks);
89 return {
90 where: (condition: unknown) => {
91 whereConditions.push(condition);
92 return Effect.succeed([]);
93 },
94 };
95 },
96 };
97 },
98 } as unknown as RelayDb.RelayDb["Service"];
99
100 return Effect.gen(function* () {
101 const links = yield* EnvironmentLinks.EnvironmentLinks;
102 expect(
103 yield* links.listDeliveryUsersForEnvironment({
104 environmentId: "env-1",
105 environmentPublicKey: "public-key-1",
106 }),
107 ).toEqual([]);
108 expect(whereConditions).toHaveLength(1);
109
110 const query = new PgDialect().sqlToQuery(whereConditions[0] as never);
111 expect(query.sql).toContain('"relay_environment_links"."environment_id" = $1');
112 expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null');
113 expect(query.sql).toContain('"relay_environment_links"."notifications_enabled" = $2');
114 expect(query.sql).toContain('"relay_environment_links"."live_activities_enabled" = $3');
115 expect(query.sql).toContain('"relay_environment_links"."environment_public_key" = $4');
116 expect(query.sql).toContain(" or ");
117 expect(query.params).toEqual(["env-1", true, true, "public-key-1"]);
118 }).pipe(
119 Effect.provide(
120 EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
121 ),
122 );
123 });
124
125 it.effect("carries the webhook-hold opt-in over only from links with the same key", () => {
126 const inserted: Array<Record<string, unknown>> = [];
127 const fakeDb = {
128 insert: () => ({
129 values: (values: Record<string, unknown>) => {
130 inserted.push(values);
131 return { onConflictDoUpdate: () => Effect.void };
132 },
133 }),
134 } as unknown as RelayDb.RelayDb["Service"];
135
136 return Effect.gen(function* () {
137 const links = yield* EnvironmentLinks.EnvironmentLinks;
138 yield* links.upsert({
139 userId: "user-1",
140 request: {
141 notificationsEnabled: false,
142 liveActivitiesEnabled: false,
143 managedTunnelsEnabled: true,
144 } as never,
145 proof: {
146 environmentId: "env-1",
147 environmentPublicKey: "public-key-1",
148 descriptor: { label: "Laptop" },
149 } as never,
150 endpoint: { httpBaseUrl: "https://a.example", wsBaseUrl: "wss://a.example" } as never,
151 });
152 const query = new PgDialect().sqlToQuery(inserted[0]?.holdWebhooksWhileOffline as never);
153 // An environment id is public: another account linking it with its own
154 // key must not switch the opt-in on for this one.
155 expect(query.sql).toContain("environment_public_key");
156 expect(query.params).toEqual(["env-1", "public-key-1"]);
157 }).pipe(
158 Effect.provide(
159 EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
160 ),
161 );
162 });
163
164 it.effect("revokes only the active link owned by the requesting user", () => {
165 const updateValues: Array<Record<string, unknown>> = [];
166 const whereConditions: Array<unknown> = [];
167 const fakeDb = {
168 update: (table: unknown) => {
169 expect(table).toBe(relayEnvironmentLinks);
170 return {
171 set: (values: Record<string, unknown>) => {
172 updateValues.push(values);
173 return {
174 where: (condition: unknown) => {
175 whereConditions.push(condition);
176 return {
177 returning: (selection: unknown) => {
178 expect(selection).toBeDefined();
179 return Effect.succeed([{ environmentId: "env-1" }]);
180 },
181 };
182 },
183 };
184 },
185 };
186 },
187 } as unknown as RelayDb.RelayDb["Service"];
188
189 return Effect.gen(function* () {
190 const links = yield* EnvironmentLinks.EnvironmentLinks;
191 const revoked = yield* links.revokeForUser({
192 userId: "user-1",
193 environmentId: "env-1",
194 });
195
196 expect(revoked).toBe(true);
197 expect(updateValues).toHaveLength(1);
198 expect(updateValues[0]?.revokedAt).toEqual(updateValues[0]?.updatedAt);
199 expect(typeof updateValues[0]?.revokedAt).toBe("string");
200 expect(whereConditions).toHaveLength(1);
201
202 const dialect = new PgDialect();
203 const query = dialect.sqlToQuery(whereConditions[0] as never);
204 expect(query.sql).toContain('"relay_environment_links"."user_id" = $1');
205 expect(query.sql).toContain('"relay_environment_links"."environment_id" = $2');
206 expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null');
207 expect(query.params).toEqual(["user-1", "env-1"]);
208 }).pipe(
209 Effect.provide(
210 EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
211 ),
212 );
213 });
214});