play managed publishing: console plan/verify play-publish-changes, ready-to-publish items in play status, desktop hint

This commit is contained in:
CRTkafa 2026-10-24 11:56:07 +08:00
parent 29a9683ab7
commit 1bb43fd4a0
20 changed files with 184 additions and 9 deletions

View file

@ -7,7 +7,8 @@ import { consoleAnswers, PLAN_FORM_IDS, PLAN_ONLY_FORM_IDS } from '../cli/lib/co
import { declarationSaveAllowed, flowAllowed, forbiddenControl, formDef, forbiddenTick, publishingControl } from '../cli/lib/console-forms';
import { consolePlan, IN_PAGE_LOOP, playConsoleReminders, planText } from '../cli/lib/console-plan';
import { APP_CONTENT_CARDS, PLAY_MATERIAL_FACT, startLabel } from '../cli/lib/console-play-plans';
import { appPrivacyPublished, verifyAppContent, verifyPlayDeclaration, verifyText } from '../cli/lib/console-verify';
import { readyLines, readyToPublish } from '../cli/lib/play-publish';
import { appPrivacyPublished, verifyPublishChangesApi, verifyAppContent, verifyPlayDeclaration, verifyText } from '../cli/lib/console-verify';
import { CONSOLE_PLAN_FORMS, TOOLS, validateInput } from '../cli/mcp-tools';
import { playRules, type PlaySnapshot } from '../cli/lib/validate-rules';
@ -27,7 +28,7 @@ describe('agent-only Play plans: shape, URLs, never-press lists, the owner batch
'play-store-settings': 'store-settings', 'play-app-content': 'app-content/overview', 'play-privacy-policy': 'app-content/privacy-policy',
'play-ads': 'app-content/ads-declaration', 'play-sign-in-details': 'app-content/testing-credentials', 'play-advertising-id': 'app-content/ad-id-declaration',
'play-government-apps': 'app-content/government-apps', 'play-financial-features': 'app-content/finance', 'play-health-apps': 'app-content/health', 'play-games-services': 'play-games-services/setup',
'play-production-countries': 'tracks/production', 'play-production-release': 'tracks/production',
'play-production-countries': 'tracks/production', 'play-production-release': 'tracks/production', 'play-publish-changes': 'publishing',
};
for (const form of PLAN_ONLY_FORM_IDS) {
test(form, () => {
@ -45,7 +46,7 @@ describe('agent-only Play plans: shape, URLs, never-press lists, the owner batch
for (const b of ['Publish', 'Submit', 'Send changes for review']) expect(p.buttons.neverPress).toContain(b);
for (const b of p.buttons.allowed) expect(b.class).not.toBe('final-submit');
// an App content card's Save is the agent's (verified 2026-10-13); the owner's button is Send changes for review
if (form.startsWith('play-') && !['play-store-settings', 'play-app-content', 'play-production-countries', 'play-production-release', 'play-games-services'].includes(form)) {
if (form.startsWith('play-') && !['play-store-settings', 'play-app-content', 'play-production-countries', 'play-production-release', 'play-games-services', 'play-publish-changes'].includes(form)) {
expect(p.buttons.final).toBe('Send changes for review');
expect(p.buttons.neverPress).not.toContain('Save');
expect(p.buttons.allowed).toContainEqual(expect.objectContaining({ button: 'Save', class: 'declaration-save' }));
@ -58,7 +59,8 @@ describe('agent-only Play plans: shape, URLs, never-press lists, the owner batch
p.ownerBatch.forEach((o, i) => expect(o.n).toBe(i + 1));
expect(planText(p).indexOf('Owner batch')).toBeLessThan(planText(p).indexOf('Steps:'));
expect(p.uiFacts.join('\n')).toContain(PLAY_MATERIAL_FACT);
expect(p.verify.command).toBe(`storebound console verify ${form} ${p.verify.capture.includes("aria-selected") ? "--from" : "--from-text"} -`);
// play-publish-changes verifies through the API (no page text needed)
expect(p.verify.command).toBe(form === 'play-publish-changes' ? 'storebound console verify play-publish-changes' : `storebound console verify ${form} ${p.verify.capture.includes("aria-selected") ? "--from" : "--from-text"} -`);
expect(p.inPageLoop).toBe(IN_PAGE_LOOP);
expect(planText(p)).toContain('window.__sb.run');
expect(JSON.parse(JSON.stringify(p))).toEqual(p);
@ -328,6 +330,32 @@ describe('the first production release: countries, managed publishing, the owner
expect(verifyText('play-production-release', base, 'Publishing overview\nChanges in review').ok).toBe(true);
});
test('publish changes: Publish N changes is the final button, never-press holds Discard / Stop managed publishing, ready list from the API', () => {
const managed = { ...base, play: { ...base.play!, managedPublishing: true } } as ShipConfig;
const p = consolePlan('play-publish-changes', managed, { readyChanges: [{ track: 'production', version: '2.0.0', build: '45', rollout: 'full rollout' }] });
expect(p.buttons.final).toBe('Publish N changes');
for (const b of ['Discard changes', 'Stop managed publishing', 'Publish N changes']) expect(p.buttons.neverPress).toContain(b);
expect(p.answers[0]!.text).toContain('production 2.0.0 (45) full rollout');
expect(p.steps.map(s => s.text).join('\n')).toContain('Changes ready to publish');
expect(p.ownerBatch.at(-1)!.text).toContain('Publish N changes');
expect(consolePlan('play-publish-changes', managed).answers[0]!.text).toContain('not read');
});
test('publish changes: ready = managed + completed production the store page does not show; verify by API and by page text', () => {
const tracks = [{ track: 'production', releases: [{ name: '2.0.0 (45)', versionCodes: ['45'], status: 'completed' }] }];
const ready = readyToPublish({ tracks, publicPage: { state: 'notPublic' }, managedPublishing: true });
expect(ready).toEqual([{ track: 'production', version: '2.0.0', build: '45', rollout: 'full rollout', liveVersion: null }]);
expect(readyToPublish({ tracks, publicPage: { state: 'notPublic' }, managedPublishing: false })).toEqual([]);
expect(readyToPublish({ tracks, publicPage: { state: 'public', versionName: '2.0.0' }, managedPublishing: true })).toEqual([]);
expect(readyToPublish({ tracks, publicPage: { state: 'public', versionName: '1.9.0' }, managedPublishing: true })[0]!.liveVersion).toBe('1.9.0');
expect(readyLines(ready).join('\n')).toContain('storebound console plan play-publish-changes');
expect(readyLines([])).toEqual([]);
expect(verifyPublishChangesApi(ready).ok).toBe(false);
expect(verifyPublishChangesApi([]).ok).toBe(true);
expect(verifyText('play-publish-changes', base, 'Publishing overview\nChanges ready to publish\nPublish 2 changes').ok).toBe(false);
expect(verifyText('play-publish-changes', base, 'Publishing overview\nChanges in review').ok).toBe(true);
});
test('validate: production with 0 countries is a blocker with a release on the track, a warning without', () => {
const snap = { defaultLanguage: 'en-US', expectedLanguages: [], listings: [], privacyUrl: { url: 'https://example.com/privacy', status: 200 }, website: null, appAds: null, appAdsLines: [], adsLikely: false, tracks: [], products: [], expectedProducts: [] } as unknown as PlaySnapshot;
expect(playRules({ ...snap, productionCountries: 0 }).find(f => f.code === 'countries')).toMatchObject({ level: 'warn', msg: 'production has 0 countries / regions (the API cannot set them)', fix: 'storebound console plan play-production-countries' });