const request = require('supertest'); const app = require('../server'); const CodeGenerationService = require('../services/CodeGenerationService'); const PrintableLayoutService = require('../services/PrintableLayoutService'); const Product = require('../models/Product'); const Inventory = require('../models/Inventory'); // Mock the services and models jest.mock('../services/CodeGenerationService'); jest.mock('../services/PrintableLayoutService'); jest.mock('../models/Product'); jest.mock('../models/Inventory'); describe('Codes API Endpoints', () => { beforeEach(() => { // Reset all mocks jest.clearAllMocks(); }); describe('GET /api/codes/formats', () => { it('should return supported barcode formats', async () => { const mockFormats = ['CODE128', 'CODE39', 'EAN13', 'EAN8', 'UPC']; CodeGenerationService.prototype.getSupportedFormats = jest.fn().mockReturnValue(mockFormats); const response = await request(app) .get('/api/codes/formats') .expect(200); expect(response.body.success).toBe(true); expect(response.body.data.barcodeFormats).toEqual(mockFormats); expect(response.body.data.qrCodeSupported).toBe(true); }); it('should handle service errors gracefully', async () => { CodeGenerationService.prototype.getSupportedFormats = jest.fn().mockImplementation(() => { throw new Error('Service error'); }); const response = await request(app) .get('/api/codes/formats') .expect(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to retrieve supported formats'); }); }); describe('POST /api/codes/barcode', () => { it('should generate barcode successfully', async () => { const mockResult = { success: true, data: 'data:image/png;base64,mockbarcodedata', format: 'CODE128', productCode: 'TEST123' }; CodeGenerationService.prototype.generateBarcode = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/barcode') .send({ productCode: 'TEST123', format: 'CODE128', options: { width: 2 } }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toEqual(mockResult); expect(response.body.message).toBe('Barcode generated successfully'); }); it('should return 400 for missing product code', async () => { const response = await request(app) .post('/api/codes/barcode') .send({ format: 'CODE128' }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Missing product code'); }); it('should return 400 for barcode generation failure', async () => { const mockResult = { success: false, error: 'Invalid product code format' }; CodeGenerationService.prototype.generateBarcode = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/barcode') .send({ productCode: 'INVALID' }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Barcode generation failed'); }); }); describe('POST /api/codes/qrcode', () => { it('should generate QR code successfully', async () => { const mockResult = { success: true, data: 'data:image/png;base64,mockqrcodedata', productCode: 'TEST123', embeddedData: { code: 'TEST123', desc: 'Test Product' } }; CodeGenerationService.prototype.generateQRCode = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/qrcode') .send({ productData: { product_code: 'TEST123', description: 'Test Product' } }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toEqual(mockResult); expect(response.body.message).toBe('QR code generated successfully'); }); it('should return 400 for invalid product data', async () => { const response = await request(app) .post('/api/codes/qrcode') .send({ productData: {} }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid product data'); }); it('should return 400 for QR code generation failure', async () => { const mockResult = { success: false, error: 'Invalid product data format' }; CodeGenerationService.prototype.generateQRCode = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/qrcode') .send({ productData: { product_code: 'TEST123' } }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('QR code generation failed'); }); }); describe('POST /api/codes/both', () => { it('should generate both barcode and QR code successfully', async () => { const mockResult = { productCode: 'TEST123', barcode: { success: true, data: 'barcode-data' }, qrCode: { success: true, data: 'qrcode-data' }, timestamp: '2023-01-01T00:00:00.000Z' }; CodeGenerationService.prototype.generateBothCodes = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/both') .send({ productData: { product_code: 'TEST123', description: 'Test Product' } }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toEqual(mockResult); expect(response.body.message).toBe('Codes generated successfully'); }); it('should return 400 for invalid product data', async () => { const response = await request(app) .post('/api/codes/both') .send({ productData: {} }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid product data'); }); }); describe('POST /api/codes/product/:productId', () => { it('should generate codes for specific product successfully', async () => { const mockProduct = { id: 1, name: 'TEST123', description: 'Test Product', category: 'Electronics', unit: 'pieces', toJSON: jest.fn().mockReturnValue({ id: 1, name: 'TEST123', description: 'Test Product', category: 'Electronics', unit: 'pieces' }) }; const mockResult = { productCode: 'TEST123', barcode: { success: true, data: 'barcode-data' }, qrCode: { success: true, data: 'qrcode-data' } }; Product.findById.mockResolvedValue(mockProduct); CodeGenerationService.prototype.generateBothCodes = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/product/1') .send({ codeType: 'both' }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data.product.id).toBe(1); expect(response.body.data.codes).toEqual(mockResult); }); it('should return 404 for non-existent product', async () => { Product.findById.mockResolvedValue(null); const response = await request(app) .post('/api/codes/product/999') .send({ codeType: 'barcode' }) .expect(404); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Product not found'); }); it('should return 400 for invalid product ID', async () => { const response = await request(app) .post('/api/codes/product/invalid') .send({ codeType: 'barcode' }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid product ID'); }); it('should generate only barcode when requested', async () => { const mockProduct = { id: 1, name: 'TEST123', toJSON: jest.fn().mockReturnValue({ id: 1, name: 'TEST123' }) }; const mockResult = { success: true, data: 'barcode-data' }; Product.findById.mockResolvedValue(mockProduct); CodeGenerationService.prototype.generateBarcode = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/product/1') .send({ codeType: 'barcode' }) .expect(200); expect(response.body.success).toBe(true); expect(CodeGenerationService.prototype.generateBarcode).toHaveBeenCalled(); }); }); describe('GET /api/codes/layouts/sizes', () => { it('should return available label sizes', async () => { const mockLabelSizes = { 'avery-5160': { width: 66.7, height: 25.4, columns: 3, rows: 10 }, 'avery-5161': { width: 101.6, height: 25.4, columns: 2, rows: 10 } }; PrintableLayoutService.prototype.getAvailableLabelSizes = jest.fn().mockReturnValue(mockLabelSizes); const response = await request(app) .get('/api/codes/layouts/sizes') .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toEqual(mockLabelSizes); }); }); describe('POST /api/codes/layouts/preview', () => { it('should generate layout preview successfully', async () => { const mockProducts = [ { id: 1, name: 'P001', description: 'Product 1' }, { id: 2, name: 'P002', description: 'Product 2' } ]; const mockPreview = { success: true, preview: { labelSize: 'avery-5160', labelsPerPage: 30, totalPages: 1, includeBarcode: true, includeQRCode: false } }; Product.findById .mockResolvedValueOnce(mockProducts[0]) .mockResolvedValueOnce(mockProducts[1]); PrintableLayoutService.prototype.generateLayoutPreview = jest.fn().mockResolvedValue(mockPreview); const response = await request(app) .post('/api/codes/layouts/preview') .send({ productIds: [1, 2], options: { labelSize: 'avery-5160' } }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data.labelSize).toBe('avery-5160'); expect(response.body.data.totalRequestedProducts).toBe(2); }); it('should return 400 for empty product IDs', async () => { const response = await request(app) .post('/api/codes/layouts/preview') .send({ productIds: [] }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid product IDs'); }); it('should return 404 when no valid products found', async () => { Product.findById.mockResolvedValue(null); const response = await request(app) .post('/api/codes/layouts/preview') .send({ productIds: [999] }) .expect(404); expect(response.body.success).toBe(false); expect(response.body.error).toBe('No valid products found'); }); }); describe('POST /api/codes/layouts/generate', () => { it('should generate printable PDF layout successfully', async () => { const mockProduct = { id: 1, name: 'P001', description: 'Product 1', category: 'Electronics', unit: 'pieces' }; const mockPdfBuffer = Buffer.from('mock-pdf-data'); const mockResult = { success: true, data: mockPdfBuffer, metadata: { totalProducts: 1, totalPages: 1, labelSize: 'avery-5160' } }; Product.findById.mockResolvedValue(mockProduct); PrintableLayoutService.prototype.generatePrintableLayout = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/layouts/generate') .send({ productIds: [1], options: { labelSize: 'avery-5160' } }) .expect(200); expect(response.headers['content-type']).toBe('application/pdf'); expect(response.headers['content-disposition']).toContain('attachment'); expect(Buffer.isBuffer(response.body)).toBe(true); }); it('should return 400 for too many products', async () => { const productIds = Array.from({ length: 1001 }, (_, i) => i + 1); const response = await request(app) .post('/api/codes/layouts/generate') .send({ productIds }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Too many products'); }); it('should return 400 for layout generation failure', async () => { const mockProduct = { id: 1, name: 'P001' }; const mockResult = { success: false, error: 'Layout generation failed' }; Product.findById.mockResolvedValue(mockProduct); PrintableLayoutService.prototype.generatePrintableLayout = jest.fn().mockResolvedValue(mockResult); const response = await request(app) .post('/api/codes/layouts/generate') .send({ productIds: [1] }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Layout generation failed'); }); }); describe('GET /api/codes/export/excel', () => { it('should export inventory data to Excel successfully', async () => { const mockInventoryData = [ { product_code: 'P001', description: 'Product 1', category: 'Electronics', current_level: 10, minimum_level: 5, stock_status: 'normal', last_updated: '2023-01-01T00:00:00Z', updated_by: 'user1' } ]; Inventory.getInventorySummary.mockResolvedValue(mockInventoryData); const response = await request(app) .get('/api/codes/export/excel') .expect(200); expect(response.headers['content-type']).toContain('spreadsheetml.sheet'); expect(response.headers['content-disposition']).toContain('attachment'); expect(response.headers['content-disposition']).toContain('inventory-export-'); }); it('should filter inventory data by category', async () => { const mockInventoryData = [ { product_code: 'P001', description: 'Product 1', category: 'Electronics', current_level: 10 } ]; Inventory.getInventorySummary.mockResolvedValue(mockInventoryData); const response = await request(app) .get('/api/codes/export/excel?category=Electronics') .expect(200); expect(Inventory.getInventorySummary).toHaveBeenCalledWith({ category: 'Electronics' }); }); it('should return 404 when no data to export', async () => { Inventory.getInventorySummary.mockResolvedValue([]); const response = await request(app) .get('/api/codes/export/excel') .expect(404); expect(response.body.success).toBe(false); expect(response.body.error).toBe('No data to export'); }); }); describe('POST /api/codes/export/excel/custom', () => { it('should export custom inventory data successfully', async () => { const mockProduct = { id: 1, name: 'P001', description: 'Product 1', category: 'Electronics' }; const mockInventory = { current_level: 10, minimum_level: 5, last_updated: '2023-01-01T00:00:00Z', updated_by: 'user1' }; Product.findById.mockResolvedValue(mockProduct); Inventory.getByProductId.mockResolvedValue(mockInventory); const response = await request(app) .post('/api/codes/export/excel/custom') .send({ productIds: [1], columns: ['product_code', 'description', 'current_level'], filename: 'custom-export.xlsx' }) .expect(200); expect(response.headers['content-type']).toContain('spreadsheetml.sheet'); expect(response.headers['content-disposition']).toContain('custom-export.xlsx'); }); it('should include history when requested', async () => { const mockProduct = { id: 1, name: 'P001' }; const mockInventory = { current_level: 10 }; const mockHistory = [ { product_code: 'P001', old_level: 5, new_level: 10, change_reason: 'Restock', updated_by: 'user1', updated_at: '2023-01-01T00:00:00Z' } ]; Product.findById.mockResolvedValue(mockProduct); Inventory.getByProductId.mockResolvedValue(mockInventory); Inventory.getInventoryHistory.mockResolvedValue(mockHistory); const response = await request(app) .post('/api/codes/export/excel/custom') .send({ productIds: [1], columns: ['product_code'], includeHistory: true }) .expect(200); expect(Inventory.getInventoryHistory).toHaveBeenCalled(); }); it('should return 400 for empty product IDs', async () => { const response = await request(app) .post('/api/codes/export/excel/custom') .send({ productIds: [] }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid product IDs'); }); it('should return 404 when no valid products found', async () => { Product.findById.mockResolvedValue(null); const response = await request(app) .post('/api/codes/export/excel/custom') .send({ productIds: [999] }) .expect(404); expect(response.body.success).toBe(false); expect(response.body.error).toBe('No data to export'); }); }); describe('POST /api/codes/qr/parse', () => { it('should parse QR code data successfully', async () => { const mockResult = { success: true, productCode: 'TEST123', description: 'Test Product', category: 'Electronics', timestamp: '2023-01-01T00:00:00Z' }; CodeGenerationService.prototype.parseQRCodeData = jest.fn().mockReturnValue(mockResult); const response = await request(app) .post('/api/codes/qr/parse') .send({ qrData: '{"code":"TEST123","desc":"Test Product","cat":"Electronics","ts":"2023-01-01T00:00:00Z"}' }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toEqual(mockResult); expect(response.body.message).toBe('QR code parsed successfully'); }); it('should return 400 for missing QR data', async () => { const response = await request(app) .post('/api/codes/qr/parse') .send({}) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Missing QR data'); }); it('should return 400 for QR parsing failure', async () => { const mockResult = { success: false, error: 'Invalid QR code data format' }; CodeGenerationService.prototype.parseQRCodeData = jest.fn().mockReturnValue(mockResult); const response = await request(app) .post('/api/codes/qr/parse') .send({ qrData: 'invalid-data' }) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('QR code parsing failed'); }); }); describe('Error Handling', () => { it('should handle service initialization errors', async () => { // Mock constructor to throw error const originalCodeGenService = CodeGenerationService; CodeGenerationService.mockImplementation(() => { throw new Error('Service initialization failed'); }); const response = await request(app) .post('/api/codes/barcode') .send({ productCode: 'TEST123' }) .expect(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to generate barcode'); // Restore original CodeGenerationService.mockImplementation(originalCodeGenService); }); it('should handle database connection errors', async () => { Product.findById.mockRejectedValue(new Error('Database connection failed')); const response = await request(app) .post('/api/codes/product/1') .send({ codeType: 'barcode' }) .expect(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to generate codes for product'); }); it('should handle Excel export errors', async () => { Inventory.getInventorySummary.mockRejectedValue(new Error('Database error')); const response = await request(app) .get('/api/codes/export/excel') .expect(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to export Excel file'); }); }); });