import { describe, expect, test } from 'bun:test'; import { availableTerritories, blockedItems, currentBasePrice, planAvailability } from '../cli/apple/appinfo'; import { reviewDetailDiff } from '../cli/apple/review-details'; const price = (id: string, startDate: string | null, point: string) => ({ type: 'appPrices', id, attributes: { startDate }, relationships: { appPricePoint: { data: { type: 'appPricePoints', id: point } } } }); const point = (id: string, customerPrice: string) => ({ type: 'appPricePoints', id, attributes: { customerPrice } }); describe('currentBasePrice', () => { test('paid price is shown as a number', () => { expect(currentBasePrice({ data: [price('a', null, 'p1')], included: [point('p1', '4.99')] })).toBe('4.99'); }); test('zero is Free', () => { expect(currentBasePrice({ data: [price('a', null, 'p1')], included: [point('p1', '0.0')] })).toBe('Free'); }); test('latest started price wins, future ones are ignored', () => { const page = { data: [price('a', null, 'p1'), price('b', '2026-01-01', 'p2'), price('c', '2099-01-01', 'p3')], included: [point('p1', '0'), point('p2', '1.99'), point('p3', '9.99')] }; expect(currentBasePrice(page, '2026-06-01')).toBe('1.99'); }); test('unreadable gives null', () => { expect(currentBasePrice(null)).toBeNull(); expect(currentBasePrice({ data: [price('a', null, 'zz')], included: [] })).toBeNull(); }); }); describe('availability', () => { const ta = (t: string, available: boolean) => ({ attributes: { available }, relationships: { territory: { data: { id: t } } } }); test('availableTerritories lists only available ones across pages', () => { expect(availableTerritories([{ data: [ta('AAA', true), ta('BBB', false)] }, { data: [ta('CCC', true)] }])).toEqual(['AAA', 'CCC']); }); test('same set and flag: no difference', () => { expect(planAvailability({ availableInNewTerritories: true, available: ['A', 'B'] }, ['A', 'B']).differs).toBe(false); }); test('missing territories are additions', () => { const p = planAvailability({ availableInNewTerritories: true, available: ['A'] }, ['A', 'B', 'C']); expect(p).toMatchObject({ add: ['B', 'C'], remove: [], differs: true, from: '1 territories', to: '3 territories' }); }); test('extra territories are removals; flag off is a change', () => { const p = planAvailability({ availableInNewTerritories: false, available: ['A', 'X'] }, ['A']); expect(p.remove).toEqual(['X']); expect(p.newFlagChange).toBe(true); }); test('blockedItems', () => { expect(blockedItems({ priceDiffers: true, removes: true, allowPrice: false, allowAvailability: false })).toEqual(['price', 'availability removal']); expect(blockedItems({ priceDiffers: true, removes: true, allowPrice: true, allowAvailability: true })).toEqual([]); expect(blockedItems({ priceDiffers: false, removes: false, allowPrice: false, allowAvailability: false })).toEqual([]); }); }); describe('reviewDetailDiff', () => { const want = { contactPhone: '+15550001111', contactEmail: 'someone@example.com', demoAccountRequired: true, demoAccountName: 'demo-user', demoAccountPassword: 'hunter2', notes: 'Hello' }; test('lists only changed keys and masks secrets', () => { const lines = reviewDetailDiff({ ...want, demoAccountPassword: 'old-secret', notes: 'Hi' }, want); expect(lines).toHaveLength(2); expect(lines.join('\n')).not.toContain('hunter2'); expect(lines.join('\n')).not.toContain('old-secret'); expect(lines[0]).toBe('demoAccountPassword: ******** -> ********'); expect(lines[1]).toBe('notes: 2 bytes -> 5 bytes'); }); test('nothing changed gives no lines', () => { expect(reviewDetailDiff({ ...want }, want)).toEqual([]); }); test('new detail lists everything, phone and email masked', () => { const lines = reviewDetailDiff(null, want); expect(lines).toHaveLength(6); expect(lines.join('\n')).not.toContain('5550001111'); expect(lines.join('\n')).not.toContain('someone@example.com'); }); });