Files
2026-05-27 23:29:53 +01:00

129 lines
4.5 KiB
TypeScript

import {
VAULT_PLUGIN_CODES,
VaultPluginError,
} from "../../src/error-codes/error-codes.js";
import type { VaultService } from "../../src/services/vault-service.js";
import type {
VaultHandle,
VaultItem,
VaultItemSummary,
} from "../../src/types/types.js";
/**
* Lightweight in-memory `VaultService` used by unit tests so route
* coverage does not pay for a real Argon2id pipeline. Stores items in a
* per-principal Map.
*/
export interface FakeVaultServiceOptions {
/** Maps principalId → expected password. Wrong password rejects. */
readonly passwords?: Record<string, string>;
}
export function createFakeVaultService(
options?: FakeVaultServiceOptions
): VaultService {
const passwords = options?.passwords ?? {};
const byPrincipal = new Map<string, Map<string, VaultItem>>();
const handles = new WeakMap<VaultHandle, { principalId: string }>();
let idCounter = 0;
function bucket(principalId: string): Map<string, VaultItem> {
let b = byPrincipal.get(principalId);
if (b === undefined) {
b = new Map();
byPrincipal.set(principalId, b);
}
return b;
}
function getState(handle: VaultHandle) {
const s = handles.get(handle);
if (s === undefined) {
throw new VaultPluginError(
VAULT_PLUGIN_CODES.FORBIDDEN,
"fake handle invalid",
{ context: { statusCode: 403 } }
);
}
return s;
}
return {
async openForPrincipal(principal, password) {
const expected = passwords[principal.id];
if (expected !== undefined && expected !== password) {
throw new VaultPluginError(
VAULT_PLUGIN_CODES.UNLOCK_FAILED,
"fake unlock failed",
{ context: { statusCode: 401 } }
);
}
const handle: VaultHandle = Object.freeze({
__brand: "VaultHandle" as const,
principalId: principal.id,
});
handles.set(handle, { principalId: principal.id });
return handle;
},
async listItemsForOwner(ownerId) {
const b = byPrincipal.get(ownerId);
if (b === undefined) return [];
const out: VaultItemSummary[] = [];
for (const i of b.values()) {
const { body: _b, ...rest } = i;
void _b;
out.push(rest);
}
return out;
},
async listItems(handle) {
const { principalId } = getState(handle);
return Array.from(bucket(principalId).values());
},
async readItem(handle, itemId) {
const { principalId } = getState(handle);
return bucket(principalId).get(itemId) ?? null;
},
async createItem(handle, input) {
const { principalId } = getState(handle);
idCounter += 1;
const now = new Date().toISOString();
const item: VaultItem = {
id: `item-${idCounter}`,
ownerId: principalId,
title: input.title,
kind: input.kind,
body: input.body,
createdAt: now,
updatedAt: now,
...(input.metadata !== undefined
? { metadata: input.metadata }
: {}),
};
bucket(principalId).set(item.id, item);
return item;
},
async updateItem(handle, itemId, patch) {
const { principalId } = getState(handle);
const existing = bucket(principalId).get(itemId);
if (existing === undefined) return null;
const updated: VaultItem = {
...existing,
...(patch.title !== undefined ? { title: patch.title } : {}),
...(patch.kind !== undefined ? { kind: patch.kind } : {}),
...(patch.body !== undefined ? { body: patch.body } : {}),
...(patch.metadata !== undefined
? { metadata: patch.metadata }
: {}),
updatedAt: new Date().toISOString(),
};
bucket(principalId).set(itemId, updated);
return updated;
},
async deleteItem(handle, itemId) {
const { principalId } = getState(handle);
return bucket(principalId).delete(itemId);
},
async close(handle) {
handles.delete(handle);
},
};
}