补齐游戏分发安全下架取证并记录发行包对象可匿名直取
Project CI / AI game creator shell Rust crates (push) Successful in 1m32s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m6s
Project CI / Backend tests (push) Successful in 4m55s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m34s
Project CI / Frontend tests (push) Successful in 2m19s
Project CI / Native shell tests (push) Successful in 7m14s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m55s
Project CI / AI game creator shell web tests (push) Successful in 1m57s
Project CI / Repository checks (push) Successful in 2m28s
Project CI / AI game creator shell Rust crates (push) Successful in 1m32s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m6s
Project CI / Backend tests (push) Successful in 4m55s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m34s
Project CI / Frontend tests (push) Successful in 2m19s
Project CI / Native shell tests (push) Successful in 7m14s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m55s
Project CI / AI game creator shell web tests (push) Successful in 1m57s
Project CI / Repository checks (push) Successful in 2m28s
- 媒体链路脚本新增管理员安全下架与恢复段:suspend 后目录/详情/发行读取全部关闭、过期 revision 的 suspend 被 CAS 拒绝、restore 后重新公开且可玩 - 新增匿名直取发行包对象探测:本地 dev bucket 返回 200(同 bucket 不存在 key 为 404),默认只打 WARN,E2E_REQUIRE_PRIVATE_BUCKET=1 时按失败处理 - 游戏分发里程碑阶段 B 第 7 条补上「不能绕过网关直取 OSS 在当前环境不成立」的实测、根因与剩余缺口 - pitfalls 记录 platform-oss 的 put_internal_object_bytes 从不发送 x-oss-object-acl 的问题与上线前处理
This commit is contained in:
@@ -1069,6 +1069,141 @@ async function main() {
|
||||
),
|
||||
);
|
||||
|
||||
// 13. 管理员安全下架 / 恢复,以及匿名直取发行包对象
|
||||
const revisionForV4 = await adminRevisionOfGame();
|
||||
const approveV4 = await api(
|
||||
`/admin/api/game-distribution/versions/${versionIdV4}/review`,
|
||||
{
|
||||
method: 'POST',
|
||||
token: admin,
|
||||
headers: { 'Idempotency-Key': `e2e-v4-approve-${id}` },
|
||||
body: {
|
||||
decision: 'approve',
|
||||
expectedPublicationRevision: revisionForV4,
|
||||
},
|
||||
},
|
||||
);
|
||||
const detailAfterV4 = await api(`/api/game-distribution/games/${gameId}`);
|
||||
check(
|
||||
'用当前 revision 批准 v4 后新版本上线',
|
||||
approveV4.status === 200 &&
|
||||
detailAfterV4.data?.currentVersion?.sha256 === sha256V4,
|
||||
`status=${approveV4.status} sha=${String(detailAfterV4.data?.currentVersion?.sha256).slice(0, 12)}`,
|
||||
);
|
||||
|
||||
const revisionBeforeSuspend = detailAfterV4.data?.publicationRevision ?? 0;
|
||||
const suspended = await api(
|
||||
`/admin/api/game-distribution/games/${gameId}/suspend`,
|
||||
{
|
||||
method: 'POST',
|
||||
token: admin,
|
||||
headers: { 'Idempotency-Key': `e2e-suspend-${id}` },
|
||||
body: {
|
||||
expectedPublicationRevision: revisionBeforeSuspend,
|
||||
reason: 'E2E 安全下架',
|
||||
},
|
||||
},
|
||||
);
|
||||
check(
|
||||
'管理员安全下架成功',
|
||||
suspended.status === 200 && suspended.data?.game?.status !== 'published',
|
||||
`status=${suspended.status} gameStatus=${suspended.data?.game?.status ?? ''}`,
|
||||
);
|
||||
|
||||
const catalogAfterSuspend = await api('/api/game-distribution/games');
|
||||
const detailAfterSuspend = await api(
|
||||
`/api/game-distribution/games/${gameId}`,
|
||||
);
|
||||
const releaseAfterSuspend = await fetch(
|
||||
`${API}/api/game-distribution/releases/${gameId}/index.html`,
|
||||
);
|
||||
check(
|
||||
'安全下架后目录 / 详情 / 发行读取全部关闭',
|
||||
!(catalogAfterSuspend.data?.games ?? []).some(
|
||||
(game) => game.id === gameId,
|
||||
) &&
|
||||
detailAfterSuspend.status === 404 &&
|
||||
releaseAfterSuspend.status === 404,
|
||||
`detail=${detailAfterSuspend.status} gateway=${releaseAfterSuspend.status}`,
|
||||
);
|
||||
|
||||
const staleSuspend = await api(
|
||||
`/admin/api/game-distribution/games/${gameId}/suspend`,
|
||||
{
|
||||
method: 'POST',
|
||||
token: admin,
|
||||
headers: { 'Idempotency-Key': `e2e-stale-suspend-${id}` },
|
||||
body: {
|
||||
expectedPublicationRevision: revisionBeforeSuspend - 1,
|
||||
reason: 'E2E 过期安全下架',
|
||||
},
|
||||
},
|
||||
);
|
||||
check(
|
||||
'过期 revision 的安全下架被 CAS 拒绝',
|
||||
staleSuspend.status === 409 &&
|
||||
staleSuspend.text.includes('PUBLICATION_CONFLICT'),
|
||||
`status=${staleSuspend.status} body=${staleSuspend.text.slice(0, 160)}`,
|
||||
);
|
||||
|
||||
const restoreRevision = await adminRevisionOfGame();
|
||||
const restored = await api(
|
||||
`/admin/api/game-distribution/games/${gameId}/restore`,
|
||||
{
|
||||
method: 'POST',
|
||||
token: admin,
|
||||
headers: { 'Idempotency-Key': `e2e-restore-${id}` },
|
||||
body: { expectedPublicationRevision: restoreRevision },
|
||||
},
|
||||
);
|
||||
const catalogAfterRestore = await api('/api/game-distribution/games');
|
||||
const releaseAfterRestore = await fetch(
|
||||
`${API}/api/game-distribution/releases/${gameId}/index.html`,
|
||||
);
|
||||
check(
|
||||
'管理员恢复后游戏重新公开且可玩',
|
||||
restored.status === 200 &&
|
||||
(catalogAfterRestore.data?.games ?? []).some(
|
||||
(game) => game.id === gameId,
|
||||
) &&
|
||||
releaseAfterRestore.status === 200,
|
||||
`status=${restored.status} gateway=${releaseAfterRestore.status}`,
|
||||
);
|
||||
|
||||
// 发行包落在 api-server 的快照 bucket(默认 agc-dev,见 config.rs 的默认值),
|
||||
// 公开读取必须走网关;这里直接用对象键构造匿名请求,验证私有 bucket 不给直取。
|
||||
const ossBucket = (
|
||||
process.env.GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET ?? 'agc-dev'
|
||||
).trim();
|
||||
const ossEndpoint = (
|
||||
process.env.GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ENDPOINT ??
|
||||
'oss-rg-china-mainland.aliyuncs.com'
|
||||
).trim();
|
||||
const objectKey = `agc/project-snapshots/v1/game-distribution/${gameId}/${versionIdV4}.zip`;
|
||||
const directObject = await fetch(
|
||||
`https://${ossBucket}.${ossEndpoint}/${objectKey}`,
|
||||
);
|
||||
// 这一条验的是「环境里的 bucket/对象 ACL」,不是接口行为:本地 dev bucket 允许匿名读,
|
||||
// 所以默认只报 WARN;上线前把 E2E_REQUIRE_PRIVATE_BUCKET=1 打开,让它在非私有环境里失败。
|
||||
if (directObject.status >= 400) {
|
||||
check(
|
||||
'匿名直取私有 bucket 里的发行包对象被拒绝',
|
||||
true,
|
||||
`status=${directObject.status} bucket=${ossBucket}`,
|
||||
);
|
||||
} else if ((process.env.E2E_REQUIRE_PRIVATE_BUCKET ?? '').trim() === '1') {
|
||||
check(
|
||||
'匿名直取私有 bucket 里的发行包对象被拒绝',
|
||||
false,
|
||||
`status=${directObject.status} bucket=${ossBucket}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`WARN 匿名直取发行包对象返回 status=${directObject.status}(bucket=${ossBucket}):` +
|
||||
'本地 dev bucket 允许匿名读,生产上线前必须确认 bucket 与对象 ACL 都是私有(可用 E2E_REQUIRE_PRIVATE_BUCKET=1 复验)。',
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`\n结果:${failures === 0 ? '全部通过' : `${failures} 项失败`}`);
|
||||
process.exitCode = failures === 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user