From fa27c1d506118ffc0c27031962237061911d6f5c Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Mon, 16 Feb 2026 00:47:59 -0300 Subject: [PATCH 1/8] Removed "COMPLETED_BY_ADMIN" status on orders and added a new 'settled_by_admin' field denoting that, and modified the 'getDetailedOrder' function output to show if an order was completed by an admin or not --- bot/commands.ts | 10 +++------- bot/modules/block/commands.ts | 1 - bot/start.ts | 20 ++++++++------------ locales/de.yaml | 2 ++ locales/en.yaml | 2 ++ locales/es.yaml | 2 ++ locales/fa.yaml | 2 ++ locales/fr.yaml | 2 ++ locales/it.yaml | 2 ++ locales/ko.yaml | 2 ++ locales/pt.yaml | 2 ++ locales/ru.yaml | 2 ++ locales/uk.yaml | 2 ++ models/order.ts | 3 ++- util/index.ts | 3 +++ 15 files changed, 36 insertions(+), 21 deletions(-) diff --git a/bot/commands.ts b/bot/commands.ts index 617d45fd..d56374d8 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -598,13 +598,9 @@ const addInvoicePHI = async ( ctx.deleteMessage(); const order = await Order.findOne({ _id: orderId }); if (order === null) throw new Error('order was not found'); - // orders with status PAID_HOLD_INVOICE or COMPLETED_BY_ADMIN are released payments - if ( - order.status !== 'PAID_HOLD_INVOICE' && - order.status !== 'COMPLETED_BY_ADMIN' - ) { - return; - } + // only orders with status PAID_HOLD_INVOICE are released payments + if (order.status !== 'PAID_HOLD_INVOICE') return; + const buyer = await User.findOne({ _id: order.buyer_id }); if (buyer === null) return; diff --git a/bot/modules/block/commands.ts b/bot/modules/block/commands.ts index c0040e74..836129ed 100644 --- a/bot/modules/block/commands.ts +++ b/bot/modules/block/commands.ts @@ -23,7 +23,6 @@ const block = async (ctx: MainContext, username: string): Promise => { 'CLOSED', 'CANCELED_BY_ADMIN', 'EXPIRED', - 'COMPLETED_BY_ADMIN', 'SUCCESS', 'PAID_HOLD_INVOICE', 'CANCELED', diff --git a/bot/start.ts b/bot/start.ts index 34ca46d4..9aaec599 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -133,16 +133,10 @@ const askForConfirmation = async (user: UserDocument, command: string) => { orders = await Order.find(where); } else if (command === '/setinvoice') { const where: FilterQuery = { - $and: [ - { buyer_id: user._id }, - { - $or: [ - { status: 'PAID_HOLD_INVOICE' }, - { status: 'COMPLETED_BY_ADMIN' }, - ], - }, - ], + buyer_id: user._id, + status: 'PAID_HOLD_INVOICE', }; + orders = await Order.find(where); } @@ -550,15 +544,17 @@ const initialize = ( } } - if (order.secret) await settleHoldInvoice({ secret: order.secret }); + if (order.secret) { + order.settled_by_admin = true; + await order.save(); + await settleHoldInvoice({ secret: order.secret }); + } if (dispute) { dispute.status = 'SETTLED'; await dispute.save(); } - order.status = 'COMPLETED_BY_ADMIN'; - await order.save(); const buyer = await User.findOne({ _id: order.buyer_id }); const seller = await User.findOne({ _id: order.seller_id }); if (buyer === null || seller === null) diff --git a/locales/de.yaml b/locales/de.yaml index 21dcdf99..b2c05c2c 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -178,6 +178,8 @@ order_detail: | Status: ${status} + Von Admin abgeschlossen: ${settledByAdmin} + Ersteller: @${creator || ''} Käufer: @${buyerUsername || ''} diff --git a/locales/en.yaml b/locales/en.yaml index c50b4b1d..e3668f04 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -182,6 +182,8 @@ order_detail: | Status: ${status} + Settled by admin: ${settledByAdmin} + Creator: @${creator || ''} Buyer: @${buyerUsername || ''} diff --git a/locales/es.yaml b/locales/es.yaml index 91fc0ce0..84ce230e 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -178,6 +178,8 @@ order_detail: | Status: ${status} + Completada por admin: ${settledByAdmin} + Creador: @${creator || ''} Comprador: @${buyerUsername || ''} diff --git a/locales/fa.yaml b/locales/fa.yaml index cc194949..cfbec56e 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -180,6 +180,8 @@ order_detail: | Status: ${status} + Settled by admin: ${settledByAdmin} + Creator: @${creator || ''} Buyer: @${buyerUsername || ''} diff --git a/locales/fr.yaml b/locales/fr.yaml index 633615bb..1152da7a 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -180,6 +180,8 @@ order_detail: | Statut : ${status} + Complété par l'administrateur : ${settledByAdmin} + Créateur : @${creator || ''} Acheteur : @${buyerUsername || ''} diff --git a/locales/it.yaml b/locales/it.yaml index b7ecf69f..adcb3bb2 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -178,6 +178,8 @@ order_detail: | Stato: ${status} + Completato dall'amministratore: ${settledByAdmin} + Creato da: @${creator || ''} Acquirente: @${buyerUsername || ''} diff --git a/locales/ko.yaml b/locales/ko.yaml index 5eae3886..b9a3706b 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -179,6 +179,8 @@ order_detail: | 상태: ${status} + 관리자에 의해 완료됨: ${settledByAdmin} + 생성자: @${creator || ''} 구매자: @${buyerUsername || ''} diff --git a/locales/pt.yaml b/locales/pt.yaml index a9a0f2e9..14a6a69b 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -179,6 +179,8 @@ order_detail: | Status: ${status} + Completado por admin: ${settledByAdmin} + Criadora: @${creator || ''} Compradora: @${buyerUsername || ''} diff --git a/locales/ru.yaml b/locales/ru.yaml index 0be6cd09..0c873fe7 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -177,6 +177,8 @@ order_detail: | Статус: ${status} + Завершено администратором: ${settledByAdmin} + Создатель: @${creator || ''} Покупатель: @${buyerUsername || ''} diff --git a/locales/uk.yaml b/locales/uk.yaml index 3451bccd..c773f568 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -177,6 +177,8 @@ order_detail: | Статус: ${status} + Завершено адміністратором: ${settledByAdmin} + Автор: @${creator || ''} Покупець: @${buyerUsername || ''} diff --git a/models/order.ts b/models/order.ts index 01e99e40..a267f45e 100644 --- a/models/order.ts +++ b/models/order.ts @@ -47,6 +47,7 @@ export interface IOrder extends Document { is_public: boolean; random_image: string; is_golden_honey_badger?: boolean; + settled_by_admin?: boolean; } const orderSchema = new Schema>({ @@ -118,10 +119,10 @@ const orderSchema = new Schema>({ 'PAID_HOLD_INVOICE', // seller released funds 'CANCELED_BY_ADMIN', 'EXPIRED', // Expired orders, stated changed by a job - 'COMPLETED_BY_ADMIN', 'FROZEN', ], }, + settled_by_admin: { type: Boolean, default: false }, type: { type: String }, fiat_amount: { type: Number, min: 1 }, // amount in fiat fiat_code: { type: String }, diff --git a/util/index.ts b/util/index.ts index 3d546729..12ccd0cd 100644 --- a/util/index.ts +++ b/util/index.ts @@ -402,6 +402,8 @@ const getDetailedOrder = ( const sellerAge = seller ? getUserAge(seller) : ''; const buyerTrades = buyer ? buyer.trades_completed : 0; const sellerTrades = seller ? seller.trades_completed : 0; + const settledByAdmin = order.settled_by_admin ? i18n.t('yes') : i18n.t('no'); + const message = i18n.t('order_detail', { order, creator, @@ -420,6 +422,7 @@ const getDetailedOrder = ( sellerAge, buyerTrades, sellerTrades, + settledByAdmin, }); return message; From a8c9520f788947593783a25d8094e4af9f853c46 Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Mon, 16 Feb 2026 00:49:48 -0300 Subject: [PATCH 2/8] Code formatting --- bot/commands.ts | 1 - util/index.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/commands.ts b/bot/commands.ts index d56374d8..86ea392c 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -600,7 +600,6 @@ const addInvoicePHI = async ( if (order === null) throw new Error('order was not found'); // only orders with status PAID_HOLD_INVOICE are released payments if (order.status !== 'PAID_HOLD_INVOICE') return; - const buyer = await User.findOne({ _id: order.buyer_id }); if (buyer === null) return; diff --git a/util/index.ts b/util/index.ts index 12ccd0cd..952e85b7 100644 --- a/util/index.ts +++ b/util/index.ts @@ -402,7 +402,9 @@ const getDetailedOrder = ( const sellerAge = seller ? getUserAge(seller) : ''; const buyerTrades = buyer ? buyer.trades_completed : 0; const sellerTrades = seller ? seller.trades_completed : 0; - const settledByAdmin = order.settled_by_admin ? i18n.t('yes') : i18n.t('no'); + const settledByAdmin = order.settled_by_admin + ? i18n.t('yes') + : i18n.t('no'); const message = i18n.t('order_detail', { order, From 7e9a1177816c18c1bc117a8cd868d7958db291a8 Mon Sep 17 00:00:00 2001 From: lucas Date: Fri, 13 Mar 2026 20:37:37 -0300 Subject: [PATCH 3/8] Added database migration for orders in status COMPLETED_BY_ADMIN and correcting order of operations in bot/start.ts due to a potential failure while settling the hold invoice --- bot/start.ts | 2 +- scripts/migrate_completed_by_admin_orders.ts | 39 ++++++++++++++++++++ tsconfig.json | 15 ++++++-- 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 scripts/migrate_completed_by_admin_orders.ts diff --git a/bot/start.ts b/bot/start.ts index 9aaec599..5c8ab43d 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -545,9 +545,9 @@ const initialize = ( } if (order.secret) { + await settleHoldInvoice({ secret: order.secret }); order.settled_by_admin = true; await order.save(); - await settleHoldInvoice({ secret: order.secret }); } if (dispute) { diff --git a/scripts/migrate_completed_by_admin_orders.ts b/scripts/migrate_completed_by_admin_orders.ts new file mode 100644 index 00000000..ad011d02 --- /dev/null +++ b/scripts/migrate_completed_by_admin_orders.ts @@ -0,0 +1,39 @@ +import 'dotenv/config'; +import { connect as mongoConnect } from '../db_connect'; +import Order from '../models/order'; +import { logger } from '../logger'; + +const migrate = async () => { + try { + const mongoose = mongoConnect(); + await new Promise((resolve, reject) => { + mongoose.connection.once('open', resolve); + mongoose.connection.on('error', reject); + }); + + logger.info('Connected to MongoDB for migration.'); + + const query = { status: 'COMPLETED_BY_ADMIN' }; + const update = { + $set: { + status: 'PAID_HOLD_INVOICE', + settled_by_admin: true, + }, + }; + + const result = await Order.updateMany(query, update); + + logger.info(`Migration completed.`); + logger.info(`Matched: ${result.matchedCount} orders.`); + logger.info(`Modified: ${result.modifiedCount} orders.`); + + await mongoose.connection.close(); + logger.info('Database connection closed.'); + process.exit(0); + } catch (error) { + logger.error(`Migration failed: ${error}`); + process.exit(1); + } +}; + +migrate(); diff --git a/tsconfig.json b/tsconfig.json index 63ff67a6..f1492da5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,10 @@ "esModuleInterop": true, "resolveJsonModule": true, "downlevelIteration": true, - "lib":["ES2021", "DOM"], + "lib": [ + "ES2021", + "DOM" + ], "outDir": "./dist", "rootDir": ".", "moduleResolution": "node" @@ -18,6 +21,12 @@ "models/**/*", "util/**/*", "locales/**/*", + "scripts/**/*" ], - "exclude": ["node_modules", "dist", "tests", "locales"] -} + "exclude": [ + "node_modules", + "dist", + "tests", + "locales" + ] +} \ No newline at end of file From 80e8adacb6b1bd1e13109929d73c360ff63133d8 Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 16 Mar 2026 21:05:00 -0300 Subject: [PATCH 4/8] test: add tests for admin settle and block queries --- tests/bot/bot.spec.ts | 103 +++++++++++++++++- tests/bot/modules/block.spec.ts | 98 +++++++++++++++++ .../migrate_completed_by_admin_orders.spec.ts | 81 ++++++++++++++ tests/util/index.spec.ts | 59 ++++++++++ 4 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 tests/bot/modules/block.spec.ts create mode 100644 tests/scripts/migrate_completed_by_admin_orders.spec.ts diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index e6ce9838..b879170c 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -255,9 +255,9 @@ describe('Bot Initialization', () => { '@grammyjs/i18n': { I18n: sinon .stub() - .returns({ middleware: sinon.stub().returns(() => {}) }), + .returns({ middleware: sinon.stub().returns(() => { }) }), }, - '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => {}) }, + '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => { }) }, '../models': { Order: { findOne: sinon.stub().resolves(null), @@ -431,8 +431,8 @@ describe('Bot Initialization', () => { userMiddleware: sinon.stub().resolves(), adminMiddleware: sinon.stub().resolves(), superAdminMiddleware: sinon.stub().resolves(), - commandArgsMiddleware: sinon.stub().returns(() => {}), - stageMiddleware: sinon.stub().returns(() => {}), + commandArgsMiddleware: sinon.stub().returns(() => { }), + stageMiddleware: sinon.stub().returns(() => { }), }, '../logger': { error: sinon.stub(), @@ -609,4 +609,99 @@ describe('Bot Initialization', () => { ctx.reply.calledWithExactly('This is an unknown command.'), ).to.be.equal(true); }); + + it('should set settled_by_admin when admin settles with secret', async () => { + const orderMock = { + _id: 'orderId', + status: 'DISPUTE', + secret: 'secret', + community_id: null, + buyer_id: 'buyer', + seller_id: 'seller', + save: sinon.stub().resolves() + } as any; + + const OrderFindOneStub = sinon.stub().resolves(orderMock); + const settleHoldInvoiceStub = sinon.stub().resolves(); + + const startModule = proxyquire('../../bot/start', { + telegraf: { Telegraf: sinon.stub().returns(botStub) }, + '../models': { + Order: { findOne: OrderFindOneStub }, + User: { findOne: sinon.stub().resolves({ id: 'user' }) }, + Dispute: { findOne: sinon.stub().resolves(null) } + }, + './validations': { + validateParams: sinon.stub().resolves(['orderId']), + validateObjectId: sinon.stub().resolves(true) + }, + '../ln': { settleHoldInvoice: settleHoldInvoiceStub }, + './messages': { successCompleteOrderMessage: sinon.stub().resolves(), successCompleteOrderByAdminMessage: sinon.stub().resolves() } + }); + + startModule.initialize('dummy-token', {}); + const settleOrderCall = botStub.command.getCalls().find((c: any) => c.args[0] === 'settleorder'); + const handler = settleOrderCall.args[2]; + + const ctx = { + admin: { admin: true }, + match: ['/settleorder orderId', 'orderId'], + reply: sinon.stub().resolves(), + i18n: { t: sinon.stub().returns('Success') }, + telegram: { sendMessage: sinon.stub().resolves() } + }; + + await handler(ctx); + + expect(settleHoldInvoiceStub.calledWith({ secret: 'secret' })).to.be.equal(true); + expect(orderMock.settled_by_admin).to.be.equal(true); + expect(orderMock.save.called).to.be.equal(true); + }); + + it('should not modify order if settleHoldInvoice fails', async () => { + const orderMock = { + _id: 'orderId', + status: 'DISPUTE', + secret: 'invalidsecret', + community_id: null, + buyer_id: 'buyer', + seller_id: 'seller', + save: sinon.stub().resolves() + } as any; + + const OrderFindOneStub = sinon.stub().resolves(orderMock); + const settleHoldInvoiceStub = sinon.stub().rejects(new Error('LND failed')); + + const startModule = proxyquire('../../bot/start', { + telegraf: { Telegraf: sinon.stub().returns(botStub) }, + '../models': { + Order: { findOne: OrderFindOneStub }, + User: { findOne: sinon.stub().resolves({ id: 'user' }) }, + Dispute: { findOne: sinon.stub().resolves(null) } + }, + './validations': { + validateParams: sinon.stub().resolves(['orderId']), + validateObjectId: sinon.stub().resolves(true) + }, + '../ln': { settleHoldInvoice: settleHoldInvoiceStub }, + './messages': { successCompleteOrderMessage: sinon.stub().resolves(), successCompleteOrderByAdminMessage: sinon.stub().resolves() } + }); + + startModule.initialize('dummy-token', {}); + const settleOrderCall = botStub.command.getCalls().find((c: any) => c.args[0] === 'settleorder'); + const handler = settleOrderCall.args[2]; + + const ctx = { + admin: { admin: true }, + match: ['/settleorder orderId', 'orderId'], + reply: sinon.stub().resolves(), + i18n: { t: sinon.stub().returns('Success') } + }; + + await handler(ctx); + + expect(orderMock.save.called).to.be.equal(false); + expect(orderMock.settled_by_admin).to.be.undefined; + }); }); + diff --git a/tests/bot/modules/block.spec.ts b/tests/bot/modules/block.spec.ts new file mode 100644 index 00000000..65e2095f --- /dev/null +++ b/tests/bot/modules/block.spec.ts @@ -0,0 +1,98 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); + +describe('Block Module block query', () => { + let sandbox: any; + let orderExistsStub: any; + let blockExistsStub: any; + let blockSaveStub: any; + let userFindOneStub: any; + let blockCmd: any; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + orderExistsStub = sandbox.stub(); + blockExistsStub = sandbox.stub(); + blockSaveStub = sandbox.stub().resolves(); + + userFindOneStub = sandbox.stub().resolves({ + id: '2', + tg_id: 2, + username: 'badguy' + }); + + const blockModule = proxyquire('../../../bot/modules/block/commands', { + '../../../models': { + Order: { exists: orderExistsStub }, + Block: { + exists: blockExistsStub, + }, + User: { findOne: userFindOneStub } + }, + './messages': { + ordersInProcess: sandbox.stub().resolves(), + userAlreadyBlocked: sandbox.stub().resolves(), + userBlocked: sandbox.stub().resolves(), + blocklistEmptyMessage: sandbox.stub().resolves(), + blocklistMessage: sandbox.stub().resolves(), + }, + '../../messages': { + notFoundUserMessage: sandbox.stub().resolves(), + } + }); + + // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. + // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. + blockCmd = blockModule.block; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should exclude settled orders from pending count when blocking', async () => { + const ctx = { + user: { + id: '1', + tg_id: 1, + username: 'goodguy' + } + }; + + orderExistsStub.resolves(false); + blockExistsStub.resolves(false); + + // We stub Block constructor for the `new Block` call at the end + const BlockMock = function (this: any) { + this.save = blockSaveStub; + }; + BlockMock.exists = blockExistsStub; + + const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { + '../../../models': { + Order: { exists: orderExistsStub }, + Block: BlockMock, + User: { findOne: userFindOneStub } + }, + './messages': { + ordersInProcess: sandbox.stub().resolves(), + userBlocked: sandbox.stub().resolves(), + } + }); + + await blockModuleFixed.block(ctx, '@badguy'); + + expect(orderExistsStub.calledOnce).to.be.true; + + const queryArgs = orderExistsStub.firstCall.args[0]; + + // The review requires that we verify the query excludes settled orders from pending count + // The query excludes these statuses using $nin. + // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. + expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); + expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); + }); +}); +export { }; diff --git a/tests/scripts/migrate_completed_by_admin_orders.spec.ts b/tests/scripts/migrate_completed_by_admin_orders.spec.ts new file mode 100644 index 00000000..d639fa12 --- /dev/null +++ b/tests/scripts/migrate_completed_by_admin_orders.spec.ts @@ -0,0 +1,81 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); + +describe('Migration Script: migrate_completed_by_admin_orders', () => { + let sandbox: any; + let updateManyStub: any; + let exitStub: any; + let infoStub: any; + let errorStub: any; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + updateManyStub = sandbox.stub().resolves({ + matchedCount: 2, + modifiedCount: 2, + }); + + // Mock logger + infoStub = sandbox.stub(); + errorStub = sandbox.stub(); + + // Mock process.exit + exitStub = sandbox.stub(process, 'exit'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { + // We proxyquire the script to inject mocks + proxyquire('../../scripts/migrate_completed_by_admin_orders', { + '../db_connect': { + connect: sandbox.stub().returns({ + connection: { + once: sandbox.stub().callsFake((event: any, cb: any) => { + if (event === 'open') cb(); + }), + on: sandbox.stub(), + close: sandbox.stub().resolves(), + } + }) + }, + '../models/order': { + default: { + updateMany: updateManyStub + } + }, + '../logger': { + logger: { + info: infoStub, + error: errorStub + } + } + }); + + // We need to wait a tick for the async immediately-invoked function to resolve + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(updateManyStub.calledOnce).to.be.true; + + const [query, update] = updateManyStub.firstCall.args; + + // Verify query + expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); + + // Verify update + expect(update).to.deep.equal({ + $set: { + status: 'PAID_HOLD_INVOICE', + settled_by_admin: true, + }, + }); + + expect(exitStub.calledWith(0)).to.be.true; + }); +}); + +export { }; diff --git a/tests/util/index.spec.ts b/tests/util/index.spec.ts index 7df03a23..78ceff47 100644 --- a/tests/util/index.spec.ts +++ b/tests/util/index.spec.ts @@ -6,6 +6,7 @@ import { plural, isFloat, toKebabCase, + getDetailedOrder, } from '../../util/index'; const { expect } = require('chai'); @@ -136,4 +137,62 @@ describe('Utility Functions', () => { expect(toKebabCase('hello_world')).to.equal('hello-world'); }); }); + + describe('getDetailedOrder', () => { + it('should show "Settled by admin: Yes" when settled_by_admin = true', async () => { + const i18n = { + t: (key: string, props?: any) => { + if (key === 'yes') return 'Yes'; + if (key === 'no') return 'No'; + if (key === 'no_community') return 'None'; + if (key === 'order_detail') return `Settled by admin: ${props.settledByAdmin}`; + return key; + } + } as any; + + const order = { + _id: '123', + created_at: new Date(), + status: 'PAID_HOLD_INVOICE', + settled_by_admin: true, + payment_method: 'bank', + price_margin: 0, + fee: 0, + } as any; + + const buyer = null; + const seller = null; + + const result = await getDetailedOrder(i18n, order, buyer, seller); + expect(result).to.equal('Settled by admin: Yes'); + }); + + it('should show "Settled by admin: No" when settled_by_admin = false', async () => { + const i18n = { + t: (key: string, props?: any) => { + if (key === 'yes') return 'Yes'; + if (key === 'no') return 'No'; + if (key === 'no_community') return 'None'; + if (key === 'order_detail') return `Settled by admin: ${props.settledByAdmin}`; + return key; + } + } as any; + + const order = { + _id: '123', + created_at: new Date(), + status: 'SUCCESS', + settled_by_admin: false, + payment_method: 'bank', + price_margin: 0, + fee: 0, + } as any; + + const buyer = null; + const seller = null; + + const result = await getDetailedOrder(i18n, order, buyer, seller); + expect(result).to.equal('Settled by admin: No'); + }); + }); }); From 3412a5272d3640cf17cb817491ea6d056a9ded43 Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 16 Mar 2026 21:08:39 -0300 Subject: [PATCH 5/8] Code formatting --- tests/bot/bot.spec.ts | 47 ++++-- tests/bot/modules/block.spec.ts | 156 +++++++++--------- .../migrate_completed_by_admin_orders.spec.ts | 124 +++++++------- tests/util/index.spec.ts | 10 +- 4 files changed, 175 insertions(+), 162 deletions(-) diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index b879170c..ccf83100 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -255,9 +255,9 @@ describe('Bot Initialization', () => { '@grammyjs/i18n': { I18n: sinon .stub() - .returns({ middleware: sinon.stub().returns(() => { }) }), + .returns({ middleware: sinon.stub().returns(() => {}) }), }, - '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => { }) }, + '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => {}) }, '../models': { Order: { findOne: sinon.stub().resolves(null), @@ -431,8 +431,8 @@ describe('Bot Initialization', () => { userMiddleware: sinon.stub().resolves(), adminMiddleware: sinon.stub().resolves(), superAdminMiddleware: sinon.stub().resolves(), - commandArgsMiddleware: sinon.stub().returns(() => { }), - stageMiddleware: sinon.stub().returns(() => { }), + commandArgsMiddleware: sinon.stub().returns(() => {}), + stageMiddleware: sinon.stub().returns(() => {}), }, '../logger': { error: sinon.stub(), @@ -618,7 +618,7 @@ describe('Bot Initialization', () => { community_id: null, buyer_id: 'buyer', seller_id: 'seller', - save: sinon.stub().resolves() + save: sinon.stub().resolves(), } as any; const OrderFindOneStub = sinon.stub().resolves(orderMock); @@ -629,18 +629,23 @@ describe('Bot Initialization', () => { '../models': { Order: { findOne: OrderFindOneStub }, User: { findOne: sinon.stub().resolves({ id: 'user' }) }, - Dispute: { findOne: sinon.stub().resolves(null) } + Dispute: { findOne: sinon.stub().resolves(null) }, }, './validations': { validateParams: sinon.stub().resolves(['orderId']), - validateObjectId: sinon.stub().resolves(true) + validateObjectId: sinon.stub().resolves(true), }, '../ln': { settleHoldInvoice: settleHoldInvoiceStub }, - './messages': { successCompleteOrderMessage: sinon.stub().resolves(), successCompleteOrderByAdminMessage: sinon.stub().resolves() } + './messages': { + successCompleteOrderMessage: sinon.stub().resolves(), + successCompleteOrderByAdminMessage: sinon.stub().resolves(), + }, }); startModule.initialize('dummy-token', {}); - const settleOrderCall = botStub.command.getCalls().find((c: any) => c.args[0] === 'settleorder'); + const settleOrderCall = botStub.command + .getCalls() + .find((c: any) => c.args[0] === 'settleorder'); const handler = settleOrderCall.args[2]; const ctx = { @@ -648,12 +653,14 @@ describe('Bot Initialization', () => { match: ['/settleorder orderId', 'orderId'], reply: sinon.stub().resolves(), i18n: { t: sinon.stub().returns('Success') }, - telegram: { sendMessage: sinon.stub().resolves() } + telegram: { sendMessage: sinon.stub().resolves() }, }; await handler(ctx); - expect(settleHoldInvoiceStub.calledWith({ secret: 'secret' })).to.be.equal(true); + expect(settleHoldInvoiceStub.calledWith({ secret: 'secret' })).to.be.equal( + true, + ); expect(orderMock.settled_by_admin).to.be.equal(true); expect(orderMock.save.called).to.be.equal(true); }); @@ -666,7 +673,7 @@ describe('Bot Initialization', () => { community_id: null, buyer_id: 'buyer', seller_id: 'seller', - save: sinon.stub().resolves() + save: sinon.stub().resolves(), } as any; const OrderFindOneStub = sinon.stub().resolves(orderMock); @@ -677,25 +684,30 @@ describe('Bot Initialization', () => { '../models': { Order: { findOne: OrderFindOneStub }, User: { findOne: sinon.stub().resolves({ id: 'user' }) }, - Dispute: { findOne: sinon.stub().resolves(null) } + Dispute: { findOne: sinon.stub().resolves(null) }, }, './validations': { validateParams: sinon.stub().resolves(['orderId']), - validateObjectId: sinon.stub().resolves(true) + validateObjectId: sinon.stub().resolves(true), }, '../ln': { settleHoldInvoice: settleHoldInvoiceStub }, - './messages': { successCompleteOrderMessage: sinon.stub().resolves(), successCompleteOrderByAdminMessage: sinon.stub().resolves() } + './messages': { + successCompleteOrderMessage: sinon.stub().resolves(), + successCompleteOrderByAdminMessage: sinon.stub().resolves(), + }, }); startModule.initialize('dummy-token', {}); - const settleOrderCall = botStub.command.getCalls().find((c: any) => c.args[0] === 'settleorder'); + const settleOrderCall = botStub.command + .getCalls() + .find((c: any) => c.args[0] === 'settleorder'); const handler = settleOrderCall.args[2]; const ctx = { admin: { admin: true }, match: ['/settleorder orderId', 'orderId'], reply: sinon.stub().resolves(), - i18n: { t: sinon.stub().returns('Success') } + i18n: { t: sinon.stub().returns('Success') }, }; await handler(ctx); @@ -704,4 +716,3 @@ describe('Bot Initialization', () => { expect(orderMock.settled_by_admin).to.be.undefined; }); }); - diff --git a/tests/bot/modules/block.spec.ts b/tests/bot/modules/block.spec.ts index 65e2095f..d5d268a9 100644 --- a/tests/bot/modules/block.spec.ts +++ b/tests/bot/modules/block.spec.ts @@ -3,96 +3,96 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); describe('Block Module block query', () => { - let sandbox: any; - let orderExistsStub: any; - let blockExistsStub: any; - let blockSaveStub: any; - let userFindOneStub: any; - let blockCmd: any; + let sandbox: any; + let orderExistsStub: any; + let blockExistsStub: any; + let blockSaveStub: any; + let userFindOneStub: any; + let blockCmd: any; - beforeEach(() => { - sandbox = sinon.createSandbox(); + beforeEach(() => { + sandbox = sinon.createSandbox(); - orderExistsStub = sandbox.stub(); - blockExistsStub = sandbox.stub(); - blockSaveStub = sandbox.stub().resolves(); + orderExistsStub = sandbox.stub(); + blockExistsStub = sandbox.stub(); + blockSaveStub = sandbox.stub().resolves(); - userFindOneStub = sandbox.stub().resolves({ - id: '2', - tg_id: 2, - username: 'badguy' - }); - - const blockModule = proxyquire('../../../bot/modules/block/commands', { - '../../../models': { - Order: { exists: orderExistsStub }, - Block: { - exists: blockExistsStub, - }, - User: { findOne: userFindOneStub } - }, - './messages': { - ordersInProcess: sandbox.stub().resolves(), - userAlreadyBlocked: sandbox.stub().resolves(), - userBlocked: sandbox.stub().resolves(), - blocklistEmptyMessage: sandbox.stub().resolves(), - blocklistMessage: sandbox.stub().resolves(), - }, - '../../messages': { - notFoundUserMessage: sandbox.stub().resolves(), - } - }); - - // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. - // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. - blockCmd = blockModule.block; + userFindOneStub = sandbox.stub().resolves({ + id: '2', + tg_id: 2, + username: 'badguy', }); - afterEach(() => { - sandbox.restore(); + const blockModule = proxyquire('../../../bot/modules/block/commands', { + '../../../models': { + Order: { exists: orderExistsStub }, + Block: { + exists: blockExistsStub, + }, + User: { findOne: userFindOneStub }, + }, + './messages': { + ordersInProcess: sandbox.stub().resolves(), + userAlreadyBlocked: sandbox.stub().resolves(), + userBlocked: sandbox.stub().resolves(), + blocklistEmptyMessage: sandbox.stub().resolves(), + blocklistMessage: sandbox.stub().resolves(), + }, + '../../messages': { + notFoundUserMessage: sandbox.stub().resolves(), + }, }); - it('should exclude settled orders from pending count when blocking', async () => { - const ctx = { - user: { - id: '1', - tg_id: 1, - username: 'goodguy' - } - }; + // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. + // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. + blockCmd = blockModule.block; + }); + + afterEach(() => { + sandbox.restore(); + }); - orderExistsStub.resolves(false); - blockExistsStub.resolves(false); + it('should exclude settled orders from pending count when blocking', async () => { + const ctx = { + user: { + id: '1', + tg_id: 1, + username: 'goodguy', + }, + }; - // We stub Block constructor for the `new Block` call at the end - const BlockMock = function (this: any) { - this.save = blockSaveStub; - }; - BlockMock.exists = blockExistsStub; + orderExistsStub.resolves(false); + blockExistsStub.resolves(false); - const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { - '../../../models': { - Order: { exists: orderExistsStub }, - Block: BlockMock, - User: { findOne: userFindOneStub } - }, - './messages': { - ordersInProcess: sandbox.stub().resolves(), - userBlocked: sandbox.stub().resolves(), - } - }); + // We stub Block constructor for the `new Block` call at the end + const BlockMock = function (this: any) { + this.save = blockSaveStub; + }; + BlockMock.exists = blockExistsStub; - await blockModuleFixed.block(ctx, '@badguy'); + const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { + '../../../models': { + Order: { exists: orderExistsStub }, + Block: BlockMock, + User: { findOne: userFindOneStub }, + }, + './messages': { + ordersInProcess: sandbox.stub().resolves(), + userBlocked: sandbox.stub().resolves(), + }, + }); - expect(orderExistsStub.calledOnce).to.be.true; + await blockModuleFixed.block(ctx, '@badguy'); - const queryArgs = orderExistsStub.firstCall.args[0]; + expect(orderExistsStub.calledOnce).to.be.true; - // The review requires that we verify the query excludes settled orders from pending count - // The query excludes these statuses using $nin. - // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. - expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); - expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); - }); + const queryArgs = orderExistsStub.firstCall.args[0]; + + // The review requires that we verify the query excludes settled orders from pending count + // The query excludes these statuses using $nin. + // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. + expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); + expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); + }); }); -export { }; +export {}; diff --git a/tests/scripts/migrate_completed_by_admin_orders.spec.ts b/tests/scripts/migrate_completed_by_admin_orders.spec.ts index d639fa12..bb04bc2a 100644 --- a/tests/scripts/migrate_completed_by_admin_orders.spec.ts +++ b/tests/scripts/migrate_completed_by_admin_orders.spec.ts @@ -3,79 +3,79 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); describe('Migration Script: migrate_completed_by_admin_orders', () => { - let sandbox: any; - let updateManyStub: any; - let exitStub: any; - let infoStub: any; - let errorStub: any; + let sandbox: any; + let updateManyStub: any; + let exitStub: any; + let infoStub: any; + let errorStub: any; - beforeEach(() => { - sandbox = sinon.createSandbox(); + beforeEach(() => { + sandbox = sinon.createSandbox(); - updateManyStub = sandbox.stub().resolves({ - matchedCount: 2, - modifiedCount: 2, - }); - - // Mock logger - infoStub = sandbox.stub(); - errorStub = sandbox.stub(); - - // Mock process.exit - exitStub = sandbox.stub(process, 'exit'); + updateManyStub = sandbox.stub().resolves({ + matchedCount: 2, + modifiedCount: 2, }); - afterEach(() => { - sandbox.restore(); - }); + // Mock logger + infoStub = sandbox.stub(); + errorStub = sandbox.stub(); - it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { - // We proxyquire the script to inject mocks - proxyquire('../../scripts/migrate_completed_by_admin_orders', { - '../db_connect': { - connect: sandbox.stub().returns({ - connection: { - once: sandbox.stub().callsFake((event: any, cb: any) => { - if (event === 'open') cb(); - }), - on: sandbox.stub(), - close: sandbox.stub().resolves(), - } - }) - }, - '../models/order': { - default: { - updateMany: updateManyStub - } - }, - '../logger': { - logger: { - info: infoStub, - error: errorStub - } - } - }); + // Mock process.exit + exitStub = sandbox.stub(process, 'exit'); + }); - // We need to wait a tick for the async immediately-invoked function to resolve - await new Promise(resolve => setTimeout(resolve, 10)); + afterEach(() => { + sandbox.restore(); + }); - expect(updateManyStub.calledOnce).to.be.true; + it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { + // We proxyquire the script to inject mocks + proxyquire('../../scripts/migrate_completed_by_admin_orders', { + '../db_connect': { + connect: sandbox.stub().returns({ + connection: { + once: sandbox.stub().callsFake((event: any, cb: any) => { + if (event === 'open') cb(); + }), + on: sandbox.stub(), + close: sandbox.stub().resolves(), + }, + }), + }, + '../models/order': { + default: { + updateMany: updateManyStub, + }, + }, + '../logger': { + logger: { + info: infoStub, + error: errorStub, + }, + }, + }); + + // We need to wait a tick for the async immediately-invoked function to resolve + await new Promise(resolve => setTimeout(resolve, 10)); - const [query, update] = updateManyStub.firstCall.args; + expect(updateManyStub.calledOnce).to.be.true; - // Verify query - expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); + const [query, update] = updateManyStub.firstCall.args; - // Verify update - expect(update).to.deep.equal({ - $set: { - status: 'PAID_HOLD_INVOICE', - settled_by_admin: true, - }, - }); + // Verify query + expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); - expect(exitStub.calledWith(0)).to.be.true; + // Verify update + expect(update).to.deep.equal({ + $set: { + status: 'PAID_HOLD_INVOICE', + settled_by_admin: true, + }, }); + + expect(exitStub.calledWith(0)).to.be.true; + }); }); -export { }; +export {}; diff --git a/tests/util/index.spec.ts b/tests/util/index.spec.ts index 78ceff47..09ba4cde 100644 --- a/tests/util/index.spec.ts +++ b/tests/util/index.spec.ts @@ -145,9 +145,10 @@ describe('Utility Functions', () => { if (key === 'yes') return 'Yes'; if (key === 'no') return 'No'; if (key === 'no_community') return 'None'; - if (key === 'order_detail') return `Settled by admin: ${props.settledByAdmin}`; + if (key === 'order_detail') + return `Settled by admin: ${props.settledByAdmin}`; return key; - } + }, } as any; const order = { @@ -173,9 +174,10 @@ describe('Utility Functions', () => { if (key === 'yes') return 'Yes'; if (key === 'no') return 'No'; if (key === 'no_community') return 'None'; - if (key === 'order_detail') return `Settled by admin: ${props.settledByAdmin}`; + if (key === 'order_detail') + return `Settled by admin: ${props.settledByAdmin}`; return key; - } + }, } as any; const order = { From d804ffdaecf0e68273e6f482428a23ff6772f906 Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 16 Mar 2026 21:11:50 -0300 Subject: [PATCH 6/8] style: fix lint errors in tests --- tests/bot/bot.spec.ts | 10 +- tests/bot/modules/block.spec.ts | 134 ++++++++---------- .../migrate_completed_by_admin_orders.spec.ts | 124 ++++++++-------- 3 files changed, 123 insertions(+), 145 deletions(-) diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index ccf83100..e9d8dcab 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -255,9 +255,9 @@ describe('Bot Initialization', () => { '@grammyjs/i18n': { I18n: sinon .stub() - .returns({ middleware: sinon.stub().returns(() => {}) }), + .returns({ middleware: sinon.stub().returns(() => { }) }), }, - '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => {}) }, + '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => { }) }, '../models': { Order: { findOne: sinon.stub().resolves(null), @@ -431,8 +431,8 @@ describe('Bot Initialization', () => { userMiddleware: sinon.stub().resolves(), adminMiddleware: sinon.stub().resolves(), superAdminMiddleware: sinon.stub().resolves(), - commandArgsMiddleware: sinon.stub().returns(() => {}), - stageMiddleware: sinon.stub().returns(() => {}), + commandArgsMiddleware: sinon.stub().returns(() => { }), + stageMiddleware: sinon.stub().returns(() => { }), }, '../logger': { error: sinon.stub(), @@ -713,6 +713,6 @@ describe('Bot Initialization', () => { await handler(ctx); expect(orderMock.save.called).to.be.equal(false); - expect(orderMock.settled_by_admin).to.be.undefined; + expect(orderMock.settled_by_admin).to.equal(undefined); }); }); diff --git a/tests/bot/modules/block.spec.ts b/tests/bot/modules/block.spec.ts index d5d268a9..8728ad07 100644 --- a/tests/bot/modules/block.spec.ts +++ b/tests/bot/modules/block.spec.ts @@ -3,96 +3,74 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); describe('Block Module block query', () => { - let sandbox: any; - let orderExistsStub: any; - let blockExistsStub: any; - let blockSaveStub: any; - let userFindOneStub: any; - let blockCmd: any; + let sandbox: any; + let orderExistsStub: any; + let blockExistsStub: any; + let blockSaveStub: any; + let userFindOneStub: any; - beforeEach(() => { - sandbox = sinon.createSandbox(); + beforeEach(() => { + sandbox = sinon.createSandbox(); - orderExistsStub = sandbox.stub(); - blockExistsStub = sandbox.stub(); - blockSaveStub = sandbox.stub().resolves(); + orderExistsStub = sandbox.stub(); + blockExistsStub = sandbox.stub(); + blockSaveStub = sandbox.stub().resolves(); - userFindOneStub = sandbox.stub().resolves({ - id: '2', - tg_id: 2, - username: 'badguy', - }); + userFindOneStub = sandbox.stub().resolves({ + id: '2', + tg_id: 2, + username: 'badguy', + }); - const blockModule = proxyquire('../../../bot/modules/block/commands', { - '../../../models': { - Order: { exists: orderExistsStub }, - Block: { - exists: blockExistsStub, - }, - User: { findOne: userFindOneStub }, - }, - './messages': { - ordersInProcess: sandbox.stub().resolves(), - userAlreadyBlocked: sandbox.stub().resolves(), - userBlocked: sandbox.stub().resolves(), - blocklistEmptyMessage: sandbox.stub().resolves(), - blocklistMessage: sandbox.stub().resolves(), - }, - '../../messages': { - notFoundUserMessage: sandbox.stub().resolves(), - }, + // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. + // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. }); - // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. - // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. - blockCmd = blockModule.block; - }); + afterEach(() => { + sandbox.restore(); + }); - afterEach(() => { - sandbox.restore(); - }); + it('should exclude settled orders from pending count when blocking', async () => { + const ctx = { + user: { + id: '1', + tg_id: 1, + username: 'goodguy', + }, + }; - it('should exclude settled orders from pending count when blocking', async () => { - const ctx = { - user: { - id: '1', - tg_id: 1, - username: 'goodguy', - }, - }; + orderExistsStub.resolves(false); + blockExistsStub.resolves(false); - orderExistsStub.resolves(false); - blockExistsStub.resolves(false); + // We stub Block constructor for the `new Block` call at the end + const BlockMock = function (this: any) { + this.save = blockSaveStub; + }; + BlockMock.exists = blockExistsStub; - // We stub Block constructor for the `new Block` call at the end - const BlockMock = function (this: any) { - this.save = blockSaveStub; - }; - BlockMock.exists = blockExistsStub; + const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { + '../../../models': { + Order: { exists: orderExistsStub }, + Block: BlockMock, + User: { findOne: userFindOneStub }, + }, + './messages': { + ordersInProcess: sandbox.stub().resolves(), + userBlocked: sandbox.stub().resolves(), + }, + }); - const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { - '../../../models': { - Order: { exists: orderExistsStub }, - Block: BlockMock, - User: { findOne: userFindOneStub }, - }, - './messages': { - ordersInProcess: sandbox.stub().resolves(), - userBlocked: sandbox.stub().resolves(), - }, - }); + await blockModuleFixed.block(ctx, '@badguy'); - await blockModuleFixed.block(ctx, '@badguy'); + expect(orderExistsStub.calledOnce).to.equal(true); - expect(orderExistsStub.calledOnce).to.be.true; + const queryArgs = orderExistsStub.firstCall.args[0]; - const queryArgs = orderExistsStub.firstCall.args[0]; - - // The review requires that we verify the query excludes settled orders from pending count - // The query excludes these statuses using $nin. - // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. - expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); - expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); - }); + // The review requires that we verify the query excludes settled orders from pending count + // The query excludes these statuses using $nin. + // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. + expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); + expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); + }); }); -export {}; +export { }; diff --git a/tests/scripts/migrate_completed_by_admin_orders.spec.ts b/tests/scripts/migrate_completed_by_admin_orders.spec.ts index bb04bc2a..77a52a31 100644 --- a/tests/scripts/migrate_completed_by_admin_orders.spec.ts +++ b/tests/scripts/migrate_completed_by_admin_orders.spec.ts @@ -3,79 +3,79 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); describe('Migration Script: migrate_completed_by_admin_orders', () => { - let sandbox: any; - let updateManyStub: any; - let exitStub: any; - let infoStub: any; - let errorStub: any; + let sandbox: any; + let updateManyStub: any; + let exitStub: any; + let infoStub: any; + let errorStub: any; - beforeEach(() => { - sandbox = sinon.createSandbox(); + beforeEach(() => { + sandbox = sinon.createSandbox(); - updateManyStub = sandbox.stub().resolves({ - matchedCount: 2, - modifiedCount: 2, - }); - - // Mock logger - infoStub = sandbox.stub(); - errorStub = sandbox.stub(); + updateManyStub = sandbox.stub().resolves({ + matchedCount: 2, + modifiedCount: 2, + }); - // Mock process.exit - exitStub = sandbox.stub(process, 'exit'); - }); + // Mock logger + infoStub = sandbox.stub(); + errorStub = sandbox.stub(); - afterEach(() => { - sandbox.restore(); - }); + // Mock process.exit + exitStub = sandbox.stub(process, 'exit'); + }); - it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { - // We proxyquire the script to inject mocks - proxyquire('../../scripts/migrate_completed_by_admin_orders', { - '../db_connect': { - connect: sandbox.stub().returns({ - connection: { - once: sandbox.stub().callsFake((event: any, cb: any) => { - if (event === 'open') cb(); - }), - on: sandbox.stub(), - close: sandbox.stub().resolves(), - }, - }), - }, - '../models/order': { - default: { - updateMany: updateManyStub, - }, - }, - '../logger': { - logger: { - info: infoStub, - error: errorStub, - }, - }, + afterEach(() => { + sandbox.restore(); }); - // We need to wait a tick for the async immediately-invoked function to resolve - await new Promise(resolve => setTimeout(resolve, 10)); + it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { + // We proxyquire the script to inject mocks + proxyquire('../../scripts/migrate_completed_by_admin_orders', { + '../db_connect': { + connect: sandbox.stub().returns({ + connection: { + once: sandbox.stub().callsFake((event: any, cb: any) => { + if (event === 'open') cb(); + }), + on: sandbox.stub(), + close: sandbox.stub().resolves(), + }, + }), + }, + '../models/order': { + default: { + updateMany: updateManyStub, + }, + }, + '../logger': { + logger: { + info: infoStub, + error: errorStub, + }, + }, + }); - expect(updateManyStub.calledOnce).to.be.true; + // We need to wait a tick for the async immediately-invoked function to resolve + await new Promise(resolve => setTimeout(resolve, 10)); - const [query, update] = updateManyStub.firstCall.args; + expect(updateManyStub.calledOnce).to.equal(true); - // Verify query - expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); + const [query, update] = updateManyStub.firstCall.args; - // Verify update - expect(update).to.deep.equal({ - $set: { - status: 'PAID_HOLD_INVOICE', - settled_by_admin: true, - }, - }); + // Verify query + expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); + + // Verify update + expect(update).to.deep.equal({ + $set: { + status: 'PAID_HOLD_INVOICE', + settled_by_admin: true, + }, + }); - expect(exitStub.calledWith(0)).to.be.true; - }); + expect(exitStub.calledWith(0)).to.equal(true); + }); }); -export {}; +export { }; From d790c63e3618eaf4ec094297141693a1db48b222 Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 16 Mar 2026 21:16:35 -0300 Subject: [PATCH 7/8] Code formatting --- tests/bot/bot.spec.ts | 8 +- tests/bot/modules/block.spec.ts | 114 ++++++++-------- .../migrate_completed_by_admin_orders.spec.ts | 124 +++++++++--------- 3 files changed, 123 insertions(+), 123 deletions(-) diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index e9d8dcab..a491d87d 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -255,9 +255,9 @@ describe('Bot Initialization', () => { '@grammyjs/i18n': { I18n: sinon .stub() - .returns({ middleware: sinon.stub().returns(() => { }) }), + .returns({ middleware: sinon.stub().returns(() => {}) }), }, - '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => { }) }, + '@grammyjs/ratelimiter': { limit: sinon.stub().returns(() => {}) }, '../models': { Order: { findOne: sinon.stub().resolves(null), @@ -431,8 +431,8 @@ describe('Bot Initialization', () => { userMiddleware: sinon.stub().resolves(), adminMiddleware: sinon.stub().resolves(), superAdminMiddleware: sinon.stub().resolves(), - commandArgsMiddleware: sinon.stub().returns(() => { }), - stageMiddleware: sinon.stub().returns(() => { }), + commandArgsMiddleware: sinon.stub().returns(() => {}), + stageMiddleware: sinon.stub().returns(() => {}), }, '../logger': { error: sinon.stub(), diff --git a/tests/bot/modules/block.spec.ts b/tests/bot/modules/block.spec.ts index 8728ad07..099e717f 100644 --- a/tests/bot/modules/block.spec.ts +++ b/tests/bot/modules/block.spec.ts @@ -3,74 +3,74 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); describe('Block Module block query', () => { - let sandbox: any; - let orderExistsStub: any; - let blockExistsStub: any; - let blockSaveStub: any; - let userFindOneStub: any; + let sandbox: any; + let orderExistsStub: any; + let blockExistsStub: any; + let blockSaveStub: any; + let userFindOneStub: any; - beforeEach(() => { - sandbox = sinon.createSandbox(); + beforeEach(() => { + sandbox = sinon.createSandbox(); - orderExistsStub = sandbox.stub(); - blockExistsStub = sandbox.stub(); - blockSaveStub = sandbox.stub().resolves(); + orderExistsStub = sandbox.stub(); + blockExistsStub = sandbox.stub(); + blockSaveStub = sandbox.stub().resolves(); - userFindOneStub = sandbox.stub().resolves({ - id: '2', - tg_id: 2, - username: 'badguy', - }); - - // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. - // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. + userFindOneStub = sandbox.stub().resolves({ + id: '2', + tg_id: 2, + username: 'badguy', }); - afterEach(() => { - sandbox.restore(); - }); + // We need to proxyquire the Block model constructor too, since `const block = new Block(...)` is used. + // Instead of full proxyquire, let's just test the `Order.exists` query passed to it, as requested by the review. + }); - it('should exclude settled orders from pending count when blocking', async () => { - const ctx = { - user: { - id: '1', - tg_id: 1, - username: 'goodguy', - }, - }; + afterEach(() => { + sandbox.restore(); + }); - orderExistsStub.resolves(false); - blockExistsStub.resolves(false); + it('should exclude settled orders from pending count when blocking', async () => { + const ctx = { + user: { + id: '1', + tg_id: 1, + username: 'goodguy', + }, + }; - // We stub Block constructor for the `new Block` call at the end - const BlockMock = function (this: any) { - this.save = blockSaveStub; - }; - BlockMock.exists = blockExistsStub; + orderExistsStub.resolves(false); + blockExistsStub.resolves(false); - const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { - '../../../models': { - Order: { exists: orderExistsStub }, - Block: BlockMock, - User: { findOne: userFindOneStub }, - }, - './messages': { - ordersInProcess: sandbox.stub().resolves(), - userBlocked: sandbox.stub().resolves(), - }, - }); + // We stub Block constructor for the `new Block` call at the end + const BlockMock = function (this: any) { + this.save = blockSaveStub; + }; + BlockMock.exists = blockExistsStub; - await blockModuleFixed.block(ctx, '@badguy'); + const blockModuleFixed = proxyquire('../../../bot/modules/block/commands', { + '../../../models': { + Order: { exists: orderExistsStub }, + Block: BlockMock, + User: { findOne: userFindOneStub }, + }, + './messages': { + ordersInProcess: sandbox.stub().resolves(), + userBlocked: sandbox.stub().resolves(), + }, + }); - expect(orderExistsStub.calledOnce).to.equal(true); + await blockModuleFixed.block(ctx, '@badguy'); - const queryArgs = orderExistsStub.firstCall.args[0]; + expect(orderExistsStub.calledOnce).to.equal(true); - // The review requires that we verify the query excludes settled orders from pending count - // The query excludes these statuses using $nin. - // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. - expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); - expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); - }); + const queryArgs = orderExistsStub.firstCall.args[0]; + + // The review requires that we verify the query excludes settled orders from pending count + // The query excludes these statuses using $nin. + // PAID_HOLD_INVOICE is one of them, which now represents completed orders along with settled_by_admin: true. + expect(queryArgs.status.$nin).to.include('PAID_HOLD_INVOICE'); + expect(queryArgs.status.$nin).to.not.include('COMPLETED_BY_ADMIN'); + }); }); -export { }; +export {}; diff --git a/tests/scripts/migrate_completed_by_admin_orders.spec.ts b/tests/scripts/migrate_completed_by_admin_orders.spec.ts index 77a52a31..631698ea 100644 --- a/tests/scripts/migrate_completed_by_admin_orders.spec.ts +++ b/tests/scripts/migrate_completed_by_admin_orders.spec.ts @@ -3,79 +3,79 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); describe('Migration Script: migrate_completed_by_admin_orders', () => { - let sandbox: any; - let updateManyStub: any; - let exitStub: any; - let infoStub: any; - let errorStub: any; + let sandbox: any; + let updateManyStub: any; + let exitStub: any; + let infoStub: any; + let errorStub: any; - beforeEach(() => { - sandbox = sinon.createSandbox(); + beforeEach(() => { + sandbox = sinon.createSandbox(); - updateManyStub = sandbox.stub().resolves({ - matchedCount: 2, - modifiedCount: 2, - }); - - // Mock logger - infoStub = sandbox.stub(); - errorStub = sandbox.stub(); - - // Mock process.exit - exitStub = sandbox.stub(process, 'exit'); + updateManyStub = sandbox.stub().resolves({ + matchedCount: 2, + modifiedCount: 2, }); - afterEach(() => { - sandbox.restore(); - }); + // Mock logger + infoStub = sandbox.stub(); + errorStub = sandbox.stub(); - it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { - // We proxyquire the script to inject mocks - proxyquire('../../scripts/migrate_completed_by_admin_orders', { - '../db_connect': { - connect: sandbox.stub().returns({ - connection: { - once: sandbox.stub().callsFake((event: any, cb: any) => { - if (event === 'open') cb(); - }), - on: sandbox.stub(), - close: sandbox.stub().resolves(), - }, - }), - }, - '../models/order': { - default: { - updateMany: updateManyStub, - }, - }, - '../logger': { - logger: { - info: infoStub, - error: errorStub, - }, - }, - }); + // Mock process.exit + exitStub = sandbox.stub(process, 'exit'); + }); - // We need to wait a tick for the async immediately-invoked function to resolve - await new Promise(resolve => setTimeout(resolve, 10)); + afterEach(() => { + sandbox.restore(); + }); - expect(updateManyStub.calledOnce).to.equal(true); + it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { + // We proxyquire the script to inject mocks + proxyquire('../../scripts/migrate_completed_by_admin_orders', { + '../db_connect': { + connect: sandbox.stub().returns({ + connection: { + once: sandbox.stub().callsFake((event: any, cb: any) => { + if (event === 'open') cb(); + }), + on: sandbox.stub(), + close: sandbox.stub().resolves(), + }, + }), + }, + '../models/order': { + default: { + updateMany: updateManyStub, + }, + }, + '../logger': { + logger: { + info: infoStub, + error: errorStub, + }, + }, + }); + + // We need to wait a tick for the async immediately-invoked function to resolve + await new Promise(resolve => setTimeout(resolve, 10)); - const [query, update] = updateManyStub.firstCall.args; + expect(updateManyStub.calledOnce).to.equal(true); - // Verify query - expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); + const [query, update] = updateManyStub.firstCall.args; - // Verify update - expect(update).to.deep.equal({ - $set: { - status: 'PAID_HOLD_INVOICE', - settled_by_admin: true, - }, - }); + // Verify query + expect(query).to.deep.equal({ status: 'COMPLETED_BY_ADMIN' }); - expect(exitStub.calledWith(0)).to.equal(true); + // Verify update + expect(update).to.deep.equal({ + $set: { + status: 'PAID_HOLD_INVOICE', + settled_by_admin: true, + }, }); + + expect(exitStub.calledWith(0)).to.equal(true); + }); }); -export { }; +export {}; From 80bbedb282b87826a24dc19679124daa7435c462 Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 23 Mar 2026 21:44:57 -0300 Subject: [PATCH 8/8] Migration script should change status of COMPLETED_BY_ADMIN orders to SUCCESS --- scripts/migrate_completed_by_admin_orders.ts | 2 +- tests/scripts/migrate_completed_by_admin_orders.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/migrate_completed_by_admin_orders.ts b/scripts/migrate_completed_by_admin_orders.ts index ad011d02..25c32537 100644 --- a/scripts/migrate_completed_by_admin_orders.ts +++ b/scripts/migrate_completed_by_admin_orders.ts @@ -16,7 +16,7 @@ const migrate = async () => { const query = { status: 'COMPLETED_BY_ADMIN' }; const update = { $set: { - status: 'PAID_HOLD_INVOICE', + status: 'SUCCESS', settled_by_admin: true, }, }; diff --git a/tests/scripts/migrate_completed_by_admin_orders.spec.ts b/tests/scripts/migrate_completed_by_admin_orders.spec.ts index 631698ea..4d10a5ba 100644 --- a/tests/scripts/migrate_completed_by_admin_orders.spec.ts +++ b/tests/scripts/migrate_completed_by_admin_orders.spec.ts @@ -29,7 +29,7 @@ describe('Migration Script: migrate_completed_by_admin_orders', () => { sandbox.restore(); }); - it('should migrate COMPLETED_BY_ADMIN orders to PAID_HOLD_INVOICE + settled_by_admin', async () => { + it('should migrate COMPLETED_BY_ADMIN orders to SUCCESS + settled_by_admin', async () => { // We proxyquire the script to inject mocks proxyquire('../../scripts/migrate_completed_by_admin_orders', { '../db_connect': { @@ -69,7 +69,7 @@ describe('Migration Script: migrate_completed_by_admin_orders', () => { // Verify update expect(update).to.deep.equal({ $set: { - status: 'PAID_HOLD_INVOICE', + status: 'SUCCESS', settled_by_admin: true, }, });