Initial Commit
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
import { SuaveplanError } from "@suaveplan/error";
|
||||
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
asDrive,
|
||||
FakeDrive,
|
||||
FakeDriveError,
|
||||
} from "../../tests/setup/fake-drive.js";
|
||||
import { DRIVE_URI_CODES, DriveUriError } from "../error-codes/error-codes.js";
|
||||
import {
|
||||
DriveStorageLikeAdapter,
|
||||
isDriveNotFound,
|
||||
} from "./drive-storage-like.js";
|
||||
|
||||
class CustomNotInDriveError extends SuaveplanError<string> {}
|
||||
|
||||
describe("isDriveNotFound", () => {
|
||||
it("matches any SuaveplanError whose code ends in .NOT_FOUND", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(
|
||||
isDriveNotFound(new CustomNotInDriveError("DRIVE.NOT_FOUND", "x"))
|
||||
).toBe(true);
|
||||
expect(
|
||||
isDriveNotFound(
|
||||
new CustomNotInDriveError("SOMETHING_ELSE.NOT_FOUND", "x")
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for non-SuaveplanError values", { timeout: 5000 }, () => {
|
||||
expect(isDriveNotFound(undefined)).toBe(false);
|
||||
expect(isDriveNotFound(null)).toBe(false);
|
||||
expect(isDriveNotFound("string")).toBe(false);
|
||||
expect(isDriveNotFound(new Error("plain"))).toBe(false);
|
||||
});
|
||||
|
||||
it("walks the cause chain", { timeout: 5000 }, () => {
|
||||
const inner = new FakeDriveError("DRIVE.NOT_FOUND", "missing");
|
||||
const outer = new Error("wrapped");
|
||||
(outer as Error & { cause?: unknown }).cause = inner;
|
||||
expect(isDriveNotFound(outer)).toBe(true);
|
||||
});
|
||||
|
||||
it("aborts on a self-referential cause cycle", { timeout: 5000 }, () => {
|
||||
const err = new Error("cyclic");
|
||||
(err as Error & { cause?: unknown }).cause = err;
|
||||
expect(isDriveNotFound(err)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when no cause member exists", { timeout: 5000 }, () => {
|
||||
expect(isDriveNotFound({ message: "no cause", code: "X" })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DriveStorageLikeAdapter", () => {
|
||||
let fake: FakeDrive;
|
||||
let adapter: DriveStorageLikeAdapter;
|
||||
|
||||
beforeEach(() => {
|
||||
fake = new FakeDrive({
|
||||
contents: new Map([["a.bin", new Uint8Array([1, 2, 3])]]),
|
||||
});
|
||||
adapter = new DriveStorageLikeAdapter(asDrive(fake));
|
||||
});
|
||||
|
||||
it("returns undefined for Drive not-found errors", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const result = await adapter.get("missing.bin");
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns the bytes on a hit", { timeout: 5000 }, async () => {
|
||||
const bytes = await adapter.get("a.bin");
|
||||
expect(bytes).toEqual(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it("re-throws non-not-found Drive errors unchanged", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const failure = new FakeDriveError(
|
||||
"DRIVE.PLUGIN_FAILED",
|
||||
"plugin crashed"
|
||||
);
|
||||
const bad = new FakeDrive({ readError: () => failure });
|
||||
const a = new DriveStorageLikeAdapter(asDrive(bad));
|
||||
await expect(a.get("x")).rejects.toBe(failure);
|
||||
});
|
||||
|
||||
it("forwards set, delete, exists, list", { timeout: 5000 }, async () => {
|
||||
await adapter.set("b.bin", new Uint8Array([9]));
|
||||
expect(fake.calls.write).toHaveLength(1);
|
||||
|
||||
expect(await adapter.exists("a.bin")).toBe(true);
|
||||
expect(await adapter.exists("missing")).toBe(false);
|
||||
|
||||
const removed = await adapter.delete("b.bin");
|
||||
expect(removed).toBe(true);
|
||||
|
||||
const listed = await adapter.list({ prefix: "a" });
|
||||
expect(listed.keys).toContain("a.bin");
|
||||
expect(listed.hasMore).toBe(false);
|
||||
expect(typeof listed.total).toBe("number");
|
||||
});
|
||||
|
||||
it("list with no options defaults prefix/limit to undefined", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const result = await adapter.list();
|
||||
expect(Array.isArray(result.keys)).toBe(true);
|
||||
});
|
||||
|
||||
it("list forwards options.limit to the drive", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const result = await adapter.list({ prefix: "a", limit: 10 });
|
||||
expect(Array.isArray(result.keys)).toBe(true);
|
||||
});
|
||||
|
||||
it("list copes with a drive returning entries-shape instead of keys-shape", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const odd = {
|
||||
async read(): Promise<Uint8Array> {
|
||||
return new Uint8Array();
|
||||
},
|
||||
async write(): Promise<void> {},
|
||||
async delete(): Promise<boolean> {
|
||||
return false;
|
||||
},
|
||||
async stat(): Promise<unknown | null> {
|
||||
return null;
|
||||
},
|
||||
async list(): Promise<unknown> {
|
||||
return {
|
||||
entries: [{ path: "p1" }, { path: "p2" }],
|
||||
};
|
||||
},
|
||||
createReadStream(): ReadableStream<Uint8Array> {
|
||||
return new ReadableStream();
|
||||
},
|
||||
};
|
||||
const a = new DriveStorageLikeAdapter(
|
||||
odd as unknown as Parameters<typeof DriveStorageLikeAdapter>[0]
|
||||
);
|
||||
const r = await a.list();
|
||||
expect(r.keys).toEqual(["p1", "p2"]);
|
||||
expect(r.total).toBe(2);
|
||||
});
|
||||
|
||||
it("list copes with a drive returning neither shape", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const oddler = {
|
||||
async read(): Promise<Uint8Array> {
|
||||
return new Uint8Array();
|
||||
},
|
||||
async write(): Promise<void> {},
|
||||
async delete(): Promise<boolean> {
|
||||
return false;
|
||||
},
|
||||
async stat(): Promise<unknown | null> {
|
||||
return null;
|
||||
},
|
||||
async list(): Promise<unknown> {
|
||||
return undefined;
|
||||
},
|
||||
createReadStream(): ReadableStream<Uint8Array> {
|
||||
return new ReadableStream();
|
||||
},
|
||||
};
|
||||
const a = new DriveStorageLikeAdapter(
|
||||
oddler as unknown as Parameters<typeof DriveStorageLikeAdapter>[0]
|
||||
);
|
||||
const r = await a.list();
|
||||
expect(r.keys).toEqual([]);
|
||||
expect(r.total).toBe(0);
|
||||
expect(r.hasMore).toBe(false);
|
||||
});
|
||||
|
||||
it("list forwards continuationToken when the drive returns one", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const cursored = {
|
||||
async read(): Promise<Uint8Array> {
|
||||
return new Uint8Array();
|
||||
},
|
||||
async write(): Promise<void> {},
|
||||
async delete(): Promise<boolean> {
|
||||
return false;
|
||||
},
|
||||
async stat(): Promise<unknown | null> {
|
||||
return null;
|
||||
},
|
||||
async list(): Promise<unknown> {
|
||||
return {
|
||||
keys: ["a", "b"],
|
||||
total: 99,
|
||||
hasMore: true,
|
||||
continuationToken: "next-page",
|
||||
};
|
||||
},
|
||||
createReadStream(): ReadableStream<Uint8Array> {
|
||||
return new ReadableStream();
|
||||
},
|
||||
};
|
||||
const a = new DriveStorageLikeAdapter(
|
||||
cursored as unknown as Parameters<typeof DriveStorageLikeAdapter>[0]
|
||||
);
|
||||
const r = await a.list();
|
||||
expect(r.continuationToken).toBe("next-page");
|
||||
expect(r.hasMore).toBe(true);
|
||||
expect(r.total).toBe(99);
|
||||
});
|
||||
|
||||
it("clear is unsupported and names the alternative", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
await expect(adapter.clear()).rejects.toBeInstanceOf(SuaveplanError);
|
||||
try {
|
||||
await adapter.clear();
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(DriveUriError);
|
||||
expect((err as DriveUriError).code).toBe(
|
||||
DRIVE_URI_CODES.WRITE_UNSUPPORTED
|
||||
);
|
||||
expect((err as DriveUriError).message).toContain(
|
||||
"Drive.delete(path)"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("createReadStream forwards to Drive.createReadStream", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const stream = adapter.createReadStream("a.bin");
|
||||
expect(fake.calls.createReadStream).toHaveLength(1);
|
||||
// Drain it
|
||||
const reader = stream.getReader();
|
||||
const { value } = await reader.read();
|
||||
expect(value).toEqual(new Uint8Array([1, 2, 3]));
|
||||
await reader.read(); // close
|
||||
reader.releaseLock();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user