Files
inventory-barcode-system/__tests__/codes.routes.test.js

661 lines
21 KiB
JavaScript

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');
});
});
});