upload --pre-check/--evidence, shots copy translate, game-center --locale-map, play games API-disabled hint

This commit is contained in:
CRTkafa 2026-10-24 12:10:23 +08:00
parent 29a9683ab7
commit 8ffa27d4f8
24 changed files with 598 additions and 27 deletions

View file

@ -5,7 +5,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } fr
import type { ShipConfig } from '../cli/lib/config';
import { validateConfig } from '../cli/lib/config';
import { capabilitySpecs, planCapabilities } from '../cli/lib/signing';
import { gamesPrivacyFindings, gcFindings, pgsFindings, planGameCenter, playAchievementBody, playLeaderboardBody, readPgsProject, validateGames } from '../cli/lib/game-services';
import { gamesPrivacyFindings, gcFindings, gcLocaleMap, liveGcLocales, suggestLocaleMap, pgsFindings, planGameCenter, playAchievementBody, playLeaderboardBody, readPgsProject, validateGames } from '../cli/lib/game-services';
import { consolePlan } from '../cli/lib/console-plan';
import { verifyText } from '../cli/lib/console-verify';
import { gameServicesFindings, type GameServicesStatus } from '../cli/games-status';
@ -116,6 +116,42 @@ describe('apple game-center (fake App Store Connect)', () => {
});
});
describe('Game Center locale map', () => {
const live = { leaderboards: [{ id: 'lb-1', attributes: { vendorIdentifier: 'com.example.game.highscore', referenceName: 'com.example.game.highscore', defaultFormatter: 'INTEGER', submissionType: 'BEST_SCORE', scoreSortType: 'DESC' },
localizations: [{ id: 'l1', attributes: { locale: 'en-GB', name: 'High score', formatterSuffix: ' points' } }, { id: 'l2', attributes: { locale: 'tr', name: 'En yüksek skor' } }] }], achievements: [] };
const only = { ...GAME.games!, achievements: [] };
const sig = (p: ReturnType<typeof planGameCenter>) => p[0]!.locales.map(l => l.locale + ':' + l.action);
test('without a map the config en-US is created next to the live en-GB, and the suggestion names the map', () => {
expect(sig(planGameCenter(only, GAME.languages, live))).toEqual(['en-US:create', 'tr:keep']);
expect(suggestLocaleMap(GAME.languages, liveGcLocales(live))).toEqual([{ id: 'en', from: 'en-US', to: 'en-GB' }]);
});
test('--locale-map en=en-GB (or games.localeMap) leaves nothing to create or update, and no suggestion remains', () => {
const map = gcLocaleMap(only, ['en=en-GB']);
expect(map).toEqual({ en: 'en-GB' });
const plan = planGameCenter(only, GAME.languages, live, map);
expect(sig(plan)).toEqual(['en-GB:keep', 'tr:keep']);
expect(plan[0]!.action).toBe('keep');
expect(suggestLocaleMap(GAME.languages, liveGcLocales(live), map)).toEqual([]);
expect(gcLocaleMap({ ...only, localeMap: { en: 'en-AU' } }, ['en=en-GB,tr=tr'])).toEqual({ en: 'en-GB', tr: 'tr' });
expect(planGameCenter(only, GAME.languages, live, { 'en-US': 'en-GB' })[0]!.locales[0]!.locale).toBe('en-GB');
});
test('two English variants live: no guess; a bad map entry is refused', () => {
expect(suggestLocaleMap(GAME.languages, ['en-GB', 'en-AU', 'tr'])).toEqual([]);
expect(() => gcLocaleMap(only, ['en'])).toThrow('--locale-map');
expect(validateGames({ ...only, localeMap: { en: 'english' } }, GAME.languages).join()).toContain('games.localeMap.en');
});
test('apple game-center against the fake store: the dry run warns with the exact flag; with the flag nothing is written', async () => {
s.asc.put({ type: 'appStoreVersions', id: 'ver-1', attributes: { versionString: '1.0.0', appVersionState: 'PREPARE_FOR_SUBMISSION', platform: 'IOS' }, rels: { app: APP } });
const base = await run2(() => gameCenter(project(GAME).ctx(['--yes'])));
expect(base.writes.length).toBeGreaterThan(0);
for (const l of [...s.asc.all('gameCenterLeaderboardLocalizations'), ...s.asc.all('gameCenterAchievementLocalizations')]) if (l.attributes.locale === 'en-US') l.attributes.locale = 'en-GB';
const dry = await run2(() => gameCenter(project(GAME).ctx(['--dry'])));
expect(dry.out + dry.err).toContain('--locale-map en=en-GB');
const mapped = await run2(() => gameCenter(project(GAME).ctx(['--yes', '--locale-map', 'en=en-GB'])));
expect(mapped.writes).toEqual([]);
});
});
describe('play games (fake Games Configuration API)', () => {
test('create, no-op re-run, update by name match; playId wins', async () => {
const p = project(GAME);
@ -141,6 +177,21 @@ describe('play games (fake Games Configuration API)', () => {
});
});
describe('play games with the Games Configuration API disabled', () => {
test('a 403 SERVICE_DISABLED names the API, the enable URL for the project and the console plan', async () => {
s.faults.push({ method: 'GET', match: /leaderboards/, when: 'before', status: 403, times: 5, code: 'SERVICE_DISABLED', detail: 'Google Play Game Services Publishing API has not been used in project 987654321 before or it is disabled. SERVICE_DISABLED accessNotConfigured' });
const err = await run2(() => playGames(project(GAME).ctx(['--dry']))).then(() => null, (e: Error) => e);
expect(err?.message).toContain('Google Play Game Services Publishing API');
expect(err?.message).toContain('gamesconfiguration.googleapis.com');
expect(err?.message).toContain('https://console.cloud.google.com/apis/library/gamesconfiguration.googleapis.com?project=987654321');
expect(err?.message).toContain('storebound console plan play-games-services');
});
test('the console plan has the enable step', () => {
const plan = consolePlan('play-games-services', GAME);
expect(JSON.stringify(plan)).toContain('gamesconfiguration.googleapis.com');
});
});
describe('project checks', () => {
const ENT = '<?xml version="1.0"?><plist><dict><key>com.apple.developer.game-center</key><true/></dict></plist>';
test('Game Center entitlement: missing, unreferenced, present, present without config', () => {