onboard reads review access from the live App Store version (READY_FOR_SALE / READY_FOR_DISTRIBUTION, newest live first; the newest version only when nothing is live): a newer draft's empty review detail no longer hides the live demo account or turns signInRequired off; reviewVersion in cli/lib/onboard.ts, tests with a live version holding a demo account and a newer draft without
This commit is contained in:
parent
0c4689a81b
commit
21fa91afd1
3 changed files with 41 additions and 5 deletions
|
|
@ -8,7 +8,7 @@ import { tmpdir } from 'node:os';
|
|||
import { join } from 'node:path';
|
||||
import { catalogDiff, type StoreItem, wantedCatalog } from '../cli/lib/catalog';
|
||||
import { loadConfig, type ShipConfig } from '../cli/lib/config';
|
||||
import { contactProposal, deletionLinks, findDeletionUrl, importCatalog, isTemplateValue, maskContact, signInDecision } from '../cli/lib/onboard';
|
||||
import { contactProposal, deletionLinks, findDeletionUrl, importCatalog, isTemplateValue, maskContact, reviewVersion, signInDecision } from '../cli/lib/onboard';
|
||||
import { toTs } from '../cli/lib/scan/config-edit';
|
||||
import { run as onboard } from '../cli/onboard';
|
||||
import { run as catalog } from '../cli/catalog';
|
||||
|
|
@ -27,7 +27,7 @@ const money = (currencyCode: string, units: string, nanos = 0) => ({ currencyCod
|
|||
|
||||
/** App Store Connect + Google Play as they answered for a live app (sanitized): Pro has its price schedule's base
|
||||
* territory in Turkey, Tips is on the App Store only, Plus monthly is a Play-only subscription. */
|
||||
function liveStores(o: { review?: Record<string, unknown> } = {}): Call[] {
|
||||
function liveStores(o: { review?: Record<string, unknown>; draft?: boolean } = {}): Call[] {
|
||||
const P = '/androidpublisher/v3/applications/com.example.notes';
|
||||
return mockFetch([
|
||||
// ---- App Store Connect: catalog
|
||||
|
|
@ -48,7 +48,13 @@ function liveStores(o: { review?: Record<string, unknown> } = {}): Call[] {
|
|||
{ match: /^\/v1\/apps\/6400000101\/subscriptionGroups/, answer: page([]) },
|
||||
// ---- App Store Connect: app details and review detail
|
||||
{ match: /^\/v1\/apps\/6400000101$/, answer: { data: { type: 'apps', id: '6400000101', attributes: { bundleId: 'com.example.notes', primaryLocale: 'en-US' } } } },
|
||||
{ match: /^\/v1\/apps\/6400000101\/appStoreVersions\?filter\[platform\]=IOS/, answer: page([{ type: 'appStoreVersions', id: 'v1', attributes: { versionString: '2.3.0', appStoreState: 'READY_FOR_SALE', appVersionState: 'READY_FOR_DISTRIBUTION' } }]) },
|
||||
// with `draft`: a newer 2.4.0 in PREPARE_FOR_SUBMISSION (listed first, as App Store Connect does) whose review detail is empty
|
||||
{ match: /^\/v1\/apps\/6400000101\/appStoreVersions\?filter\[platform\]=IOS/, answer: page([
|
||||
...(o.draft ? [{ type: 'appStoreVersions', id: 'v2', attributes: { versionString: '2.4.0', appStoreState: 'PREPARE_FOR_SUBMISSION', appVersionState: 'PREPARE_FOR_SUBMISSION', createdDate: '2026-09-20T01:00:00-07:00' } }] : []),
|
||||
{ type: 'appStoreVersions', id: 'v1', attributes: { versionString: '2.3.0', appStoreState: 'READY_FOR_SALE', appVersionState: 'READY_FOR_DISTRIBUTION', createdDate: '2026-09-01T01:00:00-07:00' } }]) },
|
||||
{ match: /^\/v1\/appStoreVersions\/v2\/appStoreVersionLocalizations/, answer: page([{ type: 'appStoreVersionLocalizations', id: 'vl2', attributes: { locale: 'en-US', supportUrl: 'https://notes.example.org/help', marketingUrl: 'https://notes.example.org' } }]) },
|
||||
{ match: /^\/v1\/appStoreVersions\/v2\/appStoreReviewDetail/, answer: { data: { type: 'appStoreReviewDetails', id: 'rd2', attributes: { contactFirstName: 'Ada', contactLastName: 'Example', contactPhone: '+1 555 0100 77', contactEmail: 'review@example.org',
|
||||
demoAccountName: null, demoAccountPassword: null, demoAccountRequired: false, notes: null } } } },
|
||||
{ match: /^\/v1\/appStoreVersions\/v1\/appStoreVersionLocalizations/, answer: page([{ type: 'appStoreVersionLocalizations', id: 'vl1', attributes: { locale: 'en-US', supportUrl: 'https://notes.example.org/help', marketingUrl: 'https://notes.example.org' } }]) },
|
||||
{ match: /^\/v1\/appStoreVersions\/v1\/appStoreReviewDetail/, answer: { data: { type: 'appStoreReviewDetails', id: 'rd1', attributes: { contactFirstName: 'Ada', contactLastName: 'Example', contactPhone: '+1 555 0100 77', contactEmail: 'review@example.org',
|
||||
demoAccountName: 'demo-reviewer', demoAccountPassword: 'demo-secret-sentinel', demoAccountRequired: true, notes: 'Sign in with the demo account.', ...o.review } } } },
|
||||
|
|
@ -125,6 +131,12 @@ describe('contact, sign-in, deletion page (pure)', () => {
|
|||
expect(isTemplateValue('https://notes.example.org')).toBe(false);
|
||||
expect(maskContact({ firstName: 'Ada', lastName: 'Example', phone: '+1 555 0100 77', email: 'review@example.org' })).toBe('A. E. r***@example.org, phone ***77');
|
||||
});
|
||||
test('review version: the live one, newest first; the newest only when nothing is live', () => {
|
||||
const v = (id: string, state: string, createdDate: string) => ({ id, attributes: { appStoreState: state, appVersionState: state === 'READY_FOR_SALE' ? 'READY_FOR_DISTRIBUTION' : state, createdDate } });
|
||||
expect(reviewVersion([v('d', 'PREPARE_FOR_SUBMISSION', '2026-09-20'), v('l1', 'READY_FOR_SALE', '2026-08-01'), v('l2', 'READY_FOR_SALE', '2026-09-01')])?.id).toBe('l2');
|
||||
expect(reviewVersion([v('a', 'PREPARE_FOR_SUBMISSION', '2026-08-01'), v('b', 'WAITING_FOR_REVIEW', '2026-09-01')])?.id).toBe('b');
|
||||
expect(reviewVersion([])).toBeNull();
|
||||
});
|
||||
test('sign-in: Apple review detail first, then the code, never a default yes', () => {
|
||||
expect(signInDecision({ auth: [], apple: null })).toMatchObject({ signInRequired: false, question: null });
|
||||
expect(signInDecision({ auth: ['src/auth.ts:1'], apple: null })).toMatchObject({ signInRequired: null });
|
||||
|
|
@ -187,6 +199,17 @@ describe('storebound onboard (recorded live shapes)', () => {
|
|||
expect(d.exit).toBeNull();
|
||||
expect(d.err + d.out).toContain('"errors": 0');
|
||||
});
|
||||
test('a newer draft with an empty review detail: the live version demo account is imported and sign-in is required', async () => {
|
||||
ascEnv(); playEnv();
|
||||
const p = liveProject();
|
||||
liveStores({ draft: true });
|
||||
const r = await capture(() => onboard(p.ctx(['--write'], ['write'])));
|
||||
expect(r.out + r.err).not.toContain('demo-secret-sentinel');
|
||||
expect(r.out + r.err).toContain('sign-in for review: required (App Store review detail: demo account required (a demo account is set there))');
|
||||
const creds = JSON.parse(readFileSync(join(p.root, '.storebound', 'credentials.json'), 'utf8'));
|
||||
expect(creds.reviewDemoAccount).toEqual({ username: 'demo-reviewer', password: 'demo-secret-sentinel' });
|
||||
expect((await loadConfig(p.loaded.path)).config.reviewAccess).toEqual({ signInRequired: true });
|
||||
});
|
||||
test('without --write nothing is written; sign-in unproven becomes the one owner question', async () => {
|
||||
ascEnv(); playEnv();
|
||||
const p = liveProject();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue