const assert = require('node:assert/strict'); const http = require('node:http'); const test = require('node:test'); const { closeDbPool, getDb } = require('../src/config/db'); const { calculateOrderCharge, createOrder, getUserOrder, listUserOrders } = require('../src/services/orders.service'); const { creditBalance } = require('../src/services/wallet.service'); const createdUserIds = []; const createdServiceIds = []; let providerCalls = []; let providerMode = 'success'; let providerMock; function unique(prefix) { return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`; } function startProviderMock() { const server = http.createServer(async (req, res) => { const chunks = []; for await (const chunk of req) { chunks.push(chunk); } const body = new URLSearchParams(Buffer.concat(chunks).toString()); providerCalls.push(Object.fromEntries(body.entries())); res.setHeader('content-type', 'application/json'); if (body.get('action') !== 'add') { res.statusCode = 400; res.end(JSON.stringify({ error: 'unsupported action' })); return; } if (providerMode === 'failure') { res.end(JSON.stringify({ error: 'provider rejected order' })); return; } res.end(JSON.stringify({ order: `provider-${Date.now()}` })); }); return new Promise((resolve) => { server.listen(0, '127.0.0.1', () => { resolve({ url: `http://127.0.0.1:${server.address().port}`, close: () => new Promise((done) => server.close(done)) }); }); }); } async function createUser() { const db = getDb(); const username = unique('order_user'); const [id] = await db('users').insert({ username, email: `${username}@example.com`, password_hash: 'hash', balance: '0.0000' }); createdUserIds.push(id); return id; } async function createService(overrides = {}) { const [id] = await getDb()('services').insert({ provider_service_id: unique('provider_service'), name: 'Instagram Followers', category: 'Instagram', provider_price: '4.0000', markup_percent: '25.0000', sell_price: '5.0000', min: 100, max: 10000, is_active: true, ...overrides }); createdServiceIds.push(id); return id; } async function readUser(userId) { return getDb()('users').where({ id: userId }).first(); } test.before(async () => { providerMock = await startProviderMock(); process.env.PROVIDER_API_URL = providerMock.url; process.env.PROVIDER_API_KEY = 'mock-key'; }); test.after(async () => { const db = getDb(); if (createdUserIds.length > 0) { await db('orders').whereIn('user_id', createdUserIds).del(); await db('deposits').whereIn('user_id', createdUserIds).orWhereIn('reviewed_by', createdUserIds).del(); await db('transactions').whereIn('user_id', createdUserIds).del(); await db('password_reset_tokens').whereIn('user_id', createdUserIds).del(); await db('refresh_tokens').whereIn('user_id', createdUserIds).del(); await db('users').whereIn('id', createdUserIds).del(); } if (createdServiceIds.length > 0) { await db('services').whereIn('id', createdServiceIds).del(); } await closeDbPool(); await providerMock.close(); }); test('calculateOrderCharge uses server sell_price per 1000', () => { assert.equal(calculateOrderCharge('5.0000', 1000), '5.0000'); assert.equal(calculateOrderCharge('5.0000', 250), '1.2500'); assert.equal(calculateOrderCharge('1.3333', 3), '0.0040'); }); test('successful order charges wallet, creates order, and sends provider order', async () => { providerMode = 'success'; providerCalls = []; const userId = await createUser(); const serviceId = await createService({ provider_service_id: 'provider-success-test' }); await creditBalance(userId, '20.0000', unique('seed_order_success'), 'seed balance'); const order = await createOrder(userId, { service_id: serviceId, link: 'https://example.com/post/1', quantity: 1500 }); const user = await readUser(userId); const transactions = await getDb()('transactions').where({ user_id: userId, type: 'order', reference: `order:${order.id}` }); assert.equal(order.status, 'processing'); assert.equal(order.charge, '7.5000'); assert.equal(user.balance, '12.5000'); assert.equal(transactions.length, 1); assert.equal(providerCalls.length, 1); assert.equal(providerCalls[0].service, 'provider-success-test'); assert.equal(providerCalls[0].quantity, '1500'); }); test('failed provider order refunds wallet and marks order failed', async () => { providerMode = 'failure'; providerCalls = []; const userId = await createUser(); const serviceId = await createService({ provider_service_id: 'provider-failure-test' }); await creditBalance(userId, '10.0000', unique('seed_order_failure'), 'seed balance'); await assert.rejects( () => createOrder(userId, { service_id: serviceId, link: 'https://example.com/post/2', quantity: 1000 }), (error) => error.code === 'PROVIDER_ERROR' ); const user = await readUser(userId); const [order] = await getDb()('orders').where({ user_id: userId, service_id: serviceId }); const refund = await getDb()('transactions').where({ user_id: userId, type: 'refund', reference: `refund:order:${order.id}` }).first(); assert.equal(order.status, 'failed'); assert.equal(user.balance, '10.0000'); assert.equal(refund.amount, '5.0000'); }); test('users can list and read only their own orders', async () => { providerMode = 'success'; const ownerId = await createUser(); const otherUserId = await createUser(); const serviceId = await createService({ provider_service_id: 'provider-visibility-test' }); await creditBalance(ownerId, '10.0000', unique('seed_owner'), 'seed owner'); await creditBalance(otherUserId, '10.0000', unique('seed_other'), 'seed other'); const ownerOrder = await createOrder(ownerId, { service_id: serviceId, link: 'https://example.com/owner', quantity: 1000 }); await createOrder(otherUserId, { service_id: serviceId, link: 'https://example.com/other', quantity: 1000 }); const ownerOrders = await listUserOrders(ownerId); assert.equal(ownerOrders.some((order) => order.id === ownerOrder.id), true); assert.equal(ownerOrders.every((order) => Number(order.user_id) === Number(ownerId)), true); await assert.rejects( () => getUserOrder(otherUserId, ownerOrder.id), (error) => error.code === 'ORDER_NOT_FOUND' ); });