Files
clean-architecture-frontend…/tests/unit/storage-manager-adapter.test.ts
T

205 lines
5.8 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createStorageDurabilityAdapter } from "../../src/adapters/browser-file-storage/storage-manager-adapter.ts";
describe("storage manager adapter", () => {
it("classifies rough origin pressure without treating it as a reservation", async () => {
const adapter = createStorageDurabilityAdapter({
estimate: async () => ({ usage: 75, quota: 100 }),
persisted: async () => false,
persist: async () => false,
});
expect(await adapter.inspect()).toEqual({
ok: true,
value: {
usageBytes: 75,
quotaBytes: 100,
persisted: false,
pressure: "PRESSURE",
},
});
});
it("snapshots pressure thresholds when the adapter is composed", async () => {
const policy = {
pressureRatio: 0.7,
criticalRatio: 0.85,
};
const adapter = createStorageDurabilityAdapter(
{
estimate: async () => ({ usage: 50, quota: 100 }),
},
policy,
);
policy.pressureRatio = 0.1;
policy.criticalRatio = 0.2;
await expect(adapter.inspect()).resolves.toMatchObject({
ok: true,
value: { pressure: "NORMAL" },
});
});
it("captures StorageManager methods when the adapter is composed", async () => {
const manager = {
estimate: async () => ({ usage: 10, quota: 100 }),
persisted: async () => false,
};
const adapter = createStorageDurabilityAdapter(manager);
manager.estimate = async () => ({ usage: 100, quota: 100 });
manager.persisted = async () => true;
await expect(adapter.inspect()).resolves.toEqual({
ok: true,
value: {
usageBytes: 10,
quotaBytes: 100,
persisted: false,
pressure: "NORMAL",
},
});
});
it("settles an aborted inspection while native reads remain pending", async () => {
const never = new Promise<never>(() => undefined);
const estimate = vi.fn(() => never);
const persisted = vi.fn(() => never);
const adapter = createStorageDurabilityAdapter({
estimate,
persisted,
});
const controller = new AbortController();
const inspection = adapter.inspect(controller.signal);
controller.abort();
await expect(inspection).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "STORAGE_ESTIMATE" },
});
expect(estimate).toHaveBeenCalledOnce();
expect(persisted).toHaveBeenCalledOnce();
});
it("consumes a late native rejection when abort wins during invocation", async () => {
const controller = new AbortController();
let rejectEstimate: ((reason: unknown) => void) | undefined;
const estimateResult = new Promise<StorageEstimate>(
(_resolve, reject) => {
rejectEstimate = reject;
},
);
const unhandled: unknown[] = [];
const onUnhandled = (reason: unknown): void => {
unhandled.push(reason);
};
process.on("unhandledRejection", onUnhandled);
try {
const adapter = createStorageDurabilityAdapter({
estimate: () => {
controller.abort();
return estimateResult;
},
});
await expect(adapter.inspect(controller.signal)).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "STORAGE_ESTIMATE" },
});
rejectEstimate?.(new Error("late estimate rejection"));
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
expect(unhandled).toEqual([]);
} finally {
process.off("unhandledRejection", onUnhandled);
}
});
it("requests persistence only through the explicit user-initiated port", async () => {
const persist = vi.fn(async () => true);
const adapter = createStorageDurabilityAdapter({
estimate: async () => ({}),
persist,
}, undefined, { isActive: true });
expect(
await adapter.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
}),
).toEqual({ ok: true, value: "GRANTED" });
expect(persist).toHaveBeenCalledOnce();
});
it("requires real transient user activation before invoking persist", async () => {
const persist = vi.fn(async () => true);
const adapter = createStorageDurabilityAdapter(
{ estimate: async () => ({}), persist },
undefined,
{ isActive: false },
);
await expect(
adapter.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "PERMISSION_DENIED" },
});
expect(persist).not.toHaveBeenCalled();
});
it("reports irreversible persist truth when abort wins after invocation", async () => {
const controller = new AbortController();
const adapter = createStorageDurabilityAdapter(
{
estimate: async () => ({}),
persist: async () => {
controller.abort();
return true;
},
},
undefined,
{ isActive: true },
);
await expect(
adapter.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
signal: controller.signal,
}),
).resolves.toEqual({ ok: true, value: "GRANTED" });
});
it("keeps unsupported persistence state distinct from false", async () => {
const adapter = createStorageDurabilityAdapter({
estimate: async () => ({ usage: 1, quota: 10 }),
});
await expect(adapter.inspect()).resolves.toMatchObject({
ok: true,
value: { persisted: null },
});
});
it("returns unsupported rather than inventing durable memory storage", async () => {
const adapter = createStorageDurabilityAdapter(undefined);
expect(await adapter.inspect()).toMatchObject({
ok: false,
error: {
code: "UNSUPPORTED",
recovery: "ONLINE_ONLY",
},
});
});
});