// Console deep links (pitfall 71): every page key builds an absolute https URL with and without ids, and no message in // cli/ sends the owner to "App Store Connect > ..." / "Play Console > ..." / "App content > ..." without a URL. import { afterEach, describe, expect, test } from 'bun:test'; import { readdirSync, readFileSync, statSync } from 'node:fs'; import { join } from 'node:path'; import { CONSOLE_PAGES, type ConsolePage, consoleLink, consoleUrl, consoleUrlExact, playTrackPage, setConsoleIds } from '../cli/lib/console-links'; const IDS = { appleAppId: '1000000001', playDeveloperId: '5550000000000000001', playAppId: '4970000000000000002' }; const PAGES = Object.keys(CONSOLE_PAGES) as ConsolePage[]; afterEach(() => setConsoleIds({})); describe('console-links', () => { test('every page: absolute https URL without ids', () => { for (const p of PAGES) { const u = consoleUrl(p, {}); expect(u).toMatch(/^https:\/\/(appstoreconnect\.apple\.com|play\.google\.com\/console)/); expect(() => new URL(u)).not.toThrow(); expect(consoleLink(p, '', {})).toContain(`: ${u}`); } }); test('every page: id-specific URL with ids', () => { for (const p of PAGES) { const u = consoleUrl(p, IDS); expect(u.startsWith('https://')).toBe(true); expect(consoleUrlExact(p, IDS)).toBe(true); const page = CONSOLE_PAGES[p]; if (page.scope === 'app') expect(u).toContain(page.store === 'apple' ? `/apps/${IDS.appleAppId}/` : `/developers/${IDS.playDeveloperId}/app/${IDS.playAppId}/`); if (page.store === 'play' && page.scope === 'developer') expect(u).toContain(`/developers/${IDS.playDeveloperId}/`); } }); test('fallbacks: app list / developer chooser; reused verified shapes', () => { expect(consoleUrl('asc.age-rating', {})).toBe('https://appstoreconnect.apple.com/apps'); expect(consoleUrl('play.data-safety', {})).toBe('https://play.google.com/console'); expect(consoleUrl('play.data-safety', { playDeveloperId: IDS.playDeveloperId })).toBe(`https://play.google.com/console/u/0/developers/${IDS.playDeveloperId}/app-list`); expect(consoleUrl('play.dashboard', IDS)).toBe(`https://play.google.com/console/u/0/developers/${IDS.playDeveloperId}/app/${IDS.playAppId}/app-dashboard`); expect(consoleUrl('play.content-rating', IDS)).toEndWith('/app-content/content-rating-overview'); expect(consoleUrl('play.create-app', IDS)).toEndWith(`/developers/${IDS.playDeveloperId}/create-new-app`); expect(consoleUrl('asc.keys', {})).toBe('https://appstoreconnect.apple.com/access/integrations/api'); expect(consoleUrlExact('asc.app-privacy', {})).toBe(false); expect(consoleUrl('asc.app-info', { appleAppId: 'not-a-number' })).toBe('https://appstoreconnect.apple.com/apps'); }); test('ids remembered from the loaded config', () => { setConsoleIds(IDS); expect(consoleUrl('asc.app-privacy')).toBe(`https://appstoreconnect.apple.com/apps/${IDS.appleAppId}/distribution/privacy`); expect(consoleLink('play.app-access')).toContain(`/app/${IDS.playAppId}/app-content/testing-credentials`); expect(playTrackPage('production')).toBe('play.production'); expect(playTrackPage('beta')).toBe('play.releases'); }); }); describe('guard: no plain console directions in cli/', () => { const PLAIN = /App Store Connect >|Play Console >|App content >/; const LINKED = /https:\/\/|consoleLink\(|consoleUrl\(|\.url\b|\$\{\w*[Uu]rl\}/; // ${url}: a variable that holds the absolute link const SKIP = new Set(['console-links.ts', 'embedded.ts']); // embedded.ts is generated from templates/ (checked below) const files = (dir: string): string[] => readdirSync(dir).flatMap(n => { const p = join(dir, n); return statSync(p).isDirectory() ? files(p) : p.endsWith('.ts') && !p.endsWith('.test.ts') && !SKIP.has(n) ? [p] : []; }); test('every message line with a console path carries an https URL or goes through console-links', () => { const bad: string[] = []; for (const f of files('cli')) { readFileSync(f, 'utf8').split('\n').forEach((line, i) => { const t = line.trim(); if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) return; // comments are not messages if (PLAIN.test(line) && !LINKED.test(line)) bad.push(`${f}:${i + 1}: ${t.slice(0, 120)}`); }); } expect(bad).toEqual([]); }); test('templates: console directions in the generated docs carry a URL', () => { const bad: string[] = []; const walk = (dir: string): string[] => readdirSync(dir).flatMap(n => { const p = join(dir, n); return statSync(p).isDirectory() ? walk(p) : p.endsWith('.md') ? [p] : []; }); for (const f of walk('templates')) readFileSync(f, 'utf8').split('\n').forEach((line, i) => { if (/App Store Connect >|Play Console >/.test(line) && !/https:\/\//.test(line)) bad.push(`${f}:${i + 1}`); }); expect(bad).toEqual([]); }); });