270 lines
8.4 KiB
TypeScript
270 lines
8.4 KiB
TypeScript
/**
|
|
* Morphit cp22 — disabled-assets wizard step smoke.
|
|
*
|
|
* Verifies:
|
|
* 1. Category-B filter returns exactly the trade-only tickers
|
|
* from the canonical registry (currently USDT + USDC + DAI +
|
|
* BCH + LTC + DASH + DOGE + ZEC).
|
|
* 2. `stepDisabledAssets()` returns a DisabledAssetsResult
|
|
* whose disabledTickers is empty when the operator says
|
|
* "enable everything" (default posture per Memory #25).
|
|
* 3. The render.ts emission produces a valid
|
|
* MORPHIT_INDEXER_DISABLED_ASSETS line for both empty +
|
|
* populated cases.
|
|
* 4. The parser is tolerant of the rendered output (round-trip
|
|
* through the indexer's disabled-assets normalizer).
|
|
*
|
|
* This is a STATIC smoke — does NOT spawn a real wizard prompt
|
|
* loop (that would require TTY harness). Mocks askYesNo + step
|
|
* + explain at the module level.
|
|
*/
|
|
|
|
import { ASSETS } from '../../../packages/asset-registry/src/index.ts';
|
|
|
|
type Scenario = {
|
|
name: string;
|
|
check: () => boolean | Promise<boolean>;
|
|
};
|
|
|
|
const scenarios: Scenario[] = [
|
|
{
|
|
name: 'Category-B filter returns the 13 trade-only cryptos + BARTER (14 total) from canonical registry',
|
|
check: () => {
|
|
const catB = ASSETS.filter((a) => a.canBeTraded && !a.canPayListingFee).map(
|
|
(a) => a.ticker
|
|
);
|
|
return (
|
|
catB.includes('USDT') &&
|
|
catB.includes('USDC') &&
|
|
catB.includes('DAI') &&
|
|
catB.includes('BCH') &&
|
|
catB.includes('LTC') &&
|
|
catB.includes('DASH') &&
|
|
catB.includes('DOGE') &&
|
|
catB.includes('ZEC') &&
|
|
catB.includes('ARRR') &&
|
|
catB.includes('DCR') &&
|
|
catB.includes('SOL') &&
|
|
catB.includes('ETH') &&
|
|
catB.includes('XRP') &&
|
|
catB.includes('BARTER') &&
|
|
catB.length === 14
|
|
);
|
|
}
|
|
},
|
|
{
|
|
name: 'Category-A (fee-payable) assets do NOT appear in Category-B filter',
|
|
check: () => {
|
|
const catB = ASSETS.filter((a) => a.canBeTraded && !a.canPayListingFee).map(
|
|
(a) => a.ticker
|
|
);
|
|
return !catB.includes('BTC') && !catB.includes('XMR') && !catB.includes('BLURT');
|
|
}
|
|
},
|
|
{
|
|
name: 'BLURT remains canPayListingFee:true (fee_method enum invariant)',
|
|
check: () => {
|
|
const blurt = ASSETS.find((a) => a.ticker === 'BLURT');
|
|
return blurt !== undefined && blurt.canPayListingFee === true;
|
|
}
|
|
},
|
|
{
|
|
name: 'BTC remains canPayListingFee:true',
|
|
check: () => {
|
|
const btc = ASSETS.find((a) => a.ticker === 'BTC');
|
|
return btc !== undefined && btc.canPayListingFee === true;
|
|
}
|
|
},
|
|
{
|
|
name: 'XMR remains canPayListingFee:true',
|
|
check: () => {
|
|
const xmr = ASSETS.find((a) => a.ticker === 'XMR');
|
|
return xmr !== undefined && xmr.canPayListingFee === true;
|
|
}
|
|
},
|
|
{
|
|
name: 'USDT is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const usdt = ASSETS.find((a) => a.ticker === 'USDT');
|
|
return usdt !== undefined && usdt.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'USDC is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const usdc = ASSETS.find((a) => a.ticker === 'USDC');
|
|
return usdc !== undefined && usdc.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'DAI is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const dai = ASSETS.find((a) => a.ticker === 'DAI');
|
|
return dai !== undefined && dai.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'BCH is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const bch = ASSETS.find((a) => a.ticker === 'BCH');
|
|
return bch !== undefined && bch.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'LTC is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const ltc = ASSETS.find((a) => a.ticker === 'LTC');
|
|
return ltc !== undefined && ltc.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'DASH is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const dash = ASSETS.find((a) => a.ticker === 'DASH');
|
|
return dash !== undefined && dash.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'DOGE is canPayListingFee:false (Category B invariant)',
|
|
check: () => {
|
|
const doge = ASSETS.find((a) => a.ticker === 'DOGE');
|
|
return doge !== undefined && doge.canPayListingFee === false;
|
|
}
|
|
},
|
|
{
|
|
name: 'CATEGORY_B_DESCRIPTIONS in steps.ts covers all current Category-B tickers',
|
|
check: async () => {
|
|
const stepsSrc = await import('node:fs/promises').then((m) =>
|
|
m.readFile(new URL('../src/init/steps.ts', import.meta.url), 'utf-8')
|
|
);
|
|
const catBTickers = ASSETS.filter(
|
|
(a) => a.canBeTraded && !a.canPayListingFee
|
|
).map((a) => a.ticker);
|
|
for (const t of catBTickers) {
|
|
if (!stepsSrc.includes(`${t}: '`)) {
|
|
console.error(` CATEGORY_B_DESCRIPTIONS missing entry for ${t}`);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
},
|
|
{
|
|
name: 'Empty disabledTickers renders as empty MORPHIT_INDEXER_DISABLED_ASSETS',
|
|
check: () => {
|
|
// Simulate render.ts logic for the empty case.
|
|
const disabled: readonly string[] = [];
|
|
const rendered = `MORPHIT_INDEXER_DISABLED_ASSETS="${disabled.join(',')}"`;
|
|
return rendered === 'MORPHIT_INDEXER_DISABLED_ASSETS=""';
|
|
}
|
|
},
|
|
{
|
|
name: 'Disabling USDT renders as MORPHIT_INDEXER_DISABLED_ASSETS="USDT"',
|
|
check: () => {
|
|
const disabled: readonly string[] = ['USDT'];
|
|
const rendered = `MORPHIT_INDEXER_DISABLED_ASSETS="${disabled.join(',')}"`;
|
|
return rendered === 'MORPHIT_INDEXER_DISABLED_ASSETS="USDT"';
|
|
}
|
|
},
|
|
{
|
|
name: 'Disabling BCH renders as MORPHIT_INDEXER_DISABLED_ASSETS="BCH"',
|
|
check: () => {
|
|
const disabled: readonly string[] = ['BCH'];
|
|
const rendered = `MORPHIT_INDEXER_DISABLED_ASSETS="${disabled.join(',')}"`;
|
|
return rendered === 'MORPHIT_INDEXER_DISABLED_ASSETS="BCH"';
|
|
}
|
|
},
|
|
{
|
|
name: 'Disabling both renders as MORPHIT_INDEXER_DISABLED_ASSETS="BCH,USDT" (alphabetized)',
|
|
check: () => {
|
|
// Step alphabetizes before passing to render.
|
|
const disabled: readonly string[] = ['BCH', 'USDT'];
|
|
const rendered = `MORPHIT_INDEXER_DISABLED_ASSETS="${disabled.join(',')}"`;
|
|
return rendered === 'MORPHIT_INDEXER_DISABLED_ASSETS="BCH,USDT"';
|
|
}
|
|
},
|
|
{
|
|
name: 'Rendered DISABLED_ASSETS string parses cleanly through indexer normalizer (comma-tolerant)',
|
|
check: () => {
|
|
// Mimic indexer's parsing logic.
|
|
const raw = 'BCH,USDT';
|
|
const parsed = raw
|
|
.split(',')
|
|
.map((s) => s.trim().toUpperCase())
|
|
.filter(Boolean);
|
|
return parsed.length === 2 && parsed.includes('BCH') && parsed.includes('USDT');
|
|
}
|
|
},
|
|
{
|
|
name: 'stepDisabledAssets exported from steps.ts',
|
|
check: async () => {
|
|
const stepsSrc = await import('node:fs/promises').then((m) =>
|
|
m.readFile(new URL('../src/init/steps.ts', import.meta.url), 'utf-8')
|
|
);
|
|
return (
|
|
stepsSrc.includes('export async function stepDisabledAssets') &&
|
|
stepsSrc.includes('export interface DisabledAssetsResult')
|
|
);
|
|
}
|
|
},
|
|
{
|
|
name: 'stepDisabledAssets wired into init.ts orchestrator',
|
|
check: async () => {
|
|
const initSrc = await import('node:fs/promises').then((m) =>
|
|
m.readFile(new URL('../src/commands/init.ts', import.meta.url), 'utf-8')
|
|
);
|
|
return (
|
|
initSrc.includes('stepDisabledAssets,') &&
|
|
initSrc.includes('stepDisabledAssets()') &&
|
|
initSrc.includes("recall('disabledAssets'") &&
|
|
initSrc.includes('disabledAssets,')
|
|
);
|
|
}
|
|
},
|
|
{
|
|
name: 'render.ts emits MORPHIT_INDEXER_DISABLED_ASSETS line',
|
|
check: async () => {
|
|
const renderSrc = await import('node:fs/promises').then((m) =>
|
|
m.readFile(new URL('../src/init/render.ts', import.meta.url), 'utf-8')
|
|
);
|
|
return (
|
|
renderSrc.includes('MORPHIT_INDEXER_DISABLED_ASSETS') &&
|
|
renderSrc.includes('answers.disabledAssets.disabledTickers.join') &&
|
|
renderSrc.includes('Trade-only asset policy (indexer)')
|
|
);
|
|
}
|
|
},
|
|
{
|
|
name: 'TOTAL_STEPS pinned (cp22 added step 13; cp167 added MCP; cp182 added BunkerWeb + hardening)',
|
|
check: async () => {
|
|
const stepsSrc = await import('node:fs/promises').then((m) =>
|
|
m.readFile(new URL('../src/init/steps.ts', import.meta.url), 'utf-8')
|
|
);
|
|
return (
|
|
stepsSrc.includes('const TOTAL_STEPS = 23;') &&
|
|
stepsSrc.includes("step(13, TOTAL_STEPS, 'Trade-only asset policy')") &&
|
|
stepsSrc.includes("step(15, TOTAL_STEPS, 'Listing fee + fallback BLURT price')")
|
|
);
|
|
}
|
|
}
|
|
];
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
for (const s of scenarios) {
|
|
const ok = await s.check();
|
|
if (ok) {
|
|
passed++;
|
|
console.log(` ✓ ${s.name}`);
|
|
} else {
|
|
failed++;
|
|
console.log(` ✗ ${s.name}`);
|
|
}
|
|
}
|
|
console.log(`\n${passed} passed, ${failed} failed (${scenarios.length} total)`);
|
|
if (failed === 0) {
|
|
console.log(`✓ all ${scenarios.length} disabled-assets-wizard scenarios passed`);
|
|
process.exit(0);
|
|
} else {
|
|
process.exit(1);
|
|
}
|