Compare commits

...

1 Commits

Author SHA1 Message Date
kdletters 95a3b9f593 后台灰度预设补上游戏发布开关
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m14s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m45s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m46s
Project CI / Backend tests (pull_request) Successful in 6m23s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m36s
Project CI / Native shell tests (pull_request) Successful in 7m8s
Project CI / Frontend tests (pull_request) Successful in 3m0s
Project CI / Repository checks (pull_request) Successful in 3m49s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m11s
- admin-web:灰度发布配置的固定目标新增「游戏分发 → 游戏发布」(game-distribution:publish),前缀标签与描述说明「未配置或关闭时默认开放,开启后只放行白名单/灰度命中」。
- 默认语义:选中预设后保持 enabled=false,避免误把发布收紧成全关;需要灰度时再显式打开并填白名单/比例。
- 用例:新增「可选择游戏发布开关且默认不收紧」与「保存写入白名单与比例」两条后台用例。
- 文档:开发运维文档说明该键配置前不会出现在已有列表、必须从预设或手填新建一行。
2026-09-22 20:06:37 +08:00
3 changed files with 90 additions and 1 deletions
@@ -175,6 +175,86 @@ test('灰度发布页可选择模板库并默认启用零比例灰度', async ()
);
});
test('灰度发布页可选择游戏发布开关,默认保持「未开启即开放」语义', async () => {
const user = userEvent.setup();
render(
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
);
await screen.findByRole('button', { name: 'editor.new-toolbar' });
await user.selectOptions(screen.getByLabelText('Gate Key 前缀'), [
'game-distribution',
]);
expect((screen.getByLabelText('Gate Key') as HTMLInputElement).value).toBe(
'game-distribution:publish',
);
expect(
(screen.getByLabelText('Gate Key 目标') as HTMLSelectElement).value,
).toBe('publish');
// 该开关的语义是「未配置/关闭 = 默认开放」,所以选中后不能默认打开收紧。
expect((screen.getByLabelText('启用') as HTMLInputElement).checked).toBe(
false,
);
expect((screen.getByLabelText('灰度比例') as HTMLInputElement).value).toBe(
'0',
);
expect(
(screen.getByLabelText('描述') as HTMLTextAreaElement).value,
).toContain('游戏发布入口灰度');
});
test('灰度发布页保存游戏发布开关时写入白名单与比例', async () => {
const user = userEvent.setup();
vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValueOnce({
gates: [
...configResponse.gates,
{
gateKey: 'game-distribution:publish',
enabled: true,
rolloutPercent: 20,
allowUserIds: ['user-internal'],
allowUserTags: [],
denyUserIds: [],
description: '游戏发布入口灰度',
updatedAt: '2026-09-22T10:00:00Z',
},
],
});
render(
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
);
await screen.findByRole('button', { name: 'editor.new-toolbar' });
await user.selectOptions(screen.getByLabelText('Gate Key 前缀'), [
'game-distribution',
]);
fireEvent.click(screen.getByLabelText('启用'));
fireEvent.change(screen.getByLabelText('灰度比例'), {
target: { value: '20' },
});
fireEvent.change(screen.getByLabelText('允许用户 ID'), {
target: { value: 'user-internal' },
});
fireEvent.change(screen.getByLabelText('描述'), {
target: { value: '游戏发布入口灰度' },
});
await user.click(screen.getByRole('button', { name: '保存配置' }));
await user.click(screen.getByRole('button', { name: '确认' }));
await waitFor(() =>
expect(upsertAdminFeatureGateConfig).toHaveBeenCalledWith('admin-token', {
gateKey: 'game-distribution:publish',
enabled: true,
rolloutPercent: 20,
allowUserIds: ['user-internal'],
allowUserTags: [],
denyUserIds: [],
description: '游戏发布入口灰度',
}),
);
});
test('灰度发布页保存时转换数组和百分比', async () => {
const user = userEvent.setup();
vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValueOnce({
@@ -28,6 +28,7 @@ interface GateTargetOption {
const GATE_PREFIX_LABELS: Record<string, string> = {
'image-editor': '画布',
agc: '客户端',
'game-distribution': '游戏分发',
};
const FIXED_GATE_TARGETS: GateTargetOption[] = [
@@ -45,6 +46,14 @@ const FIXED_GATE_TARGETS: GateTargetOption[] = [
label: 'Agent 侧边栏',
description: '画布 Agent 入口灰度',
},
{
prefix: 'game-distribution',
suffix: 'publish',
key: 'game-distribution:publish',
label: '游戏发布',
description:
'游戏发布入口灰度:未配置或关闭时对已登录作者默认开放,开启后只放行白名单 / 灰度命中',
},
];
export function AdminGrayReleaseConfigPage({
@@ -678,7 +678,7 @@ journalctl -u genarrative-api -o cat | grep 'operation="release_rejected"'
发布事故或回滚窗口里用 `game-distribution:publish` 灰度开关控制写入,不需要改代码或重启:
- 开关位置:后台「灰度发布配置」(`GET/PUT /admin/api/feature-gates`),`gateKey = game-distribution:publish`
- 开关位置:后台「灰度发布配置」(`GET/PUT /admin/api/feature-gates`),`gateKey = game-distribution:publish`;后台预设「游戏分发 → 游戏发布」,选中后默认保持 `enabled=false`(即默认开放),需要收紧时再显式打开并填白名单 / 灰度比例。该键在配置前不会出现在已有开关列表里,必须从预设或手填 Gate Key 新建一行
- 语义:没有该 gate 行或 `enabled=false` 表示**默认开放**`enabled=true` 时只有 `allowUserIds` / `allowUserTags` / `rolloutPercent` 命中的作者能发布,`rolloutPercent=0` 且无白名单即**全部关闭**(等价紧急关闭投稿)。
- 关闭范围:创建游戏、创建版本、上传包、送审、撤回、作者下架,以及管理员**批准**(新版本激活)都返回 `503 GAME_DISTRIBUTION_PUBLISH_DISABLED`
- 始终可用:目录、详情、版本回读、发行网关(已公开游戏继续游玩)、`/my-games`、审核队列读取、**拒绝审核**与管理员**安全下架**。