Google API errors as typed findings, never raw JSON: cli/lib/google-errors.ts parses SERVICE_DISABLED (api-disabled: service, Cloud project, console page), PERMISSION_DENIED (no-app-access: which app, Users and permissions), 401 / invalid_grant (auth-revoked) and 429 (quota); health / analytics print one line with the URL, raw text in detail and the log; desktop Health tab: the line, 'Turn it on in Google Cloud' and 'Check again', raw text in a collapsed Details; Portfolio shows a Cloud project blocking several apps once. Findings exit codes are results: the sidecar reports outcome (ok / findings / declined / failed) and the command's own error line; scan / check / validate / health exit 1 with findings show and cache the findings; no bare 'Exit code N' anywhere. tests/google-errors.test.ts, boot check errors cases (api-disabled, scan-findings, scan-failed) (pitfall 57)

This commit is contained in:
CRTkafa 2026-10-13 23:20:04 +08:00
parent f69c10d3a0
commit 89256f1e80
28 changed files with 666 additions and 47 deletions

View file

@ -0,0 +1,64 @@
// Live-shape failures for the boot check and tests: Google's 403 SERVICE_DISABLED body exactly as the Play Developer
// Reporting API answers it (the project id is an example), the health snapshot `storebound health --json` writes for
// it, and `storebound scan --json` results: findings with exit 1 (by design) and a real failure with its error line.
import { errorBody } from '../../../../cli/lib/http';
import { storeReadError } from '../../../../cli/lib/google-errors';
import { errorLine } from '../../../../cli/lib/desktop-protocol';
export const EXAMPLE_CLOUD_PROJECT = '123456789000';
export const SERVICE_DISABLED_403 = `{
"error": {
"code": 403,
"message": "Google Play Developer Reporting API has not been used in project ${EXAMPLE_CLOUD_PROJECT} before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/playdeveloperreporting.googleapis.com/overview?project=${EXAMPLE_CLOUD_PROJECT} then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
"status": "PERMISSION_DENIED",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "SERVICE_DISABLED",
"domain": "googleapis.com",
"metadata": {
"activationUrl": "https://console.developers.google.com/apis/api/playdeveloperreporting.googleapis.com/overview?project=${EXAMPLE_CLOUD_PROJECT}",
"service": "playdeveloperreporting.googleapis.com",
"consumer": "projects/${EXAMPLE_CLOUD_PROJECT}",
"serviceTitle": "Google Play Developer Reporting API",
"containerInfo": "${EXAMPLE_CLOUD_PROJECT}"
}
},
{
"@type": "type.googleapis.com/google.rpc.LocalizedMessage",
"locale": "en-US",
"message": "Google Play Developer Reporting API has not been used in project ${EXAMPLE_CLOUD_PROJECT} before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/playdeveloperreporting.googleapis.com/overview?project=${EXAMPLE_CLOUD_PROJECT} then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
},
{
"@type": "type.googleapis.com/google.rpc.Help",
"links": [{ "description": "Google developers console API activation", "url": "https://console.developers.google.com/apis/api/playdeveloperreporting.googleapis.com/overview?project=${EXAMPLE_CLOUD_PROJECT}" }]
}
]
}
}`;
/** The Error message storebound's Google client throws for it (play/google.ts). */
export const serviceDisabledMessage = (pkg: string): string => `GET /v1beta1/apps/${pkg}/crashRateMetricSet -> 403 ${errorBody(SERVICE_DISABLED_403, 800)}`;
/** `storebound health --json` when the Reporting API is off. */
export const healthApiDisabled = (pkg: string) => ({ date: '2026-10-13', play: storeReadError(serviceDisabledMessage(pkg), { app: pkg }) });
/** `storebound scan --json` that found problems: exit 1 by design, the result still printed. */
export const SCAN_FINDINGS = {
method: 'scan', ok: false, exitCode: 1, dryRun: false, command: 'storebound scan --json', outcome: 'findings' as const,
output: '12:00:00 [scan] 41 files\n12:00:00 [scan] 1 blocker, 0 warnings',
data: {
stacks: ['flutter'], sdks: [], counts: { blocker: 1, high: 0, medium: 0, low: 0, accepted: 0 },
declarations: { changed: false, items: [] },
findings: [{ rule: 'account-deletion', store: 'apple', severity: 'blocker', guideline: '5.1.1(v)', title: 'Sign-up without in-app account deletion',
evidence: [{ file: 'lib/auth/sign_up.dart', line: 12, text: 'createUserWithEmailAndPassword' }], fix: 'Add a "Delete account" action in the app settings', skill: 'account-deletion' }],
},
};
/** A real failure: the config does not load; the sidecar sends the command's own error line. */
const SCAN_STDERR = '\nerror: storebound.config.ts: app.name is missing';
export const SCAN_FAILED = {
method: 'scan', ok: false, exitCode: 1, dryRun: false, command: 'storebound scan --json', outcome: 'failed' as const,
output: `12:00:00 [scan] loading the config${SCAN_STDERR}`, error: errorLine(SCAN_STDERR, ''),
};
export const SCAN_FAILED_LINE = 'storebound.config.ts: app.name is missing';