morphit/apps/web/scripts/chat-shippable-gating-smoke.ts

107 lines
5 KiB
TypeScript

#!/usr/bin/env tsx
/**
* chat-shippable-gating-smoke (cp406).
*
* Two things this locks, both easy to get subtly wrong:
*
* 1. WHICH payment methods are "shippable" — i.e. move a physical thing that
* can be posted, so a trade using them unlocks the in-chat "Share mailing
* address" + "Record shipment" controls. Per Ken: barter goods, precious
* metals (handed over in person OR shipped) and cash-by-mail (the cash
* envelope is posted). Cash-in-person is physical but face-to-face only →
* NOT shippable. Everything electronic/on-chain is not physical at all.
*
* 2. The ship/mail DIRECTION invariant. The physical item IS the payment for
* the crypto, so whoever RECEIVES the crypto is the one paying physically →
* they SHIP it ("Record shipment"); whoever SENDS the crypto is receiving
* that physical payment → they say where to send it ("Share mailing
* address"). Getting this backwards shows grandma the wrong button. The
* helper below mirrors ConversationView's two deriveds exactly
* (showRecordShipmentButton = orderCanShip && showShareAddressButton;
* showShareMailingButton = orderCanShip && showPayNowButton).
*
* Usage:
* cd apps/web && npx tsx --tsconfig ../../tsconfig.smoke.json scripts/chat-shippable-gating-smoke.ts
*/
import { orderUsesShippableMethod } from '../src/lib/payments/registry.ts';
let failures = 0;
let count = 0;
function check(name: string, cond: boolean, detail = ''): void {
count++;
if (cond) console.log(`${name}`);
else {
failures++;
console.log(`${name}${detail ? `${detail}` : ''}`);
}
}
// Mirror of ConversationView's gating (the crypto-direction booleans feed in).
function chatShippingButtons(
paymentMethods: readonly string[],
iAmCryptoReceiver: boolean
): { showRecordShipment: boolean; showShareMailing: boolean } {
const canShip = orderUsesShippableMethod(paymentMethods);
return {
showRecordShipment: canShip && iAmCryptoReceiver, // I receive crypto → I pay physically → I ship
showShareMailing: canShip && !iAmCryptoReceiver // I send crypto → I receive the physical payment
};
}
// ── 1. shippable set ──
check('1 — barter_goods is shippable', orderUsesShippableMethod(['barter_goods']));
check('2 — precious_metals is shippable', orderUsesShippableMethod(['precious_metals']));
check('3 — cash_by_mail is shippable', orderUsesShippableMethod(['cash_by_mail']));
check('4 — cash_in_person is NOT shippable', !orderUsesShippableMethod(['cash_in_person']));
check('5 — a crypto rail (pay_xmr) is NOT shippable', !orderUsesShippableMethod(['pay_xmr']));
check('6 — an online method (paypal) is NOT shippable', !orderUsesShippableMethod(['paypal']));
check(
'7 — mixed methods: any shippable ⇒ shippable',
orderUsesShippableMethod(['cash_in_person', 'barter_goods'])
);
check(
'8 — mixed non-shippable ⇒ not shippable',
!orderUsesShippableMethod(['cash_in_person', 'paypal', 'pay_btc'])
);
check('9 — empty methods ⇒ not shippable', !orderUsesShippableMethod([]));
check('10 — unknown key ignored ⇒ not shippable', !orderUsesShippableMethod(['not_a_real_method']));
// ── 2. direction invariant — Ken's Kenya-baskets barter ──
// Amara: BUY order for XMR, pays with baskets (barter_goods). She RECEIVES the
// crypto → she ships the baskets.
{
const amara = chatShippingButtons(['barter_goods'], /* iAmCryptoReceiver */ true);
check('11 — barter crypto-RECEIVER (Amara) shows Record shipment', amara.showRecordShipment);
check('12 — barter crypto-RECEIVER (Amara) does NOT show Share mailing', !amara.showShareMailing);
}
// Bob: SELLS the XMR to Amara. He SENDS the crypto → he receives the baskets →
// he shares where to ship them.
{
const bob = chatShippingButtons(['barter_goods'], /* iAmCryptoReceiver */ false);
check('13 — barter crypto-SENDER (Bob) shows Share mailing', bob.showShareMailing);
check('14 — barter crypto-SENDER (Bob) does NOT show Record shipment', !bob.showRecordShipment);
}
// Same invariant for cash-by-mail (crypto buyer mails the cash → ships it).
{
const buyerMailsCash = chatShippingButtons(['cash_by_mail'], true);
check('15 — cash-by-mail crypto-RECEIVER ships the cash envelope', buyerMailsCash.showRecordShipment);
const sellerGetsCash = chatShippingButtons(['cash_by_mail'], false);
check('16 — cash-by-mail crypto-SENDER shares where to mail the cash', sellerGetsCash.showShareMailing);
}
// Cash-in-person: neither control, either side (Ken's kentest3 scenario).
{
const buyer = chatShippingButtons(['cash_in_person'], true);
const seller = chatShippingButtons(['cash_in_person'], false);
check(
'17 — cash-in-person shows neither shipping control (either party)',
!buyer.showRecordShipment && !buyer.showShareMailing && !seller.showRecordShipment && !seller.showShareMailing
);
}
console.log(`\n${count} scenarios, ${failures} failed`);
if (failures > 0) {
console.error('chat-shippable-gating-smoke FAILED');
process.exit(1);
}
console.log(`✓ all ${count} chat-shippable-gating scenarios passed`);