apple testflight distribute: a build's groups read per group (GET /v1/betaGroups/{id}/relationships/builds) instead of GET /v1/betaGroups?filter[builds]=, which App Store Connect refuses with 400 although the spec declares it; internal all-builds groups reported as already available and never sent the build; a re-run adds nothing; configured groups missing on App Store Connect point at groups sync, unknown ones list what exists; APPLE_LIVE_REJECTED (refused by storebound api too), tests/asc-query-guard.test.ts checks every App Store Connect GET with a query in cli/ against the spec's declared parameters, and the fake App Store Connect answers 400 to undeclared or live-refused parameters (pitfall 42)

This commit is contained in:
CRTkafa 2026-10-13 06:03:16 +08:00
parent f741c15829
commit bba46b5c7f
13 changed files with 354 additions and 14 deletions

View file

@ -33,6 +33,15 @@ export interface Op {
shapes: Record<string, string>;
}
/**
* App Store Connect query parameters the spec declares but the live API answers 400 to, per operation id, with what to
* use instead. `storebound api` refuses them, and tests/asc-query-guard.test.ts fails when storebound's own code uses one.
*/
export const APPLE_LIVE_REJECTED: Record<string, Record<string, string>> = {
// seen live 2026-10-13 (apple testflight distribute): GET /v1/betaGroups?filter[app]=..&filter[builds]=.. -> 400
betaGroups_getCollection: { 'filter[builds]': 'seen live: 400; read the builds of each group with GET /v1/betaGroups/{id}/relationships/builds' },
};
/** Query parameters every Google API accepts on top of a method's own. */
export const GOOGLE_GLOBAL = ['fields', 'prettyPrint', 'quotaUser'];
@ -136,6 +145,8 @@ export function buildRequest(op: Op, params: Record<string, ParamValue | undefin
const names = op.query.map(x => x.name);
throw new Error(`${op.id}: unknown parameter "${k}"${names.length ? ` (takes: ${names.slice(0, 30).join(', ')}${names.length > 30 ? ', …' : ''})` : ' (takes no query parameters)'}`);
}
const refused = op.store === 'apple' ? APPLE_LIVE_REJECTED[op.id]?.[k] : undefined;
if (refused) throw new Error(`${op.id}: App Store Connect refuses "${k}" although the spec declares it (${refused})`);
const values = Array.isArray(v) ? v.map(str) : [str(v as string)];
if (q?.repeated) for (const x of values) parts.push(`${encName(k)}=${encVal(x)}`);
else parts.push(`${encName(k)}=${encVal(values.join(','))}`);