@gencow/core 0.1.19 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/crud.d.ts +30 -12
- package/dist/crud.js +233 -52
- package/dist/index.d.ts +18 -17
- package/dist/index.js +10 -10
- package/dist/reactive.d.ts +4 -4
- package/dist/rls-db.d.ts +3 -5
- package/dist/rls-db.js +3 -5
- package/dist/rls.d.ts +44 -1
- package/dist/rls.js +62 -2
- package/dist/server.d.ts +5 -4
- package/dist/server.js +4 -4
- package/dist/storage.d.ts +29 -2
- package/dist/storage.js +396 -8
- package/package.json +6 -2
- package/src/__tests__/crud-owner-rls.test.ts +380 -0
- package/src/__tests__/fixtures/basic/auth.ts +32 -0
- package/src/__tests__/fixtures/basic/drizzle.config.ts +15 -0
- package/src/__tests__/fixtures/basic/index.ts +6 -0
- package/src/__tests__/fixtures/basic/migrations/0000_faithful_silver_sable.sql +66 -0
- package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +438 -0
- package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +13 -0
- package/src/__tests__/fixtures/basic/schema.ts +35 -0
- package/src/__tests__/fixtures/basic/tasks.ts +15 -0
- package/src/__tests__/fixtures/common/auth-schema.ts +63 -0
- package/src/__tests__/helpers/pglite-migrations.ts +35 -0
- package/src/__tests__/helpers/pglite-rls-session.ts +54 -0
- package/src/__tests__/helpers/seed-like-fill.ts +196 -0
- package/src/__tests__/helpers/test-gencow-ctx-rls.ts +53 -0
- package/src/__tests__/image-optimization.test.ts +652 -0
- package/src/__tests__/rls-crud-basic.test.ts +431 -0
- package/src/__tests__/tsconfig.json +8 -0
- package/src/crud.ts +272 -49
- package/src/index.ts +18 -17
- package/src/reactive.ts +4 -4
- package/src/rls-db.ts +3 -5
- package/src/rls.ts +87 -3
- package/src/server.ts +5 -4
- package/src/storage.ts +473 -8
- package/dist/scoped-db.d.ts +0 -34
- package/dist/scoped-db.js +0 -364
- package/dist/table.d.ts +0 -67
- package/dist/table.js +0 -98
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fixtures/basic + PGlite — apply migrations, explicit fixtures, verify tasks.list CRUD handler.
|
|
3
|
+
*
|
|
4
|
+
* Users and tasks: fixed `id` / FK / `done` per row. Any other column omitted from the fixture is
|
|
5
|
+
* filled by drizzle-seed’s same per-type generators as `seed()` (see `fillPartialRowsForInsert`).
|
|
6
|
+
*
|
|
7
|
+
* Migrations run as the PGlite bootstrap user (table owner). We then create `gencow_rls_app` and
|
|
8
|
+
* `SET ROLE` so the session is non-owner → RLS policies apply. `createRlsDb` + crud’s use of
|
|
9
|
+
* `db.transaction` sets `app.current_user_id` per operation.
|
|
10
|
+
* We rely on `current_setting('app.current_user_id', true)` (missing_ok=true): this avoids
|
|
11
|
+
* missing-GUC errors before `set_config` and keeps PGlite behavior aligned with PostgreSQL.
|
|
12
|
+
*
|
|
13
|
+
* Run: bun test packages/core/src/__tests__/rls-crud-basic.test.ts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
describe,
|
|
18
|
+
it,
|
|
19
|
+
expect,
|
|
20
|
+
beforeAll,
|
|
21
|
+
afterAll,
|
|
22
|
+
} from "bun:test";
|
|
23
|
+
import { dirname, join } from "path";
|
|
24
|
+
import { fileURLToPath } from "url";
|
|
25
|
+
import type { InferSelectModel } from "drizzle-orm";
|
|
26
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
27
|
+
import { drizzle } from "drizzle-orm/pglite";
|
|
28
|
+
import { crud } from "../crud";
|
|
29
|
+
import type { UserIdentity } from "../reactive";
|
|
30
|
+
import { tasks, user } from "./fixtures/basic/schema";
|
|
31
|
+
import {
|
|
32
|
+
createPgliteRlsAppRole,
|
|
33
|
+
DEFAULT_PGLITE_RLS_APP_ROLE,
|
|
34
|
+
setPgliteSessionRole,
|
|
35
|
+
} from "./helpers/pglite-rls-session";
|
|
36
|
+
import { loadAndApplyMigrations } from "./helpers/pglite-migrations";
|
|
37
|
+
import { fillPartialRowsForInsert } from "./helpers/seed-like-fill";
|
|
38
|
+
import {
|
|
39
|
+
makeTestGencowCtxWithRls,
|
|
40
|
+
runWithRollbackTestGencowCtxWithRls,
|
|
41
|
+
} from "./helpers/test-gencow-ctx-rls";
|
|
42
|
+
|
|
43
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
|
|
45
|
+
const fixtureUsers = [
|
|
46
|
+
{ id: "us_000", name: "User 0", email: "user-0@s.com", emailVerified: true },
|
|
47
|
+
{ id: "us_001", name: "User 1", email: "user-1@s.com", emailVerified: true },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/** Realistic titles; "Project Alpha" appears on two rows for us_000 and one for us_001 (RLS). */
|
|
51
|
+
const fixtureTasks = [
|
|
52
|
+
{
|
|
53
|
+
id: "tk-000",
|
|
54
|
+
userId: fixtureUsers[0].id,
|
|
55
|
+
done: false,
|
|
56
|
+
title: "Project Alpha — Q4 review prep",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "tk-001",
|
|
60
|
+
userId: fixtureUsers[1].id,
|
|
61
|
+
done: true,
|
|
62
|
+
title: "Project Alpha — teammate handoff",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "tk-002",
|
|
66
|
+
userId: fixtureUsers[0].id,
|
|
67
|
+
done: false,
|
|
68
|
+
title: "Project Alpha — backlog grooming",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "tk-003",
|
|
72
|
+
userId: fixtureUsers[0].id,
|
|
73
|
+
done: false,
|
|
74
|
+
title: "Quarterly planning — Q4",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "tk-004",
|
|
78
|
+
userId: fixtureUsers[1].id,
|
|
79
|
+
done: false,
|
|
80
|
+
title: "Project Beta — API docs",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "tk-005",
|
|
84
|
+
userId: fixtureUsers[1].id,
|
|
85
|
+
done: false,
|
|
86
|
+
title: "Project Gamma — research notes",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "tk-006",
|
|
90
|
+
userId: fixtureUsers[0].id,
|
|
91
|
+
done: false,
|
|
92
|
+
title: "Project Beta — spike",
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const user0Identity = {
|
|
97
|
+
id: fixtureUsers[0].id,
|
|
98
|
+
email: fixtureUsers[0].email,
|
|
99
|
+
} satisfies UserIdentity;
|
|
100
|
+
|
|
101
|
+
const user1Identity = {
|
|
102
|
+
id: fixtureUsers[1].id,
|
|
103
|
+
email: fixtureUsers[1].email,
|
|
104
|
+
} satisfies UserIdentity;
|
|
105
|
+
|
|
106
|
+
type TaskRow = InferSelectModel<typeof tasks>;
|
|
107
|
+
type CrudDefs = ReturnType<typeof crud<typeof tasks>>;
|
|
108
|
+
type TaskListResult = { data: TaskRow[]; total: number };
|
|
109
|
+
|
|
110
|
+
async function seedBasicFixtures(db: ReturnType<typeof drizzle>) {
|
|
111
|
+
await db.insert(user).values(fillPartialRowsForInsert(user, fixtureUsers));
|
|
112
|
+
const taskRows = fillPartialRowsForInsert(
|
|
113
|
+
tasks,
|
|
114
|
+
fixtureTasks
|
|
115
|
+
) as TaskRow[];
|
|
116
|
+
await db.insert(tasks).values(taskRows);
|
|
117
|
+
return taskRows;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function createSeededCrudEnv() {
|
|
121
|
+
const client = new PGlite();
|
|
122
|
+
await client.waitReady;
|
|
123
|
+
await loadAndApplyMigrations(
|
|
124
|
+
client,
|
|
125
|
+
join(__dirname, "fixtures/basic/migrations")
|
|
126
|
+
);
|
|
127
|
+
const db = drizzle(client);
|
|
128
|
+
const taskRows = await seedBasicFixtures(db);
|
|
129
|
+
await createPgliteRlsAppRole(client, {
|
|
130
|
+
roleName: DEFAULT_PGLITE_RLS_APP_ROLE,
|
|
131
|
+
});
|
|
132
|
+
await setPgliteSessionRole(client, DEFAULT_PGLITE_RLS_APP_ROLE);
|
|
133
|
+
|
|
134
|
+
const defs = crud(tasks, {
|
|
135
|
+
prefix: "fixture_basic_pglite_tasks",
|
|
136
|
+
searchFields: ["title", "description"],
|
|
137
|
+
defaultLimit: 50,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return { client, db, taskRows, defs };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
describe("fixtures/basic + PGlite + CRUD + RLS", () => {
|
|
144
|
+
let client: PGlite;
|
|
145
|
+
let db: ReturnType<typeof drizzle>;
|
|
146
|
+
let seededTaskRows: TaskRow[];
|
|
147
|
+
let listDef: CrudDefs["list"];
|
|
148
|
+
let listHandler: (ctx: unknown, args: unknown) => Promise<TaskListResult>;
|
|
149
|
+
let getHandler: NonNullable<CrudDefs["get"]>["handler"];
|
|
150
|
+
let createHandler: NonNullable<CrudDefs["create"]>["handler"];
|
|
151
|
+
let updateHandler: NonNullable<CrudDefs["update"]>["handler"];
|
|
152
|
+
let removeHandler: NonNullable<CrudDefs["remove"]>["handler"];
|
|
153
|
+
|
|
154
|
+
beforeAll(async () => {
|
|
155
|
+
const env = await createSeededCrudEnv();
|
|
156
|
+
client = env.client;
|
|
157
|
+
db = env.db;
|
|
158
|
+
seededTaskRows = env.taskRows;
|
|
159
|
+
listDef = env.defs.list;
|
|
160
|
+
listHandler = env.defs.list!.handler as (
|
|
161
|
+
ctx: unknown,
|
|
162
|
+
args: unknown
|
|
163
|
+
) => Promise<TaskListResult>;
|
|
164
|
+
getHandler = env.defs.get!.handler as (
|
|
165
|
+
ctx: unknown,
|
|
166
|
+
args: unknown
|
|
167
|
+
) => Promise<TaskRow | null>;
|
|
168
|
+
createHandler = env.defs.create!.handler as (
|
|
169
|
+
ctx: unknown,
|
|
170
|
+
args: unknown
|
|
171
|
+
) => Promise<TaskRow>;
|
|
172
|
+
updateHandler = env.defs.update!.handler as (
|
|
173
|
+
ctx: unknown,
|
|
174
|
+
args: unknown
|
|
175
|
+
) => Promise<TaskRow | undefined>;
|
|
176
|
+
removeHandler = env.defs.remove!.handler as (
|
|
177
|
+
ctx: unknown,
|
|
178
|
+
args: unknown
|
|
179
|
+
) => Promise<{ success: boolean }>;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
afterAll(async () => {
|
|
183
|
+
try {
|
|
184
|
+
await client.close();
|
|
185
|
+
} catch {
|
|
186
|
+
/* ignore */
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("tasks.list returns only the current user (us_000) rows per RLS and total matches", async () => {
|
|
191
|
+
expect(listDef).toBeDefined();
|
|
192
|
+
|
|
193
|
+
await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
|
|
194
|
+
const listResult = await listHandler(ctx, {});
|
|
195
|
+
|
|
196
|
+
const expected = seededTaskRows.filter(
|
|
197
|
+
(r) => r.userId === fixtureUsers[0].id
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(listResult).toHaveProperty("data");
|
|
201
|
+
expect(listResult).toHaveProperty("total");
|
|
202
|
+
expect(listResult.total).toBe(expected.length);
|
|
203
|
+
|
|
204
|
+
const rows = listResult.data;
|
|
205
|
+
|
|
206
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
207
|
+
expect(byId.size).toBe(expected.length);
|
|
208
|
+
|
|
209
|
+
for (const seeded of expected) {
|
|
210
|
+
const row = byId.get(seeded.id);
|
|
211
|
+
expect(row).toBeDefined();
|
|
212
|
+
expect(row!.id).toBe(seeded.id);
|
|
213
|
+
expect(row!.userId).toBe(seeded.userId);
|
|
214
|
+
expect(row!.done).toBe(seeded.done);
|
|
215
|
+
expect(row!.title).toBe(seeded.title);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("search parameter applies to title/description ilike search", async () => {
|
|
221
|
+
expect(listDef).toBeDefined();
|
|
222
|
+
|
|
223
|
+
await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
|
|
224
|
+
const sharedSubstring = "Project Alpha";
|
|
225
|
+
const expectedForUser0 = seededTaskRows.filter(
|
|
226
|
+
(r) =>
|
|
227
|
+
r.userId === user0Identity.id &&
|
|
228
|
+
r.title.toLowerCase().includes(sharedSubstring.toLowerCase())
|
|
229
|
+
);
|
|
230
|
+
expect(expectedForUser0.length).toBe(2);
|
|
231
|
+
|
|
232
|
+
const result = await listHandler(ctx, { search: sharedSubstring });
|
|
233
|
+
const rows = result.data;
|
|
234
|
+
expect(rows.length).toBe(expectedForUser0.length);
|
|
235
|
+
|
|
236
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
237
|
+
for (const seeded of expectedForUser0) {
|
|
238
|
+
expect(byId.get(seeded.id)?.title).toBe(seeded.title);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("tasks.get succeeds when current user reads own task", async () => {
|
|
244
|
+
const ownTaskId = "tk-003";
|
|
245
|
+
|
|
246
|
+
await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
|
|
247
|
+
const task = await getHandler(ctx, { id: ownTaskId });
|
|
248
|
+
|
|
249
|
+
expect(task).toBeDefined();
|
|
250
|
+
expect(task?.id).toBe(ownTaskId);
|
|
251
|
+
expect(task?.userId).toBe(user0Identity.id);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("tasks.get fails when current user tries to read another user's task", async () => {
|
|
256
|
+
const user1TaskId = "tk-001";
|
|
257
|
+
|
|
258
|
+
await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
|
|
259
|
+
const task = await getHandler(ctx, { id: user1TaskId });
|
|
260
|
+
expect(task).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("tasks.update succeeds when updating a task owned by current user", async () => {
|
|
265
|
+
const ownTaskId = "tk-000";
|
|
266
|
+
const updatedTitle = "Project Alpha — updated by owner";
|
|
267
|
+
const updatedDone = true;
|
|
268
|
+
|
|
269
|
+
await runWithRollbackTestGencowCtxWithRls(
|
|
270
|
+
db,
|
|
271
|
+
user0Identity,
|
|
272
|
+
async (ctx) => {
|
|
273
|
+
const before = await getHandler(ctx, {
|
|
274
|
+
id: ownTaskId,
|
|
275
|
+
});
|
|
276
|
+
expect(before?.title).not.toBe(updatedTitle);
|
|
277
|
+
expect(before?.done).not.toBe(updatedDone);
|
|
278
|
+
|
|
279
|
+
const updated = await updateHandler(ctx, {
|
|
280
|
+
id: ownTaskId,
|
|
281
|
+
title: updatedTitle,
|
|
282
|
+
done: updatedDone,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(updated).toBeDefined();
|
|
286
|
+
expect(updated?.id).toBe(ownTaskId);
|
|
287
|
+
expect(updated?.userId).toBe(user0Identity.id);
|
|
288
|
+
expect(updated?.title).toBe(updatedTitle);
|
|
289
|
+
expect(updated?.done).toBe(updatedDone);
|
|
290
|
+
|
|
291
|
+
const fetched = await getHandler(ctx, {
|
|
292
|
+
id: ownTaskId,
|
|
293
|
+
});
|
|
294
|
+
expect(fetched?.id).toBe(ownTaskId);
|
|
295
|
+
expect(fetched?.title).toBe(updatedTitle);
|
|
296
|
+
expect(fetched?.done).toBe(updatedDone);
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("tasks.create succeeds when creating a task with current userId", async () => {
|
|
302
|
+
const ownTaskId = "tk-own-create";
|
|
303
|
+
const ownTaskTitle = "Project Delta — created by owner";
|
|
304
|
+
|
|
305
|
+
await runWithRollbackTestGencowCtxWithRls(
|
|
306
|
+
db,
|
|
307
|
+
user0Identity,
|
|
308
|
+
async (user0Ctx) => {
|
|
309
|
+
const created = await createHandler(user0Ctx, {
|
|
310
|
+
id: ownTaskId,
|
|
311
|
+
userId: user0Identity.id,
|
|
312
|
+
title: ownTaskTitle,
|
|
313
|
+
done: false,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(created).toBeDefined();
|
|
317
|
+
expect(created.id).toBe(ownTaskId);
|
|
318
|
+
expect(created.userId).toBe(user0Identity.id);
|
|
319
|
+
expect(created.title).toBe(ownTaskTitle);
|
|
320
|
+
expect(created.done).toBe(false);
|
|
321
|
+
|
|
322
|
+
const fetched = await getHandler(user0Ctx, {
|
|
323
|
+
id: ownTaskId,
|
|
324
|
+
});
|
|
325
|
+
expect(fetched).toBeDefined();
|
|
326
|
+
expect(fetched?.id).toBe(ownTaskId);
|
|
327
|
+
expect(fetched?.userId).toBe(user0Identity.id);
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("tasks.create fails when current user tries to create with another userId", async () => {
|
|
333
|
+
const unauthorizedTaskId = "tk-other-user-create";
|
|
334
|
+
await runWithRollbackTestGencowCtxWithRls(
|
|
335
|
+
db,
|
|
336
|
+
user0Identity,
|
|
337
|
+
async (user0Ctx) => {
|
|
338
|
+
await expect(
|
|
339
|
+
createHandler(user0Ctx, {
|
|
340
|
+
id: unauthorizedTaskId,
|
|
341
|
+
userId: user1Identity.id,
|
|
342
|
+
title: "Unauthorized create attempt",
|
|
343
|
+
done: false,
|
|
344
|
+
})
|
|
345
|
+
).rejects.toThrow();
|
|
346
|
+
|
|
347
|
+
const user1Ctx = makeTestGencowCtxWithRls(user0Ctx.unsafeDb as any, user1Identity);
|
|
348
|
+
const after = await getHandler(user1Ctx, { id: unauthorizedTaskId });
|
|
349
|
+
expect(after).toBeNull();
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("tasks.update fails when current user tries to update user1 task", async () => {
|
|
355
|
+
const user1TaskId = "tk-001";
|
|
356
|
+
await runWithRollbackTestGencowCtxWithRls(
|
|
357
|
+
db,
|
|
358
|
+
user0Identity,
|
|
359
|
+
async (user0Ctx) => {
|
|
360
|
+
const user1Ctx = makeTestGencowCtxWithRls(user0Ctx.unsafeDb as any, user1Identity);
|
|
361
|
+
|
|
362
|
+
const before = await getHandler(user1Ctx, {
|
|
363
|
+
id: user1TaskId,
|
|
364
|
+
});
|
|
365
|
+
expect(before).toBeDefined();
|
|
366
|
+
const beforeTitle = before?.title;
|
|
367
|
+
const beforeDone = before?.done ?? false;
|
|
368
|
+
|
|
369
|
+
const unauthorizedUpdate = await updateHandler(user0Ctx, {
|
|
370
|
+
id: user1TaskId,
|
|
371
|
+
title: "Unauthorized update attempt",
|
|
372
|
+
done: !beforeDone,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(unauthorizedUpdate).toBeUndefined();
|
|
376
|
+
|
|
377
|
+
const after = await getHandler(user1Ctx, {
|
|
378
|
+
id: user1TaskId,
|
|
379
|
+
});
|
|
380
|
+
expect(after).toBeDefined();
|
|
381
|
+
expect(after?.title).toBe(beforeTitle);
|
|
382
|
+
expect(after?.done).toBe(beforeDone);
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("tasks.remove succeeds when deleting a task owned by current user", async () => {
|
|
388
|
+
const ownTaskId = "tk-002";
|
|
389
|
+
await runWithRollbackTestGencowCtxWithRls(
|
|
390
|
+
db,
|
|
391
|
+
user0Identity,
|
|
392
|
+
async (user0Ctx) => {
|
|
393
|
+
const before = await getHandler(user0Ctx, { id: ownTaskId });
|
|
394
|
+
expect(before).toBeDefined();
|
|
395
|
+
|
|
396
|
+
const removeResult = await removeHandler(user0Ctx, {
|
|
397
|
+
id: ownTaskId,
|
|
398
|
+
});
|
|
399
|
+
expect(removeResult.success).toBe(true);
|
|
400
|
+
|
|
401
|
+
const after = await getHandler(user0Ctx, { id: ownTaskId });
|
|
402
|
+
expect(after).toBeNull();
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("tasks.remove cannot delete a task owned by another user", async () => {
|
|
408
|
+
const user1TaskId = "tk-004";
|
|
409
|
+
await runWithRollbackTestGencowCtxWithRls(
|
|
410
|
+
db,
|
|
411
|
+
user0Identity,
|
|
412
|
+
async (user0Ctx) => {
|
|
413
|
+
const user1Ctx = makeTestGencowCtxWithRls(user0Ctx.unsafeDb as any, user1Identity);
|
|
414
|
+
|
|
415
|
+
const before = await getHandler(user1Ctx, { id: user1TaskId });
|
|
416
|
+
expect(before).toBeDefined();
|
|
417
|
+
|
|
418
|
+
const removeResult = await removeHandler(user0Ctx, {
|
|
419
|
+
id: user1TaskId,
|
|
420
|
+
});
|
|
421
|
+
expect(removeResult.success).toBe(true);
|
|
422
|
+
|
|
423
|
+
const after = await getHandler(user1Ctx, {
|
|
424
|
+
id: user1TaskId,
|
|
425
|
+
});
|
|
426
|
+
expect(after).toBeDefined();
|
|
427
|
+
expect(after?.id).toBe(user1TaskId);
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
});
|