Initial Commit
This commit is contained in:
295
packages/security-stack/security/vault/src/shares/shares.test.ts
Normal file
295
packages/security-stack/security/vault/src/shares/shares.test.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { VAULT_CODES } from "../errors/codes.js";
|
||||
import { VaultError } from "../errors/vault-error.js";
|
||||
import type {
|
||||
CreatedShareLink,
|
||||
CreateShareLinkInput,
|
||||
DriveSharingLike,
|
||||
} from "../types/adapters.js";
|
||||
import type { ShareLink } from "../types/types.js";
|
||||
import { VaultShares } from "./shares.js";
|
||||
|
||||
function vaultKey(): Uint8Array {
|
||||
const key = new Uint8Array(32);
|
||||
crypto.getRandomValues(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
class StubDriveSharing implements DriveSharingLike {
|
||||
public received: CreateShareLinkInput[] = [];
|
||||
public revoked: string[] = [];
|
||||
public failOn: "create" | "revoke" | undefined;
|
||||
public counter = 0;
|
||||
public async createLink(
|
||||
input: CreateShareLinkInput
|
||||
): Promise<CreatedShareLink> {
|
||||
if (this.failOn === "create") throw new Error("offline");
|
||||
this.received.push(input);
|
||||
this.counter += 1;
|
||||
const id = `share-${this.counter}`;
|
||||
return { id, url: `https://share.example/${id}` };
|
||||
}
|
||||
public async revokeLink(id: string): Promise<void> {
|
||||
if (this.failOn === "revoke") throw new Error("offline");
|
||||
this.revoked.push(id);
|
||||
}
|
||||
public async isActive(id: string): Promise<boolean> {
|
||||
return !this.revoked.includes(id);
|
||||
}
|
||||
}
|
||||
|
||||
function makeShares(driveSharing?: DriveSharingLike): {
|
||||
shares: VaultShares;
|
||||
driveSharing: StubDriveSharing;
|
||||
metadata: Map<string, ShareLink>;
|
||||
} {
|
||||
const stub =
|
||||
(driveSharing as StubDriveSharing | undefined) ??
|
||||
new StubDriveSharing();
|
||||
const metadata = new Map<string, ShareLink>();
|
||||
const shares = new VaultShares({
|
||||
driveSharing: stub,
|
||||
partitionId: "part-1",
|
||||
vaultKey: vaultKey(),
|
||||
clock: () => 1,
|
||||
rng: undefined,
|
||||
metadata,
|
||||
onMutation: () => {},
|
||||
});
|
||||
return { shares, driveSharing: stub, metadata };
|
||||
}
|
||||
|
||||
describe("VaultShares", () => {
|
||||
it("createLink wraps the plaintext and returns metadata", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { shares, driveSharing } = makeShares();
|
||||
const link = await shares.createLink({
|
||||
itemId: "i1",
|
||||
itemPlaintext: new TextEncoder().encode("payload"),
|
||||
expiresAt: 99,
|
||||
maxViews: 5,
|
||||
});
|
||||
expect(link.id).toBe("share-1");
|
||||
expect(link.url).toContain("share-1");
|
||||
expect(driveSharing.received[0]?.partitionId).toBe("part-1");
|
||||
expect(driveSharing.received[0]?.wrappedKey.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("createLink wraps drive failures as DRIVE.FAILURE", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const stub = new StubDriveSharing();
|
||||
stub.failOn = "create";
|
||||
const { shares } = makeShares(stub);
|
||||
try {
|
||||
await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
throw new Error("should have thrown");
|
||||
} catch (err) {
|
||||
expect(VaultError.is(err)).toBe(true);
|
||||
expect((err as VaultError).code).toBe(VAULT_CODES["DRIVE.FAILURE"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("revokeLink removes local metadata", { timeout: 5000 }, async () => {
|
||||
const { shares, driveSharing, metadata } = makeShares();
|
||||
const link = await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
await shares.revokeLink(link.id);
|
||||
expect(metadata.has(link.id)).toBe(false);
|
||||
expect(driveSharing.revoked).toEqual([link.id]);
|
||||
});
|
||||
|
||||
it("revokeLink throws SHARE.NOT_FOUND on unknown ids", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { shares } = makeShares();
|
||||
try {
|
||||
await shares.revokeLink("missing");
|
||||
throw new Error("should have thrown");
|
||||
} catch (err) {
|
||||
expect(VaultError.is(err)).toBe(true);
|
||||
expect((err as VaultError).code).toBe(
|
||||
VAULT_CODES["SHARE.NOT_FOUND"]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("revokeLink wraps drive failures as DRIVE.FAILURE", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const stub = new StubDriveSharing();
|
||||
const { shares } = makeShares(stub);
|
||||
const link = await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
stub.failOn = "revoke";
|
||||
try {
|
||||
await shares.revokeLink(link.id);
|
||||
throw new Error("should have thrown");
|
||||
} catch (err) {
|
||||
expect(VaultError.is(err)).toBe(true);
|
||||
expect((err as VaultError).code).toBe(VAULT_CODES["DRIVE.FAILURE"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("isActive short-circuits when local metadata is missing", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { shares } = makeShares();
|
||||
expect(await shares.isActive("missing")).toBe(false);
|
||||
});
|
||||
|
||||
it("isActive returns the canonical answer from drive-sharing", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { shares } = makeShares();
|
||||
const link = await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
expect(await shares.isActive(link.id)).toBe(true);
|
||||
await shares.revokeLink(link.id);
|
||||
// After revoke, metadata is removed locally; isActive returns false.
|
||||
expect(await shares.isActive(link.id)).toBe(false);
|
||||
});
|
||||
|
||||
it("list returns all known share metadata", { timeout: 5000 }, async () => {
|
||||
const { shares } = makeShares();
|
||||
await shares.createLink({
|
||||
itemId: "a",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
await shares.createLink({
|
||||
itemId: "b",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
expect(shares.list().length).toBe(2);
|
||||
});
|
||||
|
||||
it("createLink with an injected RNG produces deterministic wrappedKey nonces", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const stub = new StubDriveSharing();
|
||||
const metadata = new Map<string, ShareLink>();
|
||||
const rng = {
|
||||
nextUint32: () => 1,
|
||||
nextFloat: () => 0,
|
||||
nextBytes: (len: number) => new Uint8Array(len).fill(2),
|
||||
};
|
||||
const shares = new VaultShares({
|
||||
driveSharing: stub,
|
||||
partitionId: "p",
|
||||
vaultKey: new Uint8Array(32),
|
||||
clock: () => 0,
|
||||
rng,
|
||||
metadata,
|
||||
onMutation: () => {},
|
||||
});
|
||||
await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
const wrapped = stub.received[0]?.wrappedKey as Uint8Array;
|
||||
expect(wrapped.slice(0, 24)).toEqual(new Uint8Array(24).fill(2));
|
||||
});
|
||||
|
||||
it("non-Error createLink failures still surface as DRIVE.FAILURE", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const sentinel: unknown = "non-error";
|
||||
class BadStub extends StubDriveSharing {
|
||||
public override async createLink(): Promise<CreatedShareLink> {
|
||||
throw sentinel;
|
||||
}
|
||||
}
|
||||
const stub = new BadStub();
|
||||
const { shares } = makeShares(stub);
|
||||
try {
|
||||
await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
throw new Error("should have thrown");
|
||||
} catch (err) {
|
||||
expect(VaultError.is(err)).toBe(true);
|
||||
expect((err as VaultError).code).toBe(VAULT_CODES["DRIVE.FAILURE"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("non-Error revokeLink failures still surface as DRIVE.FAILURE", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const sentinel: unknown = "non-error";
|
||||
class BadStub extends StubDriveSharing {
|
||||
public override async revokeLink(): Promise<void> {
|
||||
throw sentinel;
|
||||
}
|
||||
}
|
||||
const stub = new BadStub();
|
||||
const { shares } = makeShares(stub);
|
||||
const link = await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
try {
|
||||
await shares.revokeLink(link.id);
|
||||
throw new Error("should have thrown");
|
||||
} catch (err) {
|
||||
expect(VaultError.is(err)).toBe(true);
|
||||
expect((err as VaultError).code).toBe(VAULT_CODES["DRIVE.FAILURE"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("createLink validates the produced ShareLink", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
class BadStub extends StubDriveSharing {
|
||||
public override async createLink(
|
||||
_input: CreateShareLinkInput
|
||||
): Promise<CreatedShareLink> {
|
||||
return { id: "x", url: "not-a-url" };
|
||||
}
|
||||
}
|
||||
const stub = new BadStub();
|
||||
const { shares } = makeShares(stub);
|
||||
try {
|
||||
await shares.createLink({
|
||||
itemId: "i",
|
||||
itemPlaintext: new Uint8Array(1),
|
||||
expiresAt: null,
|
||||
maxViews: null,
|
||||
});
|
||||
throw new Error("should have thrown");
|
||||
} catch (err) {
|
||||
expect(VaultError.is(err)).toBe(true);
|
||||
expect((err as VaultError).code).toBe(
|
||||
VAULT_CODES["ITEM.VALIDATION_FAILED"]
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user