play: changesNotSentForReview goes only on edits.commit (validate answers 400 "Unknown name changesNotSentForReview: Cannot bind query parameter", found by a game on 220e9bb's real run): a refused commit is committed again with the parameter, a dry run's refused validate only says how the real run commits; the fake Play refuses the parameter on validate and a test asserts validate never gets it

This commit is contained in:
CRTkafa 2026-10-16 05:53:18 +08:00
parent dcb553fd16
commit 8a84ab4089
6 changed files with 22 additions and 13 deletions

View file

@ -359,6 +359,8 @@ export class FakePlay {
/** How many validate / commit calls without changesNotSentForReview=true Play refuses with its 400 "Changes cannot
* be sent for review automatically" (after a rejection, pending changes, managed publishing: pitfall 72). */
refuseAutoReview = 0;
/** validate calls that carried changesNotSentForReview (live Play answers them 400 "Unknown name") */
validateWithNotSent = 0;
handle(method: string, url: string, body: unknown): Response {
const u = new URL(url);
@ -371,6 +373,11 @@ export class FakePlay {
const e = this.edits.get(id!);
if (!e) return err(404, 'NOT_FOUND', 'Edit has been deleted');
if (method === 'DELETE' && rest === '') { this.edits.delete(id!); return new Response(null, { status: 204 }); }
// live: only edits.commit binds changesNotSentForReview; validate refuses the parameter
if (method === 'POST' && rest === ':validate' && u.searchParams.has('changesNotSentForReview')) {
this.validateWithNotSent++;
return json({ error: { code: 400, status: 'INVALID_ARGUMENT', message: 'Invalid JSON payload received. Unknown name "changesNotSentForReview": Cannot bind query parameter. Field \'changesNotSentForReview\' could not be found in request message.' } }, 400);
}
if (method === 'POST' && (rest === ':validate' || rest === ':commit') && this.refuseAutoReview > 0 && u.searchParams.get('changesNotSentForReview') !== 'true') {
this.refuseAutoReview--;
return json({ error: { code: 400, status: 'INVALID_ARGUMENT', message: 'Changes cannot be sent for review automatically. Please set the query parameter changesNotSentForReview to true. Once committed, the changes in this edit can be sent for review from the Google Play Console UI.', errors: [{ message: 'Changes cannot be sent for review automatically. Please set the query parameter changesNotSentForReview to true.', domain: 'global', reason: 'badRequest' }] } }, 400);

View file

@ -35,13 +35,14 @@ describe('Play refuses the automatic send for review', () => {
expect(isReviewRefused(new Error('POST /edits/e1:commit -> 400 {"error":{"message":"Version code 5 has already been used."}}'))).toBe(false);
});
test('real run: the commit is refused once, then the same edit is validated and committed with changesNotSentForReview=true', async () => {
test('real run: the commit is refused once, then the same edit is committed with changesNotSentForReview=true (validate never takes it)', async () => {
const p = project(BASE, { 'store/listing.md': LISTING });
s.play.refuseAutoReview = 1;
const r = await release(p);
expect(r.exit ?? 0).toBe(0);
expect(edits('commit')).toEqual(['edit-1:commit', 'edit-1:commit?changesNotSentForReview=true']);
expect(edits('validate')).toEqual(['edit-1:validate?changesNotSentForReview=true']);
expect(edits('validate').filter(v => v.includes('changesNotSentForReview'))).toEqual([]);
expect(s.play.validateWithNotSent).toBe(0);
expect(s.calls.filter(c => c.method === 'POST' && /\/edits$/.test(c.path))).toHaveLength(1); // one edit, never reopened
expect(s.play.committed).toEqual(['edit-1']);
expect(s.play.live.tracks.production).toBeDefined();
@ -53,12 +54,13 @@ describe('Play refuses the automatic send for review', () => {
expect(rec!.summary).toContain('production completed');
});
test('dry run: validate is refused once, validated again with the parameter, no commit, says how the real run commits', async () => {
test('dry run: validate is refused once, never sent the parameter, no commit, says how the real run commits', async () => {
const p = project(BASE, { 'store/listing.md': LISTING });
s.play.refuseAutoReview = 1;
const r = await release(p, ['--dry']);
expect(r.exit ?? 0).toBe(0);
expect(edits('validate')).toEqual(['edit-1:validate', 'edit-1:validate?changesNotSentForReview=true']);
expect(edits('validate')).toEqual(['edit-1:validate']);
expect(s.play.validateWithNotSent).toBe(0);
expect(edits('commit')).toEqual([]);
expect(s.play.committed).toEqual([]);
expect(s.play.edits.size).toBe(0);