1import { expect } from "@alchemy.run/test";
2import * as Test from "@alchemy.run/test/Alchemy";
3import * as Turso from "@alchemy.run/turso";
4import { organization } from "@alchemy.run/turso/Credentials";
5import { Credentials, DEFAULT_API_BASE_URL } from "@distilled.cloud/turso/Credentials";
6import * as turso from "@distilled.cloud/turso/turso";
7import { Stage } from "alchemy/Stage";
8import * as Effect from "effect/Effect";
9
10const { test } = Test.make({ providers: Turso.providers() });
11
12const listed = (name: string) =>
13 turso
14 .listAPITokens({})
15 .pipe(Effect.map(({ tokens }) => (tokens ?? []).find((t) => t.name === name)));
16
17test.provider(
18 "group-scoped API token is minted, usable, and revoked on destroy",
19 (stack) =>
20 Effect.gen(function* () {
21 yield* stack.destroy();
22 const org = yield* organization;
23
24 const { group, token } = yield* stack.deploy(
25 Effect.gen(function* () {
26 const group = yield* Turso.Group("Group", { location: "aws-us-east-1" });
27 const token = yield* Turso.ApiToken("Token", {
28 group: group.name,
29 scopes: ["read", "db:create", "db:delete"],
30 });
31 return { group, token };
32 }),
33 );
34 expect(token.organization).toBe(org);
35 expect(token.group).toBe(group.name);
36 expect(token.scopes).toEqual(expect.arrayContaining(["read", "db:create", "db:delete"]));
37 expect((yield* listed(token.name))?.id).toBe(token.id);
38
39 // The token can list the group's databases with its own credentials.
40 const { databases } = yield* turso
41 .listDatabases({ organizationSlug: org, group: group.name })
42 .pipe(
43 Effect.provideService(
44 Credentials,
45 Effect.succeed({ apiKey: token.token, apiBaseUrl: DEFAULT_API_BASE_URL }),
46 ),
47 );
48 expect(databases).toEqual([]);
49
50 // Changing the scopes replaces (re-mints) the token.
51 const replaced = yield* stack.deploy(
52 Effect.gen(function* () {
53 const group = yield* Turso.Group("Group", { location: "aws-us-east-1" });
54 return yield* Turso.ApiToken("Token", { group: group.name, scopes: ["read"] });
55 }),
56 );
57 expect(replaced.id).not.toBe(token.id);
58 expect(replaced.scopes).toEqual(["read"]);
59
60 yield* stack.destroy();
61 expect(yield* listed(token.name)).toBeUndefined();
62 expect(yield* listed(replaced.name)).toBeUndefined();
63 }),
64 { tags: ["provider:turso", "provider:turso:apitoken", "live"], timeout: 120_000 },
65);
66
67test.provider(
68 "changing the scopes of a token with a pinned name revokes the old one first",
69 (stack) =>
70 Effect.gen(function* () {
71 yield* stack.destroy();
72 const stage = yield* Stage;
73 const name = `alchemy-${stage}-pinned-token`;
74
75 const app = (scopes: Turso.ApiTokenScope[]) =>
76 Effect.gen(function* () {
77 const group = yield* Turso.Group("Group", { location: "aws-us-east-1" });
78 return yield* Turso.ApiToken("Token", { name, group: group.name, scopes });
79 });
80
81 const first = yield* stack.deploy(app(["read"]));
82 const second = yield* stack.deploy(app(["read", "db:create"]));
83 expect(second.name).toBe(name);
84 expect(second.id).not.toBe(first.id);
85 expect((yield* listed(name))?.id).toBe(second.id);
86
87 yield* stack.destroy();
88 expect(yield* listed(name)).toBeUndefined();
89 }),
90 { tags: ["provider:turso", "provider:turso:apitoken", "live"], timeout: 120_000 },
91);