补齐游戏分发审核 CAS 与下架可见性取证
Project CI / AI game creator shell Rust crates (push) Successful in 1m19s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m39s
Project CI / Backend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled

- 媒体链路脚本新增 publicationRevision CAS 段:过期审核与过期下架 409 PUBLICATION_CONFLICT、同 key 重放批准 replayed=true、并发激活只有一次成功、下架前的 revision 不能批准新版本
- 新增作者下架后的可见性段:公开目录不含、详情 404、发行网关 404,且目录与详情不返回对象存储地址
- 游戏分发里程碑阶段 B 第 3、4 条改为已勾选,脚本共 58 项 PASS
This commit is contained in:
kdletters
2026-09-28 19:04:23 +08:00
parent 253f99d921
commit 6cbc56dd98
2 changed files with 289 additions and 6 deletions
@@ -787,6 +787,288 @@ async function main() {
`status=${adminVersionAfterReject?.status ?? 'missing'} reviewedAt=${adminVersionAfterReject?.reviewedAt ?? ''}`,
);
// 12. publicationRevision CAS 与下架后的可见性
const revisionOfPublicGame = async () =>
(await api(`/api/game-distribution/games/${gameId}`)).data
?.publicationRevision;
const adminRevisionOfGame = async () => {
const list = await api('/admin/api/game-distribution/games', {
token: admin,
});
return (list.data?.games ?? []).find((game) => game.gameId === gameId)
?.publicationRevision;
};
const staleApprove = await api(
`/admin/api/game-distribution/versions/${versionId}/review`,
{
method: 'POST',
token: admin,
headers: { 'Idempotency-Key': `e2e-stale-approve-${id}` },
body: { decision: 'approve', expectedPublicationRevision: 0 },
},
);
check(
'过期 revision 的审核被 CAS 拒绝',
staleApprove.status === 409 &&
staleApprove.text.includes('PUBLICATION_CONFLICT'),
`status=${staleApprove.status} body=${staleApprove.text.slice(0, 160)}`,
);
const replayedApprove = await api(
`/admin/api/game-distribution/versions/${versionId}/review`,
{
method: 'POST',
token: admin,
headers: { 'Idempotency-Key': `e2e-approve-${id}` },
body: {
decision: 'approve',
expectedPublicationRevision: readback.data.version.publicationRevision,
},
},
);
check(
'同 key 重放批准返回 replayed=true 且不产生第二次激活',
replayedApprove.status === 200 && replayedApprove.data?.replayed === true,
`status=${replayedApprove.status} replayed=${replayedApprove.data?.replayed}`,
);
const builtV3 = await buildZipWithMarker('E2E-V3-OK');
const sha256V3 = crypto
.createHash('sha256')
.update(builtV3.bytes)
.digest('hex');
const versionV3 = await api(
`/api/game-distribution/games/${gameId}/versions`,
{
method: 'POST',
token: author,
headers: { 'Idempotency-Key': `e2e-v3-version-${id}` },
body: {
packageSha256: sha256V3,
packageBytes: builtV3.bytes.length,
packageFileCount: builtV3.fileCount,
packageEntryPath: 'index.html',
gameMetadata: metadata,
},
},
);
const versionIdV3 = versionV3.data?.versionId;
const uploadV3 = await api(
`/api/game-distribution/versions/${versionIdV3}/package`,
{
method: 'PUT',
token: author,
headers: {
'Idempotency-Key': `e2e-v3-upload-${id}`,
'Content-Type': 'application/zip',
},
binary: builtV3.bytes,
},
);
const revisionBeforeActivation = await revisionOfPublicGame();
const submitV3 = await api(
`/api/game-distribution/versions/${versionIdV3}/submit`,
{
method: 'POST',
token: author,
headers: { 'Idempotency-Key': `e2e-v3-submit-${id}` },
body: { expectedPublicationRevision: revisionBeforeActivation },
},
);
check(
'在线游戏的第三个版本上传并送审成功',
uploadV3.status === 200 && submitV3.status === 202,
`upload=${uploadV3.status} submit=${submitV3.status}`,
);
const activationAttempts = await Promise.all([
api(`/admin/api/game-distribution/versions/${versionIdV3}/review`, {
method: 'POST',
token: admin,
headers: { 'Idempotency-Key': `e2e-v3-approve-a-${id}` },
body: {
decision: 'approve',
expectedPublicationRevision: revisionBeforeActivation,
},
}),
api(`/admin/api/game-distribution/versions/${versionIdV3}/review`, {
method: 'POST',
token: admin,
headers: { 'Idempotency-Key': `e2e-v3-approve-b-${id}` },
body: {
decision: 'approve',
expectedPublicationRevision: revisionBeforeActivation,
},
}),
]);
const activationWins = activationAttempts.filter(
(item) => item.status === 200,
);
const activationConflicts = activationAttempts.filter(
(item) => item.status === 409 && item.text.includes('PUBLICATION_CONFLICT'),
);
check(
'并发激活只有一次成功、另一次被 CAS 拒绝',
activationWins.length === 1 && activationConflicts.length === 1,
activationAttempts
.map(
(item) =>
`${item.status}${item.error?.code ? `/${item.error.code}` : ''}`,
)
.join(' '),
);
const detailAfterActivation = await api(
`/api/game-distribution/games/${gameId}`,
);
check(
'并发激活后公开详情指向新版本',
detailAfterActivation.data?.currentVersion?.sha256 === sha256V3,
`sha=${String(detailAfterActivation.data?.currentVersion?.sha256).slice(0, 12)} v3=${sha256V3.slice(0, 12)}`,
);
const revisionBeforeUnpublish =
detailAfterActivation.data?.publicationRevision ?? 0;
const unpublished = await api(
`/api/game-distribution/games/${gameId}/unpublish`,
{
method: 'POST',
token: author,
headers: { 'Idempotency-Key': `e2e-unpublish-${id}` },
body: { expectedPublicationRevision: revisionBeforeUnpublish },
},
);
check(
'作者下架成功',
unpublished.status === 200 &&
unpublished.data?.game?.status === 'unpublished',
`status=${unpublished.status} gameStatus=${unpublished.data?.game?.status ?? ''}`,
);
const catalogAfterUnpublish = await api('/api/game-distribution/games');
check(
'下架后公开目录不含该游戏',
!(catalogAfterUnpublish.data?.games ?? []).some(
(game) => game.id === gameId,
),
);
const detailAfterUnpublish = await api(
`/api/game-distribution/games/${gameId}`,
);
check(
'下架后公开详情不可见',
detailAfterUnpublish.status === 404,
`status=${detailAfterUnpublish.status}`,
);
const releaseAfterUnpublish = await fetch(
`${API}/api/game-distribution/releases/${gameId}/index.html`,
);
check(
'下架后发行网关不再服务该游戏',
releaseAfterUnpublish.status === 404,
`status=${releaseAfterUnpublish.status}`,
);
check(
'下架后的目录与详情不返回对象存储地址',
!/aliyuncs\.com|oss-cn|Signature=/iu.test(
catalogAfterUnpublish.text + detailAfterUnpublish.text,
),
);
const staleUnpublish = await api(
`/api/game-distribution/games/${gameId}/unpublish`,
{
method: 'POST',
token: author,
headers: { 'Idempotency-Key': `e2e-stale-unpublish-${id}` },
body: {
expectedPublicationRevision: revisionBeforeUnpublish - 1,
},
},
);
check(
'过期 revision 的下架被 CAS 拒绝',
staleUnpublish.status === 409 &&
staleUnpublish.text.includes('PUBLICATION_CONFLICT'),
`status=${staleUnpublish.status} body=${staleUnpublish.text.slice(0, 160)}`,
);
const builtV4 = await buildZipWithMarker('E2E-V4-OK');
const sha256V4 = crypto
.createHash('sha256')
.update(builtV4.bytes)
.digest('hex');
const versionV4 = await api(
`/api/game-distribution/games/${gameId}/versions`,
{
method: 'POST',
token: author,
headers: { 'Idempotency-Key': `e2e-v4-version-${id}` },
body: {
packageSha256: sha256V4,
packageBytes: builtV4.bytes.length,
packageFileCount: builtV4.fileCount,
packageEntryPath: 'index.html',
gameMetadata: metadata,
},
},
);
const versionIdV4 = versionV4.data?.versionId;
const uploadV4 = await api(
`/api/game-distribution/versions/${versionIdV4}/package`,
{
method: 'PUT',
token: author,
headers: {
'Idempotency-Key': `e2e-v4-upload-${id}`,
'Content-Type': 'application/zip',
},
binary: builtV4.bytes,
},
);
const revisionAfterUnpublish = await adminRevisionOfGame();
const submitV4 = await api(
`/api/game-distribution/versions/${versionIdV4}/submit`,
{
method: 'POST',
token: author,
headers: { 'Idempotency-Key': `e2e-v4-submit-${id}` },
body: { expectedPublicationRevision: revisionAfterUnpublish },
},
);
check(
'下架后仍可准备新的待审版本',
uploadV4.status === 200 && submitV4.status === 202,
`upload=${uploadV4.status} submit=${submitV4.status}`,
);
const reviveAttempt = await api(
`/admin/api/game-distribution/versions/${versionIdV4}/review`,
{
method: 'POST',
token: admin,
headers: { 'Idempotency-Key': `e2e-revive-${id}` },
body: {
decision: 'approve',
expectedPublicationRevision: revisionBeforeUnpublish,
},
},
);
check(
'下架前的 revision 不能批准新版本(不会复活已下架游戏)',
reviveAttempt.status === 409 &&
reviveAttempt.text.includes('PUBLICATION_CONFLICT'),
`status=${reviveAttempt.status} body=${reviveAttempt.text.slice(0, 160)}`,
);
const catalogAfterReviveAttempt = await api('/api/game-distribution/games');
check(
'陈旧审核之后游戏仍未公开',
!(catalogAfterReviveAttempt.data?.games ?? []).some(
(game) => game.id === gameId,
),
);
console.log(`\n结果:${failures === 0 ? '全部通过' : `${failures} 项失败`}`);
process.exitCode = failures === 0 ? 0 : 1;
}