Skip to content
Source
Sign inSign up

Tokens.test.ts

105 lines4.4 KB

1import { expect } from "@alchemy.run/test";
2import * as Test from "@alchemy.run/test/Alchemy";
3import * as Turso from "@alchemy.run/turso";
4import * as Hrana from "@alchemy.run/turso/Hrana";
5import * as Effect from "effect/Effect";
6import * as Redacted from "effect/Redacted";
7​
8const { test } = Test.make({ providers: Turso.providers() });
9​
10const run = (hostname: string, token: Redacted.Redacted<string>, statement: string) =>
11 Hrana.query({ url: `https://${hostname}`, authToken: token }, statement);
12​
13test.provider(
14 "database tokens authorize SQL at their access level",
15 (stack) =>
16 Effect.gen(function* () {
17 yield* stack.destroy();
18​
19 const app = (readOnlyExpiration?: string) =>
20 Effect.gen(function* () {
21 const group = yield* Turso.Group("Group", { location: "aws-us-east-1" });
22 const db = yield* Turso.Database("Db", { group: group.name });
23 const full = yield* Turso.DatabaseToken("Full", { database: db.name });
24 const readOnly = yield* Turso.DatabaseToken("ReadOnly", {
25 database: db.name,
26 authorization: "read-only",
27 expiration: readOnlyExpiration,
28 });
29 return { db, full, readOnly };
30 });
31​
32 const { db, full, readOnly } = yield* stack.deploy(app("1d"));
33 expect(full.expiresAt).toBeUndefined();
34 expect(readOnly.expiresAt).toBeGreaterThan(Date.now());
35 expect(readOnly.dbId).toBe(db.dbId);
36​
37 yield* run(db.hostname, full.token, "CREATE TABLE t (v INTEGER)");
38 yield* run(db.hostname, full.token, "INSERT INTO t VALUES (1)");
39 expect(yield* run(db.hostname, readOnly.token, "SELECT v FROM t")).toEqual([{ v: 1 }]);
40 const denied = yield* run(db.hostname, readOnly.token, "INSERT INTO t VALUES (2)").pipe(
41 Effect.flip,
42 );
43 expect(denied._tag).toBe("Turso::HranaError");
44​
45 // An unchanged redeploy keeps the tokens; a prop change re-mints.
46 const same = yield* stack.deploy(app("1d"));
47 expect(Redacted.value(same.full.token)).toBe(Redacted.value(full.token));
48 expect(Redacted.value(same.readOnly.token)).toBe(Redacted.value(readOnly.token));
49 const rotated = yield* stack.deploy(app("2d"));
50 expect(Redacted.value(rotated.readOnly.token)).not.toBe(Redacted.value(readOnly.token));
51 expect(rotated.readOnly.expiresAt).toBeGreaterThan(readOnly.expiresAt!);
52​
53 yield* stack.destroy();
54 }),
55 { tags: ["provider:turso", "provider:turso:databasetoken", "live"], timeout: 180_000 },
56);
57​
58test.provider(
59 "a group token authorizes every database in the group",
60 (stack) =>
61 Effect.gen(function* () {
62 yield* stack.destroy();
63​
64 const { a, b, token } = yield* stack.deploy(
65 Effect.gen(function* () {
66 const group = yield* Turso.Group("Group", { location: "aws-us-east-1" });
67 const a = yield* Turso.Database("A", { group: group.name });
68 const b = yield* Turso.Database("B", { group: group.name });
69 const token = yield* Turso.GroupToken("Token", { group: group.name });
70 return { a, b, token };
71 }),
72 );
73 expect(token.groupId).toBeTruthy();
74 expect(yield* run(a.hostname, token.token, "SELECT 1 AS one")).toEqual([{ one: 1 }]);
75 expect(yield* run(b.hostname, token.token, "SELECT 2 AS two")).toEqual([{ two: 2 }]);
76​
77 yield* stack.destroy();
78 }),
79 { tags: ["provider:turso", "provider:turso:grouptoken", "live"], timeout: 180_000 },
80);
81​
82test.provider(
83 "a token close to expiry is re-minted on deploy",
84 (stack) =>
85 Effect.gen(function* () {
86 yield* stack.destroy();
87​
88 const app = Effect.gen(function* () {
89 const group = yield* Turso.Group("Group", { location: "aws-us-east-1" });
90 const db = yield* Turso.Database("Db", { group: group.name });
91 // Expires within the hour, so every deploy re-mints it.
92 return yield* Turso.DatabaseToken("Short", { database: db.name, expiration: "30m" });
93 });
94​
95 const first = yield* stack.deploy(app);
96 // Turso tokens are deterministic per second of issue time.
97 yield* Effect.sleep("1100 millis");
98 const second = yield* stack.deploy(app);
99 expect(Redacted.value(second.token)).not.toBe(Redacted.value(first.token));
100 expect(second.expiresAt).toBeGreaterThan(first.expiresAt!);
101​
102 yield* stack.destroy();
103 }),
104 { tags: ["provider:turso", "provider:turso:databasetoken", "live"], timeout: 180_000 },
105);