205 lines
5.8 KiB
TypeScript
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",
|
|
},
|
|
});
|
|
});
|
|
});
|