// console fill apple-app-privacy without apple.appId: the app id from the App Store Connect API (by bundle id, cached) // -> deep link; no credentials / no app -> the Apps list by name. No network: the lookup is injected. import { afterAll, describe, expect, test } from 'bun:test'; import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { appleAppId, withAppleAppId } from '../cli/console/app-id'; import { appKey } from '../cli/console/fill'; import type { ShipConfig } from '../cli/lib/config'; import { navPlan } from '../cli/lib/console-forms'; const tmp = mkdtempSync(join(tmpdir(), 'sb-app-id-')); afterAll(() => rmSync(tmp, { recursive: true, force: true })); const config = { app: { name: 'Example Game', profile: 'game', privacyPolicyUrl: 'https://example.com/privacy', supportUrl: 'https://example.com/support', website: 'https://example.com', contactEmail: 'support@example.com' }, languages: [{ id: 'en', play: 'en-US', apple: 'en-US' }], apple: { bundleId: 'com.example.game', primaryLocale: 'en-US' }, } as ShipConfig; describe('apple app id for console fill', () => { test('API credentials: looked up by bundle id, cached, and the form opens by deep link', async () => { const cacheFile = join(tmp, 'a', 'console-app-ids.json'); const said: string[] = []; const asked: string[] = []; const lookup = async (b: string) => { asked.push(b); return '6800000001'; }; const id = await appleAppId(tmp, config, { say: m => said.push(m), lookup, cacheFile }); expect(id).toBe('6800000001'); expect(asked).toEqual(['com.example.game']); expect(JSON.parse(readFileSync(cacheFile, 'utf8'))).toEqual({ 'com.example.game': '6800000001' }); expect(said.some(m => m.includes('Apple ID 6800000001 (App Store Connect API)'))).toBe(true); const plan = navPlan('apple-app-privacy', withAppleAppId(config, id)); expect(plan.map(s => s.kind)).toEqual(['goto', 'wait']); expect(plan[0]).toMatchObject({ url: 'https://appstoreconnect.apple.com/apps/6800000001/distribution/privacy' }); // the second run reads the cache, no API call const again = await appleAppId(tmp, config, { say: () => {}, lookup: async () => { throw new Error('not called'); }, cacheFile }); expect(again).toBe('6800000001'); }); test('no API credentials: null, said, and the Apps list by name is used', async () => { const cacheFile = join(tmp, 'b', 'console-app-ids.json'); const said: string[] = []; const id = await appleAppId(tmp, config, { say: m => said.push(m), lookup: async () => { throw new Error('ASC_KEY_ID and ASC_ISSUER_ID are not set (env, or .storebound/credentials.json)'); }, cacheFile }); expect(id).toBeNull(); expect(existsSync(cacheFile)).toBe(false); expect(said.join('\n')).toContain('finding the app in the Apps list by name'); const plan = navPlan('apple-app-privacy', withAppleAppId(config, id)); expect(plan.map(s => s.kind)).toEqual(['goto', 'pickApp', 'click', 'wait']); }); test('no app with that bundle id: null; apple.appId in the config wins without a lookup; no bundle id: null', async () => { const cacheFile = join(tmp, 'c', 'console-app-ids.json'); expect(await appleAppId(tmp, config, { say: () => {}, lookup: async () => null, cacheFile })).toBeNull(); const set = { ...config, apple: { ...config.apple!, appId: '123' } } as ShipConfig; expect(await appleAppId(tmp, set, { say: () => {}, lookup: async () => { throw new Error('not called'); }, cacheFile })).toBe('123'); expect(withAppleAppId(set, '999')).toBe(set); const noBundle = { ...config, apple: { primaryLocale: 'en-US' } } as unknown as ShipConfig; expect(await appleAppId(tmp, noBundle, { say: () => {}, lookup: async () => { throw new Error('not called'); }, cacheFile })).toBeNull(); }); }); describe('appKey (one app shown twice in the list is one app)', () => { test('App Store Connect app URLs by id, any sub page; other links by URL without fragment', () => { expect(appKey('https://appstoreconnect.apple.com/apps/6800000001/distribution/ios/version/inflight')).toBe('apps/6800000001'); expect(appKey('https://appstoreconnect.apple.com/apps/6800000001/appstore')).toBe('apps/6800000001'); expect(appKey('https://appstoreconnect.apple.com/apps/6800000001')).toBe('apps/6800000001'); expect(appKey('http://127.0.0.1:1/asc-app.html?id=1#x')).toBe('http://127.0.0.1:1/asc-app.html?id=1'); expect(appKey(null)).toBeNull(); }); });