@@ -1367,4 +1367,166 @@ describe('Validations', () => {
13671367 expect ( result ) . to . equal ( true ) ;
13681368 } ) ;
13691369 } ) ;
1370+
1371+ describe ( 'checkMarketOrderSatsLimits (market price estimate)' , ( ) => {
1372+ let axiosGet : any ;
1373+ let checkMarketOrderSatsLimits : any ;
1374+
1375+ beforeEach ( ( ) => {
1376+ sandbox . restore ( ) ;
1377+ sandbox = sinon . createSandbox ( ) ;
1378+ sandbox . stub ( process , 'env' ) . value ( {
1379+ MIN_PAYMENT_AMT : 100 ,
1380+ MAX_PAYMENT_AMT : 5000 ,
1381+ FIAT_RATE_EP : 'http://fake-oracle' ,
1382+ NODE_ENV : 'test' ,
1383+ } ) ;
1384+ axiosGet = sinon . stub ( ) ;
1385+ // 1 BTC = 1e8 fiat => estimated sats == fiat amount
1386+ axiosGet . resolves ( { data : { btc : 1e8 } } ) ;
1387+ checkMarketOrderSatsLimits = proxyquire ( '../../util' , {
1388+ axios : { default : { get : axiosGet } , __esModule : true } ,
1389+ } ) . checkMarketOrderSatsLimits ;
1390+ } ) ;
1391+
1392+ it ( 'rejects (below_min) when estimated sats are under MIN' , async ( ) => {
1393+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 50 ] , 0 ) ;
1394+ expect ( res . status ) . to . equal ( 'below_min' ) ;
1395+ expect ( res . limit ) . to . equal ( 100 ) ;
1396+ } ) ;
1397+
1398+ it ( 'accepts (ok) when estimated sats are within limits' , async ( ) => {
1399+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 100 ] , 0 ) ;
1400+ expect ( res . status ) . to . equal ( 'ok' ) ;
1401+ } ) ;
1402+
1403+ it ( 'rejects (above_max) when estimated sats exceed MAX' , async ( ) => {
1404+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 6000 ] , 0 ) ;
1405+ expect ( res . status ) . to . equal ( 'above_max' ) ;
1406+ expect ( res . limit ) . to . equal ( 5000 ) ;
1407+ } ) ;
1408+
1409+ it ( 'on ranges checks the lower bound against MIN' , async ( ) => {
1410+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 50 , 200 ] , 0 ) ;
1411+ expect ( res . status ) . to . equal ( 'below_min' ) ;
1412+ } ) ;
1413+
1414+ it ( 'on ranges checks the upper bound against MAX' , async ( ) => {
1415+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 200 , 6000 ] , 0 ) ;
1416+ expect ( res . status ) . to . equal ( 'above_max' ) ;
1417+ } ) ;
1418+
1419+ it ( 'applies the price margin to the estimate' , async ( ) => {
1420+ // 200 fiat -> 200 sats, with +60% premium -> floor(80) = 80 < MIN(100)
1421+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 200 ] , 60 ) ;
1422+ expect ( res . status ) . to . equal ( 'below_min' ) ;
1423+ } ) ;
1424+
1425+ it ( 'returns price_unavailable when the oracle reports an error' , async ( ) => {
1426+ axiosGet . reset ( ) ;
1427+ axiosGet . resolves ( { data : { error : true } } ) ;
1428+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 100 ] , 0 ) ;
1429+ expect ( res . status ) . to . equal ( 'price_unavailable' ) ;
1430+ } ) ;
1431+
1432+ it ( 'returns price_unavailable when the oracle request throws' , async ( ) => {
1433+ axiosGet . reset ( ) ;
1434+ axiosGet . rejects ( new Error ( 'network down' ) ) ;
1435+ const res = await checkMarketOrderSatsLimits ( 'USD' , [ 100 ] , 0 ) ;
1436+ expect ( res . status ) . to . equal ( 'price_unavailable' ) ;
1437+ } ) ;
1438+ } ) ;
1439+
1440+ describe ( 'validateSellOrder (market price sats estimate)' , ( ) => {
1441+ const loadWith = ( checkStub : any ) =>
1442+ proxyquire ( '../../bot/validations' , {
1443+ '../util' : { checkMarketOrderSatsLimits : checkStub } ,
1444+ } ) . validateSellOrder ;
1445+
1446+ beforeEach ( ( ) => {
1447+ sandbox . restore ( ) ;
1448+ sandbox = sinon . createSandbox ( ) ;
1449+ sandbox . stub ( process , 'env' ) . value ( {
1450+ MIN_PAYMENT_AMT : 100 ,
1451+ MAX_PAYMENT_AMT : 5000 ,
1452+ NODE_ENV : 'production' ,
1453+ INVOICE_EXPIRATION_WINDOW : 3600000 ,
1454+ } ) ;
1455+ } ) ;
1456+
1457+ it ( 'rejects when the estimate is below the minimum' , async ( ) => {
1458+ const checkStub = sinon
1459+ . stub ( )
1460+ . resolves ( { status : 'below_min' , limit : 100 } ) ;
1461+ const validate = loadWith ( checkStub ) ;
1462+ ctx . state . command . args = [ '0' , '50' , 'USD' , 'zelle' ] ;
1463+ const result = await validate ( ctx ) ;
1464+ expect ( result ) . to . equal ( false ) ;
1465+ expect ( checkStub . calledOnce ) . to . equal ( true ) ;
1466+ } ) ;
1467+
1468+ it ( 'rejects when the estimate exceeds the maximum' , async ( ) => {
1469+ const checkStub = sinon
1470+ . stub ( )
1471+ . resolves ( { status : 'above_max' , limit : 5000 } ) ;
1472+ const validate = loadWith ( checkStub ) ;
1473+ ctx . state . command . args = [ '0' , '100000' , 'USD' , 'zelle' ] ;
1474+ const result = await validate ( ctx ) ;
1475+ expect ( result ) . to . equal ( false ) ;
1476+ } ) ;
1477+
1478+ it ( 'accepts when the estimate is within limits' , async ( ) => {
1479+ const checkStub = sinon . stub ( ) . resolves ( { status : 'ok' } ) ;
1480+ const validate = loadWith ( checkStub ) ;
1481+ ctx . state . command . args = [ '0' , '100' , 'USD' , 'zelle' ] ;
1482+ const result = await validate ( ctx ) ;
1483+ expect ( result ) . to . be . an ( 'object' ) ;
1484+ } ) ;
1485+
1486+ it ( 'accepts (pass-through) when the price oracle is unavailable' , async ( ) => {
1487+ const checkStub = sinon . stub ( ) . resolves ( { status : 'price_unavailable' } ) ;
1488+ const validate = loadWith ( checkStub ) ;
1489+ ctx . state . command . args = [ '0' , '100' , 'USD' , 'zelle' ] ;
1490+ const result = await validate ( ctx ) ;
1491+ expect ( result ) . to . be . an ( 'object' ) ;
1492+ expect ( checkStub . calledOnce ) . to . equal ( true ) ;
1493+ } ) ;
1494+ } ) ;
1495+
1496+ describe ( 'validateBuyOrder (market price sats estimate)' , ( ) => {
1497+ const loadWith = ( checkStub : any ) =>
1498+ proxyquire ( '../../bot/validations' , {
1499+ '../util' : { checkMarketOrderSatsLimits : checkStub } ,
1500+ } ) . validateBuyOrder ;
1501+
1502+ beforeEach ( ( ) => {
1503+ sandbox . restore ( ) ;
1504+ sandbox = sinon . createSandbox ( ) ;
1505+ sandbox . stub ( process , 'env' ) . value ( {
1506+ MIN_PAYMENT_AMT : 100 ,
1507+ MAX_PAYMENT_AMT : 5000 ,
1508+ NODE_ENV : 'production' ,
1509+ INVOICE_EXPIRATION_WINDOW : 3600000 ,
1510+ } ) ;
1511+ } ) ;
1512+
1513+ it ( 'rejects when the estimate is below the minimum' , async ( ) => {
1514+ const checkStub = sinon
1515+ . stub ( )
1516+ . resolves ( { status : 'below_min' , limit : 100 } ) ;
1517+ const validate = loadWith ( checkStub ) ;
1518+ ctx . state . command . args = [ '0' , '50' , 'USD' , 'zelle' ] ;
1519+ const result = await validate ( ctx ) ;
1520+ expect ( result ) . to . equal ( false ) ;
1521+ expect ( checkStub . calledOnce ) . to . equal ( true ) ;
1522+ } ) ;
1523+
1524+ it ( 'accepts when the estimate is within limits' , async ( ) => {
1525+ const checkStub = sinon . stub ( ) . resolves ( { status : 'ok' } ) ;
1526+ const validate = loadWith ( checkStub ) ;
1527+ ctx . state . command . args = [ '0' , '100' , 'USD' , 'zelle' ] ;
1528+ const result = await validate ( ctx ) ;
1529+ expect ( result ) . to . be . an ( 'object' ) ;
1530+ } ) ;
1531+ } ) ;
13701532} ) ;
0 commit comments