import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' import { CLIENT_ID_SETUP_URL, CLIENT_SECRET_SETUP_URL, DEFAULT_CLIENT_ID_SECRET, DEFAULT_CLIENT_SECRET_SECRET, buildInvoiceBody, buildPayoutBody, clientIdSetupUrl, clientSecretSetupUrl, moneyValue, mutationPreview, requireConfirm, resolveClientIdSecretName, resolveClientSecretSecretName, toPayPalDate, } from './paypal-helpers.ts' const here = dirname(fileURLToPath(import.meta.url)) const root = join(here, '..') test('secret names default and suffix accounts', () => { assert.equal(resolveClientIdSecretName(), DEFAULT_CLIENT_ID_SECRET) assert.equal(resolveClientSecretSecretName(), DEFAULT_CLIENT_SECRET_SECRET) assert.equal(resolveClientIdSecretName({ account: 'default' }), DEFAULT_CLIENT_ID_SECRET) assert.equal(resolveClientIdSecretName({ account: 'work' }), 'paypalClientId-work') assert.equal(resolveClientSecretSecretName({ account: 'work' }), 'paypalClientSecret-work') assert.equal( resolveClientIdSecretName({ clientIdSecret: 'paypalClientId-live' }), 'paypalClientId-live', ) }) test('secret names reject Kent-style aliases', () => { assert.throws( () => resolveClientIdSecretName({ clientIdSecret: 'PAYPAL_CLIENT_ID' }), /paypalClientId/, ) assert.throws( () => resolveClientSecretSecretName({ clientSecretSecret: 'paypalSecret' }), /paypalClientSecret/, ) }) test('setup URLs are prefilled for both secrets', () => { assert.match(CLIENT_ID_SETUP_URL, /name=paypalClientId/) assert.match(CLIENT_ID_SETUP_URL, /allowedHosts=api-m\.paypal\.com%2Capi-m\.sandbox\.paypal\.com/) assert.match(CLIENT_ID_SETUP_URL, /scope=user/) assert.match(CLIENT_SECRET_SETUP_URL, /name=paypalClientSecret/) assert.match(clientIdSetupUrl('paypalClientId-work'), /name=paypalClientId-work/) assert.match(clientSecretSetupUrl('paypalClientSecret-work'), /name=paypalClientSecret-work/) }) test('requireConfirm blocks live mutations', () => { assert.throws(() => requireConfirm({}, 'send invoice INV2-1'), /confirm: true/) requireConfirm({ confirm: true }, 'send invoice INV2-1') }) test('mutationPreview dryRun skips confirm', () => { const preview = mutationPreview( { dryRun: true }, { action: 'send invoice INV2-1', method: 'POST', path: '/v2/invoicing/invoices/INV2-1/send', body: { send_to_recipient: true }, requireConfirm: true, }, ) assert.deepEqual(preview, { dryRun: true, action: 'send invoice INV2-1', method: 'POST', path: '/v2/invoicing/invoices/INV2-1/send', body: { send_to_recipient: true }, }) }) test('mutationPreview requires confirm when not dry-run', () => { assert.throws( () => mutationPreview( {}, { action: 'create payout batch', method: 'POST', path: '/v1/payments/payouts', requireConfirm: true, }, ), /confirm: true/, ) assert.equal( mutationPreview( { confirm: true }, { action: 'create payout batch', method: 'POST', path: '/v1/payments/payouts', requireConfirm: true, }, ), null, ) }) test('invoice body is generic and caller-supplied', () => { const body = buildInvoiceBody({ recipientEmail: 'ada@example.com', itemName: 'Consulting', amount: 42, memo: 'March invoice', }) assert.equal(body.primary_recipients[0]?.billing_info.email_address, 'ada@example.com') assert.equal(body.items[0]?.name, 'Consulting') assert.equal(body.items[0]?.unit_amount.value, '42.00') assert.equal(body.detail.currency_code, 'USD') const source = JSON.stringify(body) assert.equal(source.includes('kentcdodds'), false) assert.equal(source.includes('me@'), false) }) test('payout body is generic and caller-supplied', () => { const body = buildPayoutBody({ receiver: 'ada@example.com', amount: '25.5', note: 'Conference travel', }) assert.equal(body.items[0]?.receiver, 'ada@example.com') assert.equal((body.items[0]?.amount as { value: string }).value, '25.50') assert.equal(body.sender_batch_header.recipient_type, 'EMAIL') const source = JSON.stringify(body) assert.equal(source.includes('kentcdodds'), false) }) test('moneyValue rejects negatives', () => { assert.equal(moneyValue('10'), '10.00') assert.throws(() => moneyValue(-1), /non-negative/) }) test('toPayPalDate formats ISO without millis', () => { assert.equal(toPayPalDate('2026-08-01T00:00:00.123Z'), '2026-08-01T00:00:00Z') }) test('package metadata is official and public', () => { const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) assert.equal(manifest.name, '@kody/paypal') assert.equal(manifest.license, 'MIT') assert.notEqual(manifest.private, true) assert.equal(manifest.kody.id, 'paypal') assert.equal(manifest.kody.jobs['reimbursement-scan'].enabled, false) const readme = readFileSync(join(root, 'README.md'), 'utf8') assert.match(readme, /^## Intent$/m) assert.match(readme, /https:\/\/kody\.codes\/@kody\/paypal/) assert.match(readme, /account\/secrets\/new/) assert.equal(readme.includes('kentcdodds.com'), false) assert.equal(readme.includes('me@kentcdodds'), false) })