补齐游戏分发审核 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
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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user