test(deploy): exercise external module packages #90
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
EMPTY_MODULE_STORAGE,
|
||||
isModuleStorageError,
|
||||
isValidModuleStorageKey,
|
||||
MODULE_STORAGE_KEY_MAX_BYTES,
|
||||
MODULE_STORAGE_READ_URL_DEFAULT_SECONDS,
|
||||
@@ -322,6 +323,17 @@ describe("module storage scoping", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("recognizes a storage error from an installed module's separate package copy", () => {
|
||||
const external = Object.assign(new Error("The storage service could not be reached."), {
|
||||
code: "STORAGE_PROVIDER_UNAVAILABLE",
|
||||
name: "ModuleStorageError",
|
||||
});
|
||||
|
||||
expect(external).not.toBeInstanceOf(ModuleStorageError);
|
||||
expect(isModuleStorageError(external)).toBe(true);
|
||||
expect(isModuleStorageError(Object.assign(new Error("no"), { code: "UNKNOWN" }))).toBe(false);
|
||||
});
|
||||
|
||||
it("gives a module no way to reach another module's storage by knowing its name", async () => {
|
||||
const shared = fakeProvider();
|
||||
const announcements = createModuleStorage({
|
||||
|
||||
@@ -87,6 +87,28 @@ export class ModuleStorageError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `value` is a module-storage error, judged by its stable public shape rather than its class.
|
||||
*
|
||||
* An installed module bundles its own copy of the contracts package (ADR 0049), so an error created
|
||||
* by the host's storage provider does not share class identity with the module's
|
||||
* `ModuleStorageError`. `instanceof ModuleStorageError` is therefore not a valid check across the
|
||||
* module boundary.
|
||||
*/
|
||||
export function isModuleStorageError(
|
||||
value: unknown,
|
||||
): value is Pick<ModuleStorageError, "code" | "key" | "message" | "name"> {
|
||||
if (!(value instanceof Error) || value.name !== "ModuleStorageError") {
|
||||
return false;
|
||||
}
|
||||
const { code, key } = value as { code?: unknown; key?: unknown };
|
||||
return (
|
||||
typeof code === "string" &&
|
||||
(MODULE_STORAGE_ERROR_CODES as readonly string[]).includes(code) &&
|
||||
(key === undefined || typeof key === "string")
|
||||
);
|
||||
}
|
||||
|
||||
/** The S3 object-key ceiling, which every S3-compatible provider shares. Measured in UTF-8 bytes,
|
||||
* not characters, because that is what the wire format bounds. */
|
||||
export const MODULE_STORAGE_KEY_MAX_BYTES = 1024;
|
||||
|
||||
@@ -37,6 +37,7 @@ export {
|
||||
isDevicePrincipal,
|
||||
isModuleHttpMethod,
|
||||
isModuleRouteBodyMode,
|
||||
isModuleStorageError,
|
||||
isValidModuleStorageKey,
|
||||
isValidModuleStorageReadUrlFilename,
|
||||
MODULE_HTTP_BODY_MODES,
|
||||
|
||||
@@ -426,7 +426,9 @@ async function seedSignage(owner) {
|
||||
const base = "/api/v1/modules/signage";
|
||||
const images = [];
|
||||
for (const [index, colour] of ["#b03030", "#3060b0", "#30a050"].entries()) {
|
||||
const uploaded = await owner.evaluate(
|
||||
let uploaded;
|
||||
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
||||
uploaded = await owner.evaluate(
|
||||
`(async () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 640;
|
||||
@@ -445,11 +447,26 @@ async function seedSignage(owner) {
|
||||
})()`,
|
||||
`uploading slide ${index}`,
|
||||
);
|
||||
if (
|
||||
attempt === 1 &&
|
||||
uploaded.status === 503 &&
|
||||
uploaded.body?.code === "SIGNAGE_STORAGE_UNAVAILABLE"
|
||||
) {
|
||||
note(`slide ${index} met one transient storage outage; retrying once`);
|
||||
await sleep(1_000);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (uploaded.status === "timeout") {
|
||||
throw new HangError(`HTTP POST ${base}/images from page "owner"`, requestTimeoutMs);
|
||||
}
|
||||
if (uploaded.status !== 200 && uploaded.status !== 201) {
|
||||
throw new Error(`Uploading slide ${index} returned HTTP ${uploaded.status}.`);
|
||||
const code =
|
||||
typeof uploaded.body?.code === "string" && /^SIGNAGE_[A-Z_]+$/u.test(uploaded.body.code)
|
||||
? ` (${uploaded.body.code})`
|
||||
: "";
|
||||
throw new Error(`Uploading slide ${index} returned HTTP ${uploaded.status}${code}.`);
|
||||
}
|
||||
images.push(uploaded.body?.image?.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user