88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
/**
|
|
* Garage-backed integration test for `S3.setPartial`.
|
|
*
|
|
* Pins STORAGE-01..03 (set-partial semantics) against a real S3 wire so the
|
|
* read-modify-write fallback survives AWS-SDK body encoding, content-length,
|
|
* and range-read round-trips that an in-memory FakeS3Client cannot exercise.
|
|
*
|
|
* Skips the suite when the GarageS3Fixture failed to start (no Docker /
|
|
* env-bridge incomplete). Set `GARAGE_S3_ENDPOINT`,
|
|
* `GARAGE_S3_ADMIN_ENDPOINT`, `GARAGE_S3_ACCESS_KEY_ID`,
|
|
* `GARAGE_S3_SECRET_ACCESS_KEY` to bypass container startup and use an
|
|
* existing Garage instance.
|
|
*/
|
|
import {
|
|
afterEach,
|
|
beforeEach,
|
|
describe,
|
|
expect,
|
|
it,
|
|
inject as vitestInject,
|
|
} from "@suaveplan/testing/runner";
|
|
import { S3 } from "../../src/s3/s3.js";
|
|
import { inject } from "./setup.js";
|
|
|
|
const failed =
|
|
(vitestInject as (k: string) => string[] | null | undefined)(
|
|
"__integration_failed__"
|
|
) ?? [];
|
|
const garageUnavailable = failed.includes("garage-s3");
|
|
|
|
describe.skipIf(garageUnavailable)("S3.setPartial — Garage backed", () => {
|
|
let storage: S3;
|
|
let key: string;
|
|
|
|
beforeEach(() => {
|
|
const cfg = inject("garage-s3");
|
|
storage = new S3({
|
|
bucket: cfg.bucket,
|
|
region: cfg.region,
|
|
endpoint: cfg.endpoint,
|
|
accessKeyId: cfg.accessKeyId,
|
|
secretAccessKey: cfg.secretAccessKey,
|
|
});
|
|
key = `set-partial/${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await storage.delete(key).catch(() => undefined);
|
|
});
|
|
|
|
it("creates a sparse zero-filled object when key does not exist", {
|
|
timeout: 60_000,
|
|
}, async () => {
|
|
await storage.setPartial(key, 4, new Uint8Array([0xaa, 0xbb]));
|
|
const got = await storage.get(key);
|
|
expect(got).toEqual(
|
|
new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb])
|
|
);
|
|
});
|
|
|
|
it("extends an existing shorter object with zero gap", {
|
|
timeout: 60_000,
|
|
}, async () => {
|
|
await storage.set(key, new Uint8Array([0x01, 0x02]));
|
|
await storage.setPartial(key, 5, new Uint8Array([0xcc]));
|
|
const got = await storage.get(key);
|
|
expect(got).toEqual(
|
|
new Uint8Array([0x01, 0x02, 0x00, 0x00, 0x00, 0xcc])
|
|
);
|
|
});
|
|
|
|
it("overwrites overlap and preserves surrounding bytes", {
|
|
timeout: 60_000,
|
|
}, async () => {
|
|
await storage.set(key, new Uint8Array([1, 2, 3, 4, 5, 6]));
|
|
await storage.setPartial(key, 2, new Uint8Array([0xff, 0xff]));
|
|
const got = await storage.get(key);
|
|
expect(got).toEqual(new Uint8Array([1, 2, 0xff, 0xff, 5, 6]));
|
|
});
|
|
|
|
it("zero-length writes are a no-op", { timeout: 60_000 }, async () => {
|
|
await storage.set(key, new Uint8Array([0x01, 0x02, 0x03]));
|
|
await storage.setPartial(key, 1, new Uint8Array(0));
|
|
const got = await storage.get(key);
|
|
expect(got).toEqual(new Uint8Array([0x01, 0x02, 0x03]));
|
|
});
|
|
});
|