const assert = require('node:assert/strict'); const test = require('node:test'); const { pool } = require('../db'); const productionOrderRouter = require('../routes/productionOrderRoutes'); const analyticsRouter = require('../routes/analyticsRoutes'); const getRouteHandler = (router, method, path) => { const route = router.stack.find(layer => layer.route?.path === path && layer.route.methods[method]); if (!route) throw new Error(`Route not found: ${method.toUpperCase()} ${path}`); return route.route.stack.at(-1).handle; }; const tinySyncHandler = getRouteHandler(productionOrderRouter, 'post', '/production-orders/tiny-sync'); const productCompositionHandler = getRouteHandler(analyticsRouter, 'get', '/analytics/products/:productId/composition'); const invokeHandler = async (handler, req) => { let statusCode = 200; let body; const res = { status(code) { statusCode = code; return this; }, json(payload) { body = payload; return this; } }; await handler(req, res); return { statusCode, body }; }; const compositionPayload = (components = [ { componentTinyId: '919498232', componentName: 'ETIQUETA DE TAMANHO GG', componentSku: 'ETIQ.T.GG', quantityPerUnit: '1', totalQuantity: '1', unit: 'UN' }, { componentTinyId: '976059144', componentName: '6118 MALHA CAMISETA 30OE COR CAFE', componentSku: 'MC.30.CAF', quantityPerUnit: '0.1851', totalQuantity: '0.1851', unit: 'KG' } ]) => ({ order: { tinyId: 'STRUCTURE-V3-976058813', number: 'STRUCTURE-V3-BLCS.CAF.GG', status: 'completed', productSku: 'BLCS.CAF.GG', productDescription: 'BASE LISA CAMISETA COR CAFE TAMANHO - GG', quantity: '1', unit: 'UN', notes: 'Composição sincronizada da API pública Olist V3.' }, components, steps: [] }); const withFakeDatabase = async (run) => { const originalConnect = pool.connect; const originalQuery = pool.query; const state = { nextCompositionId: 1, nextOrderId: 1, compositions: [], compositionComponents: [], productionOrders: [] }; const query = async (sql, params = []) => { const normalizedSql = String(sql).replace(/\s+/g, ' ').trim(); if (/^(BEGIN|COMMIT|ROLLBACK);?$/.test(normalizedSql)) return { rows: [] }; if (normalizedSql.startsWith('SELECT id FROM product_compositions WHERE source')) { const composition = state.compositions.find(item => item.source === params[0] && item.finished_product_identity === params[1]); return { rows: composition ? [{ id: composition.id }] : [] }; } if (normalizedSql.startsWith('INSERT INTO product_compositions')) { const composition = { id: state.nextCompositionId++, source: params[0], external_source_id: params[1], finished_product_identity: params[2], finished_product_sku: params[3], finished_product_description: params[4], finished_product_unit: params[5], finished_tiny_product_id: params[6], source_metadata: JSON.parse(params[7]), last_synced_at: '2026-07-29T12:00:00.000Z' }; state.compositions.push(composition); return { rows: [{ id: composition.id }] }; } if (normalizedSql.startsWith('UPDATE product_compositions')) { const composition = state.compositions.find(item => item.id === params[7]); composition.external_source_id = params[0]; composition.finished_product_sku = params[2]; composition.finished_product_description = params[3]; composition.finished_product_unit = params[4]; composition.finished_tiny_product_id = params[5]; composition.source_metadata = JSON.parse(params[6]); return { rows: [] }; } if (normalizedSql.startsWith('DELETE FROM product_composition_components')) { state.compositionComponents = state.compositionComponents.filter(item => item.product_composition_id !== params[0]); return { rows: [] }; } if (normalizedSql.startsWith('INSERT INTO product_composition_components')) { state.compositionComponents.push({ id: state.compositionComponents.length + 1, product_composition_id: params[0], component_identity: params[1], component_tiny_id: params[2], component_sku: params[3], component_name: params[4], quantity_per_unit: params[5], unit: params[6] }); return { rows: [] }; } if (normalizedSql.includes('FROM product_compositions composition')) { const composition = state.compositions.find(item => ( item.source === params[0] && (item.finished_tiny_product_id === params[1] || item.finished_product_sku === String(params[1]).toUpperCase()) )); if (!composition) return { rows: [] }; return { rows: [{ ...composition, components: state.compositionComponents .filter(item => item.product_composition_id === composition.id) .map(item => ({ ...item, component_product_id: null })) }] }; } if (normalizedSql.startsWith('SELECT id FROM production_orders')) { return { rows: [] }; } if (normalizedSql.startsWith('INSERT INTO production_orders')) { const order = { id: state.nextOrderId++, tiny_id: params[0], number: params[1], status: params[2], order_reference: params[3], issue_date: params[4], expected_date: params[5], product_sku: params[6], product_description: params[7], quantity: params[8], unit: params[9], integration_status: params[10], notes: params[11], supplier: params[12], lot_code: params[13], roll_quantity: params[14], fabric_kg: params[15], rib_kg: params[16], yield_pieces_per_kg: params[17], created_at: null, updated_at: null }; state.productionOrders.push(order); return { rows: [{ id: order.id }] }; } if (normalizedSql.startsWith('DELETE FROM production_order_components') || normalizedSql.startsWith('DELETE FROM production_order_steps')) { return { rows: [] }; } if (normalizedSql.includes('FROM production_orders po') && normalizedSql.includes('WHERE po.id = $1')) { const order = state.productionOrders.find(item => item.id === params[0]); return { rows: order ? [{ ...order, markers: [], components: [], steps: [] }] : [] }; } throw new Error(`Unexpected SQL in test double: ${normalizedSql.slice(0, 100)}`); }; pool.query = query; pool.connect = async () => ({ query, release() {} }); try { await run(state); } finally { pool.connect = originalConnect; pool.query = originalQuery; } }; test('Tiny/Olist V3 first import creates a composition without creating an OP', async () => { await withFakeDatabase(async (state) => { const response = await invokeHandler(tinySyncHandler, { body: compositionPayload() }); const result = response.body; assert.equal(response.statusCode, 201); assert.equal(result.componentCount, 2); assert.equal(state.compositions.length, 1); assert.equal(state.compositionComponents.length, 2); assert.equal(state.productionOrders.length, 0); assert.equal(result.composition.finishedTinyProductId, '976058813'); }); }); test('Tiny/Olist V3 re-sync replaces composition components without duplicates', async () => { await withFakeDatabase(async (state) => { await invokeHandler(tinySyncHandler, { body: compositionPayload() }); await invokeHandler(tinySyncHandler, { body: compositionPayload([ { componentTinyId: '976059144', componentName: 'MALHA ATUALIZADA', componentSku: 'MC.30.CAF', quantityPerUnit: '0.2', totalQuantity: '0.2', unit: 'KG' }, { componentTinyId: '976059144', componentName: 'MALHA ATUALIZADA', componentSku: 'MC.30.CAF', quantityPerUnit: '0.2', totalQuantity: '0.2', unit: 'KG' } ]) }); assert.equal(state.compositions.length, 1); assert.equal(state.compositionComponents.length, 1); assert.equal(state.compositionComponents[0].quantity_per_unit, 0.2); assert.equal(state.productionOrders.length, 0); }); }); test('product composition detail lookup returns stored components', async () => { await withFakeDatabase(async () => { await invokeHandler(tinySyncHandler, { body: compositionPayload() }); const response = await invokeHandler(productCompositionHandler, { params: { productId: '976058813' } }); const composition = response.body.composition; assert.equal(response.statusCode, 200); assert.equal(composition.finishedProductSku, 'BLCS.CAF.GG'); assert.equal(composition.components.length, 2); assert.deepEqual(composition.components[0], { id: 1, componentTinyId: '919498232', componentSku: 'ETIQ.T.GG', componentName: 'ETIQUETA DE TAMANHO GG', quantityPerUnit: 1, unit: 'UN', productId: null }); }); }); test('ordinary Tiny production-order payloads continue to create production orders', async () => { await withFakeDatabase(async (state) => { const response = await invokeHandler(tinySyncHandler, { body: { order: { tinyId: '12345', number: 'OP-12345', status: 'in_progress', productSku: 'BLCS.CAF.GG', productDescription: 'BASE LISA CAMISETA COR CAFE TAMANHO - GG', quantity: '10', unit: 'UN' }, components: [], steps: [] } }); const result = response.body; assert.equal(response.statusCode, 201); assert.equal(state.compositions.length, 0); assert.equal(state.productionOrders.length, 1); assert.equal(result.order.tinyId, '12345'); assert.equal(result.order.number, 'OP-12345'); }); });