tests: game services on both stores against the fakes (capability ensure, Game Center enable and read-back, leaderboard / achievement create, update, no-op, lost answers), entitlement and APP_ID checks, PGS console plan URLs and verify, validate findings; fake Games Configuration API
This commit is contained in:
parent
9f48dd57a4
commit
a4c31e096b
2 changed files with 255 additions and 2 deletions
|
|
@ -75,6 +75,12 @@ const LISTS: Record<string, { type: string; one?: boolean }> = {
|
|||
'appAssetLibraries/images': { type: 'appAssetLibraryImages' },
|
||||
'appAssetLibraries/videos': { type: 'appAssetLibraryVideos' },
|
||||
'appStoreVersionLocalizations/placements': { type: 'appAssetLibraryPlacements' },
|
||||
'apps/gameCenterDetail': { type: 'gameCenterDetails', one: true },
|
||||
'appStoreVersions/gameCenterAppVersion': { type: 'gameCenterAppVersions', one: true },
|
||||
'gameCenterDetails/gameCenterLeaderboards': { type: 'gameCenterLeaderboards' },
|
||||
'gameCenterDetails/gameCenterAchievements': { type: 'gameCenterAchievements' },
|
||||
'gameCenterLeaderboards/localizations': { type: 'gameCenterLeaderboardLocalizations' },
|
||||
'gameCenterAchievements/localizations': { type: 'gameCenterAchievementLocalizations' },
|
||||
};
|
||||
/** Stores refuse a second resource with the same key (409 ENTITY_ERROR ... already exists). */
|
||||
const UNIQUE: Record<string, (r: R) => string> = {
|
||||
|
|
@ -100,6 +106,12 @@ const UNIQUE: Record<string, (r: R) => string> = {
|
|||
promotedPurchases: r => String(r.rels.inAppPurchaseV2 ?? r.rels.subscription),
|
||||
winBackOffers: r => `${r.rels.subscription}|${r.attributes.offerId}`,
|
||||
betaTesters: r => String(r.attributes.email).toLowerCase(),
|
||||
gameCenterDetails: r => String(r.rels.app),
|
||||
gameCenterAppVersions: r => String(r.rels.appStoreVersion),
|
||||
gameCenterLeaderboards: r => String(r.attributes.vendorIdentifier),
|
||||
gameCenterAchievements: r => String(r.attributes.vendorIdentifier),
|
||||
gameCenterLeaderboardLocalizations: r => `${r.rels.gameCenterLeaderboard}|${r.attributes.locale}`,
|
||||
gameCenterAchievementLocalizations: r => `${r.rels.gameCenterAchievement}|${r.attributes.locale}`,
|
||||
};
|
||||
const ASSETS = /Screenshots$|^appPreviews$|^appScreenshots$|^appAssetLibrary(Images|Videos)$/;
|
||||
/** App Asset Library assets: a state of their own (AWAITING_UPLOAD -> PREPARE_FOR_SUBMISSION after the commit, as Apple's
|
||||
|
|
@ -496,13 +508,43 @@ export class FakePlay {
|
|||
|
||||
// ------------------------------------------------------------------------------------------------- fetch + counting
|
||||
|
||||
export interface Stores { asc: FakeAsc; play: FakePlay; calls: Call[]; faults: Fault[]; failPart?: (n: number) => boolean }
|
||||
// ----------------------------------------------------------------- Play Games Services Configuration API (v1configuration)
|
||||
|
||||
/** achievementConfigurations / leaderboardConfigurations: list (items, nextPageToken), insert (Google generates the
|
||||
* id, CgkI...), update (PUT replaces the configuration; `published` stays Google's). Shapes from Google's reference. */
|
||||
export class FakeGames {
|
||||
achievements = new Map<string, Record<string, unknown>>();
|
||||
leaderboards = new Map<string, Record<string, unknown>>();
|
||||
next = 1;
|
||||
handle(method: string, url: string, body: unknown): Response {
|
||||
const p = new URL(url).pathname.replace(/^\/games\/v1configuration/, '');
|
||||
let m: RegExpExecArray | null;
|
||||
if ((m = /^\/applications\/(\d+)\/(achievements|leaderboards)$/.exec(p))) {
|
||||
const map = this[m[2] as 'achievements' | 'leaderboards'];
|
||||
if (method === 'GET') return json({ kind: `gamesConfiguration#${m[2] === 'achievements' ? 'achievement' : 'leaderboard'}ConfigurationListResponse`, items: [...map.values()] });
|
||||
const id = `CgkI${m[1]}EAIQ${this.next++}`;
|
||||
const r = { ...(body as Record<string, unknown>), id, token: id };
|
||||
map.set(id, r);
|
||||
return json(r);
|
||||
}
|
||||
if ((m = /^\/(achievements|leaderboards)\/([^/]+)$/.exec(p))) {
|
||||
const map = this[m[1] as 'achievements' | 'leaderboards'];
|
||||
const id = decodeURIComponent(m[2]!);
|
||||
if (!map.has(id)) return json({ error: { code: 404, status: 'NOT_FOUND', message: 'Not found' } }, 404);
|
||||
if (method === 'PUT') map.set(id, { ...(body as Record<string, unknown>), id, token: id, published: map.get(id)!.published });
|
||||
return json(map.get(id));
|
||||
}
|
||||
return json({ error: { code: 599, message: `fake games: ${method} ${p}` } }, 599);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Stores { asc: FakeAsc; play: FakePlay; games: FakeGames; calls: Call[]; faults: Fault[]; failPart?: (n: number) => boolean }
|
||||
|
||||
const realFetch = globalThis.fetch;
|
||||
|
||||
/** Installs both fakes as fetch. Faults apply in order (`times`, default 1). */
|
||||
export function installStores(): Stores {
|
||||
const s: Stores = { asc: new FakeAsc(), play: new FakePlay(), calls: [], faults: [] };
|
||||
const s: Stores = { asc: new FakeAsc(), play: new FakePlay(), games: new FakeGames(), calls: [], faults: [] };
|
||||
let partNo = 0;
|
||||
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
||||
const url = String(input instanceof Request ? input.url : input);
|
||||
|
|
@ -524,6 +566,7 @@ export function installStores(): Stores {
|
|||
res = new Response(null, { status: 200 });
|
||||
} else if (url.startsWith(ASC)) res = s.asc.handle(method, path, body);
|
||||
else if (url.startsWith('https://androidpublisher.googleapis.com/')) res = s.play.handle(method, url, body instanceof Uint8Array ? body : body);
|
||||
else if (url.startsWith('https://gamesconfiguration.googleapis.com/')) res = s.games.handle(method, url, body);
|
||||
else res = new Response(`fake: no route ${method} ${url}`, { status: 599 });
|
||||
if (f?.when === 'after') return json({ errors: [{ status: String(f.status ?? 502), detail: 'injected after the write' }] }, f.status ?? 502);
|
||||
return res;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue