Files
Genarrative/scripts/game-distribution-e2e-helpers.mjs
T
k88936 1f239a96ce 游戏分发 E2E 抽出共享 multipart 发布 helper
- 新增 scripts/game-distribution-e2e-helpers.mjs:COVER_PNG、imagePart、coverImagePart、publishFormData、resolveApiRequest
- 11 个 E2E 脚本删除各自的重复实现,改为 import;api() 的 body 分支收敛到 resolveApiRequest
- 各脚本的封面文件名前缀、envelope 与鉴权头合并顺序、返回形状保持不变
2026-10-07 11:42:45 +08:00

96 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 游戏分发 E2E 脚本共用的 multipart 发布 helper。
//
// 这些脚本都直连 api-server,发布口是同一套契约:`metadata` JSON 文本 part + 可选 `cover`
// 二进制 part + 按顺序重复的 `screenshot` 二进制 part;各自的 `api()` 也只在 body 形态上分支。
// 这里收敛这段稳定契约,脚本只保留自己的路径、鉴权头与返回形状差异。
/** 1x1 透明 PNG:仅用于满足发布必填的封面/截图 part,内容不参与断言。 */
export const COVER_PNG = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
'base64',
);
/**
* 发布用图片 part。
*
* 默认文件名 `<kind>-<id>.png`、`image/png`;脚本可通过 `options.fileName` 保留自己的前缀,
* 或用 `options.bytes` / `options.contentType` 覆盖字节与类型。
*/
export function imagePart(kind, id, options = {}) {
const {
bytes = COVER_PNG,
fileName = `${kind}-${id}.png`,
contentType = 'image/png',
} = options;
return { bytes, fileName, contentType };
}
/**
* 封面 part 便捷封装:等价于 `imagePart('cover', id, options)`。
*
* 脚本可用 `options.fileName` 给出完整文件名,或用 `options.namePrefix` 生成
* `<prefix>-<id>.png`。
*/
export function coverImagePart(id, options = {}) {
const { namePrefix, fileName, ...rest } = options;
const resolvedFileName =
fileName ?? (namePrefix ? `${namePrefix}-${id}.png` : `cover-${id}.png`);
return imagePart('cover', id, { ...rest, fileName: resolvedFileName });
}
/**
* 组装发布请求体:`metadata` 文本 part + 可选 `cover` / 按顺序重复的 `screenshot` 二进制 part。
*
* `metadata.screenshots` 里的 `null` 槽位按出现顺序消费 `screenshots` 数组里的 part;string
* 槽位沿用该 objectKey。调用方负责保证 null 槽位数量与 part 数量一致(不一致服务端会 400)。
*/
export function publishFormData(metadata, { cover, screenshots = [] } = {}) {
const form = new FormData();
form.append('metadata', JSON.stringify(metadata));
if (cover) {
form.append(
'cover',
new Blob([cover.bytes], { type: cover.contentType }),
cover.fileName,
);
}
for (const screenshot of screenshots) {
if (!screenshot) continue;
form.append(
'screenshot',
new Blob([screenshot.bytes], { type: screenshot.contentType }),
screenshot.fileName,
);
}
return form;
}
/**
* 推导 `api()` 的最终请求头与请求体。
*
* - `formData`:multipart/form-data,不手动设 `Content-Type`,交给 fetch 生成 boundary。
* - `binary`:直接作为 body(例如分片 PUT 的字节)。
* - `body !== undefined`:按 JSON 序列化,并补 `Content-Type: application/json`。
*
* 调用方先自行合并 `{ ...ENVELOPE, ...headers }` 与 `Authorization`,再把结果作为 `headers`
* 传进来;返回值里的 `headers` 是补完 `Content-Type` 后的副本。
*/
export function resolveApiRequest({
formData,
binary,
body,
headers = {},
} = {}) {
const finalHeaders = { ...headers };
let finalBody;
if (formData) {
finalBody = formData;
} else if (binary) {
finalBody = binary;
} else if (body !== undefined) {
finalHeaders['Content-Type'] = 'application/json';
finalBody = JSON.stringify(body);
}
return { headers: finalHeaders, body: finalBody };
}