108 lines
7.9 KiB
TypeScript
108 lines
7.9 KiB
TypeScript
// One app record, two App Store platforms (pitfall 79). Seen live: `apple resubmit` said "nothing to resubmit" while
|
|
// the macOS version was REJECTED in an UNRESOLVED_ISSUES submission, and `apple listing --version 0.8.7` refused
|
|
// because the iOS 0.8.7 was live while the macOS 0.8.7 was rejected. Against the fake App Store Connect.
|
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
|
|
import type { ShipConfig } from '../cli/lib/config';
|
|
import { planVersion } from '../cli/lib/listing-plan';
|
|
import { upsertTiming } from '../cli/lib/upsert';
|
|
import { appleRules } from '../cli/lib/validate-rules';
|
|
import { applePlatformWarnings, appleReleases } from '../cli/lib/portfolio';
|
|
import { parsePlatform, pickPlatform, platformLabel } from '../cli/apple/platform';
|
|
import { run as appleResubmit } from '../cli/apple/resubmit';
|
|
import { run as appleStatus } from '../cli/apple/status';
|
|
import { run as appleRelease } from '../cli/apple/release';
|
|
import { ascEnv, BASE, capture, project, restore } from './helpers/store-mock';
|
|
import { installStores, type Stores, uninstallStores } from './helpers/fake-stores';
|
|
|
|
const APP = '6400000101';
|
|
const CONFIG: ShipConfig = { ...BASE };
|
|
let s: Stores;
|
|
beforeAll(ascEnv);
|
|
afterAll(restore);
|
|
beforeEach(() => { upsertTiming.waitMs = 0; s = installStores(); });
|
|
afterEach(uninstallStores);
|
|
|
|
const ver = (id: string, platform: string, versionString: string, state: string) =>
|
|
s.asc.put({ type: 'appStoreVersions', id, attributes: { versionString, appVersionState: state, platform }, rels: { app: APP } });
|
|
/** iOS 0.8.7 live, macOS 0.8.7 rejected in an UNRESOLVED_ISSUES submission, build 8 uploaded. */
|
|
function split(): void {
|
|
ver('v-ios', 'IOS', '0.8.7', 'READY_FOR_DISTRIBUTION');
|
|
ver('v-mac', 'MAC_OS', '0.8.7', 'REJECTED');
|
|
s.asc.put({ type: 'builds', id: 'b-8', attributes: { version: '8', processingState: 'VALID', usesNonExemptEncryption: false }, rels: { app: APP } });
|
|
s.asc.put({ type: 'reviewSubmissions', id: 'rs-mac', attributes: { platform: 'MAC_OS', state: 'UNRESOLVED_ISSUES' }, rels: { app: APP } });
|
|
s.asc.put({ type: 'reviewSubmissionItems', id: 'it-mac', attributes: { state: 'REJECTED' }, rels: { reviewSubmission: 'rs-mac', appStoreVersion: 'v-mac' } });
|
|
}
|
|
|
|
describe('platform picking (pure)', () => {
|
|
const v = (platform: string, versionString: string, state: string) => ({ attributes: { platform, versionString, appVersionState: state } });
|
|
const vs = [v('IOS', '0.8.7', 'READY_FOR_DISTRIBUTION'), v('MAC_OS', '0.8.7', 'REJECTED')];
|
|
test('--platform values, labels', () => {
|
|
expect(parsePlatform('macos')).toBe('MAC_OS');
|
|
expect(parsePlatform('MAC_OS')).toBe('MAC_OS');
|
|
expect(parsePlatform('visionOS')).toBe('VISION_OS');
|
|
expect(parsePlatform(undefined)).toBeUndefined();
|
|
expect(() => parsePlatform('android')).toThrow(/ios \| macos/);
|
|
expect(platformLabel('MAC_OS')).toBe('macOS');
|
|
});
|
|
test('one platform has it -> that one; both -> refuse naming both; none -> the only platform, else iOS', () => {
|
|
const rejected = (x: typeof vs[0]) => x.attributes.appVersionState === 'REJECTED';
|
|
expect(pickPlatform(vs, rejected, undefined, 'a rejected version')).toBe('MAC_OS');
|
|
expect(pickPlatform(vs, rejected, 'IOS', 'a rejected version')).toBe('IOS');
|
|
expect(() => pickPlatform([...vs, v('IOS', '0.8.8', 'REJECTED')], rejected, undefined, 'a rejected version'))
|
|
.toThrow(/a rejected version on more than one platform \(0\.8\.7 \(macOS\) REJECTED, 0\.8\.8 \(iOS\) REJECTED\): run it again with --platform macos or --platform ios/);
|
|
expect(pickPlatform([v('MAC_OS', '1.0', 'READY_FOR_SALE')], rejected, undefined, 'x')).toBe('MAC_OS');
|
|
expect(pickPlatform(vs, () => false, undefined, 'x')).toBe('IOS');
|
|
});
|
|
test('listing --version 0.8.7: the macOS one is editable, so macOS; its plan updates it instead of refusing', () => {
|
|
const p = pickPlatform(vs, [x => x.attributes.versionString === '0.8.7' && x.attributes.appVersionState === 'REJECTED', () => true], undefined, 'x');
|
|
expect(p).toBe('MAC_OS');
|
|
const rows = [{ id: 'v-mac', attributes: { versionString: '0.8.7', appVersionState: 'REJECTED', platform: 'MAC_OS', copyright: '2026 Example', releaseType: 'MANUAL' } }];
|
|
expect(planVersion(rows, '0.8.7', { copyright: '2026 Example', releaseType: 'MANUAL' }, 'MAC_OS').action).toBe('keep');
|
|
const ios = [{ id: 'v-ios', attributes: { versionString: '0.8.7', appVersionState: 'READY_FOR_DISTRIBUTION', platform: 'IOS' } }];
|
|
expect(planVersion(ios, '0.8.7', { copyright: 'c', releaseType: 'MANUAL' }, 'IOS').action).toBe('refuse');
|
|
expect(planVersion([], '1.0.0', { copyright: 'c', releaseType: 'MANUAL' }, 'MAC_OS').attributes.platform).toBe('MAC_OS');
|
|
});
|
|
test('validate and release status name the other platform', () => {
|
|
const f = appleRules({ version: { versionString: '0.8.7', state: 'READY_FOR_DISTRIBUTION', platform: 'IOS' }, otherPlatformVersions: [{ versionString: '0.8.7', state: 'REJECTED', platform: 'MAC_OS' }],
|
|
build: null, expectedLocales: [], locales: [], requiredDisplayTypes: [], reviewDetail: null, ageRating: null,
|
|
priceSchedule: true, availability: true, iaps: [], expectedProducts: [], privacyUrl: { url: 'https://example.com/privacy', status: 200 } });
|
|
expect(f.find(x => x.code === 'version')!.msg).toBe('version 0.8.7 (iOS) is READY_FOR_DISTRIBUTION (nothing to prepare)');
|
|
expect(f.find(x => x.code === 'version-platform')).toMatchObject({ level: 'warn', msg: 'version 0.8.7 (macOS) is REJECTED too: this report covers 0.8.7 (iOS) only' });
|
|
const data = { versions: [{ id: 'v-ios', attributes: { platform: 'IOS', versionString: '0.8.7', appVersionState: 'READY_FOR_DISTRIBUTION' } }, { id: 'v-mac', attributes: { platform: 'MAC_OS', versionString: '0.8.7', appVersionState: 'REJECTED' } }] };
|
|
expect(applePlatformWarnings(data)).toEqual(['App Store macOS 0.8.7: REJECTED (storebound apple status --platform macos)']);
|
|
expect(appleReleases(data).map(r => [r.version, r.platform])).toEqual([['0.8.7', 'IOS']]);
|
|
});
|
|
});
|
|
|
|
describe('commands against an app with iOS live and macOS rejected', () => {
|
|
test('apple resubmit picks the macOS version and its UNRESOLVED_ISSUES submission', async () => {
|
|
split();
|
|
const r = await capture(() => appleResubmit(project(CONFIG).ctx(['--build', '8', '--dry'])));
|
|
expect(r.out + r.err).toContain('app version 0.8.7 (macOS): REJECTED');
|
|
expect(r.out).toMatch(/rs-mac\s+UNRESOLVED_ISSUES\s+reuse/);
|
|
});
|
|
test('apple resubmit --platform ios: nothing on iOS, said per platform', async () => {
|
|
split();
|
|
await expect(appleResubmit(project(CONFIG).ctx(['--build', '8', '--platform', 'ios', '--dry']))).rejects.toThrow(/no rejected iOS App Store version and no iOS review submission/);
|
|
});
|
|
test('both platforms rejected: refuse and ask for --platform', async () => {
|
|
split();
|
|
s.asc.all('appStoreVersions').find(x => x.id === 'v-ios')!.attributes.appVersionState = 'REJECTED';
|
|
await expect(appleResubmit(project(CONFIG).ctx(['--build', '8', '--dry']))).rejects.toThrow(/more than one platform .*--platform (ios|macos) or --platform (ios|macos)/);
|
|
});
|
|
test('apple status labels every version and submission with its platform; --platform shows one', async () => {
|
|
split();
|
|
const r = await capture(() => appleStatus(project(CONFIG).ctx([])));
|
|
expect(r.out).toMatch(/version 0\.8\.7\s+iOS\s+READY_FOR_DISTRIBUTION/);
|
|
expect(r.out).toMatch(/version 0\.8\.7\s+macOS\s+REJECTED/);
|
|
expect(r.out).toContain('review submission rs-mac (macOS) is back from App Review');
|
|
expect(r.out).toContain('storebound apple resubmit --build <n> --platform macos');
|
|
const ios = await capture(() => appleStatus(project(CONFIG).ctx(['--platform', 'ios'])));
|
|
expect(ios.out).not.toContain('macOS');
|
|
});
|
|
test('apple release --phased on picks the platform with a version not yet live', async () => {
|
|
split();
|
|
const r = await capture(() => appleRelease(project(CONFIG).ctx(['--phased', 'on', '--dry'])));
|
|
expect(r.out + r.err).toContain('0.8.7 macOS (REJECTED)');
|
|
});
|
|
});
|