24 lines
1.7 KiB
TypeScript
24 lines
1.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { after, test } from "node:test";
|
|
import { createHash, randomUUID } from "node:crypto";
|
|
import { rmSync } from "node:fs";
|
|
|
|
const databasePath = `/tmp/mebbling-invites-${randomUUID()}.db`;
|
|
process.env.DATABASE_PATH = databasePath;
|
|
delete process.env.HUB_BUILD;
|
|
let database: typeof import("../lib/db").db | undefined;
|
|
after(() => { database?.close(); rmSync(databasePath, { force: true }); rmSync(`${databasePath}-wal`, { force: true }); rmSync(`${databasePath}-shm`, { force: true }); });
|
|
|
|
test("consumes a source invitation exactly once and grants its role", async () => {
|
|
const { db } = await import("../lib/db"); database = db;
|
|
const { acceptSourceInvite } = await import("../lib/invites");
|
|
const owner = Number(db.prepare("INSERT INTO users(username,password_hash) VALUES('owner','hash')").run().lastInsertRowid);
|
|
const member = Number(db.prepare("INSERT INTO users(username,password_hash) VALUES('member','hash')").run().lastInsertRowid);
|
|
const source = Number(db.prepare("INSERT INTO sources(user_id,name,base_url,token_encrypted) VALUES(?,?,?,?)").run(owner, "Source", "https://example.test", "encrypted").lastInsertRowid);
|
|
const token = "single-use-token";
|
|
db.prepare("INSERT INTO source_invites(source_id,token_hash,role,expires_at,created_by) VALUES(?,?,?,datetime('now','+1 day'),?)").run(source, createHash("sha256").update(token).digest("hex"), "editor", owner);
|
|
assert.deepEqual(acceptSourceInvite(member, token), { id: 1, source_id: source, role: "editor" });
|
|
assert.equal((db.prepare("SELECT role FROM source_members WHERE source_id=? AND user_id=?").get(source, member) as { role: string }).role, "editor");
|
|
assert.throws(() => acceptSourceInvite(member, token), /已使用/);
|
|
});
|