Initial commit: Inventory Barcode System
This commit is contained in:
717
__tests__/inventory.routes.test.js
Normal file
717
__tests__/inventory.routes.test.js
Normal file
@ -0,0 +1,717 @@
|
||||
const request = require('supertest');
|
||||
const app = require('../server');
|
||||
const Inventory = require('../models/Inventory');
|
||||
const Product = require('../models/Product');
|
||||
const database = require('../models/database');
|
||||
|
||||
// Mock the database and models
|
||||
jest.mock('../models/database');
|
||||
jest.mock('../models/Inventory');
|
||||
jest.mock('../models/Product');
|
||||
|
||||
describe('Inventory API Endpoints', () => {
|
||||
let mockDb;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Create mock database instance
|
||||
mockDb = {
|
||||
prepare: jest.fn(),
|
||||
transaction: jest.fn()
|
||||
};
|
||||
|
||||
database.getDatabase.mockReturnValue(mockDb);
|
||||
database.getInstance = jest.fn().mockReturnValue(mockDb);
|
||||
database.executeTransaction = jest.fn();
|
||||
});
|
||||
|
||||
describe('GET /api/inventory', () => {
|
||||
it('should return inventory summary for all products', async () => {
|
||||
const mockInventorySummary = [
|
||||
{
|
||||
id: 1,
|
||||
product_code: 'P001',
|
||||
description: 'Product 1',
|
||||
current_level: 10,
|
||||
minimum_level: 5,
|
||||
stock_status: 'normal'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
product_code: 'P002',
|
||||
description: 'Product 2',
|
||||
current_level: 2,
|
||||
minimum_level: 5,
|
||||
stock_status: 'low'
|
||||
}
|
||||
];
|
||||
|
||||
Inventory.getInventorySummary.mockResolvedValue(mockInventorySummary);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data).toHaveLength(2);
|
||||
expect(response.body.count).toBe(2);
|
||||
expect(Inventory.getInventorySummary).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
it('should filter inventory by category', async () => {
|
||||
const mockInventorySummary = [
|
||||
{
|
||||
id: 1,
|
||||
product_code: 'P001',
|
||||
description: 'Product 1',
|
||||
category: 'Electronics',
|
||||
current_level: 10
|
||||
}
|
||||
];
|
||||
|
||||
Inventory.getInventorySummary.mockResolvedValue(mockInventorySummary);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory?category=Electronics')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(Inventory.getInventorySummary).toHaveBeenCalledWith({ category: 'Electronics' });
|
||||
});
|
||||
|
||||
it('should filter for low stock items', async () => {
|
||||
const mockLowStockSummary = [
|
||||
{
|
||||
id: 2,
|
||||
product_code: 'P002',
|
||||
description: 'Product 2',
|
||||
current_level: 2,
|
||||
minimum_level: 5,
|
||||
stock_status: 'low'
|
||||
}
|
||||
];
|
||||
|
||||
Inventory.getInventorySummary.mockResolvedValue(mockLowStockSummary);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory?lowStock=true')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(Inventory.getInventorySummary).toHaveBeenCalledWith({ lowStock: true });
|
||||
});
|
||||
|
||||
it('should handle database errors gracefully', async () => {
|
||||
Inventory.getInventorySummary.mockRejectedValue(new Error('Database connection failed'));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory')
|
||||
.expect(500);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Failed to retrieve inventory summary');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/inventory/low-stock', () => {
|
||||
it('should return low stock items', async () => {
|
||||
const mockLowStockItems = [
|
||||
{
|
||||
id: 1,
|
||||
product_code: 'P001',
|
||||
description: 'Low Stock Product',
|
||||
current_level: 2,
|
||||
minimum_level: 10
|
||||
}
|
||||
];
|
||||
|
||||
Inventory.getLowStockItems.mockResolvedValue(mockLowStockItems);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/low-stock')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data).toHaveLength(1);
|
||||
expect(response.body.count).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle errors when retrieving low stock items', async () => {
|
||||
Inventory.getLowStockItems.mockRejectedValue(new Error('Database error'));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/low-stock')
|
||||
.expect(500);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Failed to retrieve low stock items');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/inventory/product/:productId', () => {
|
||||
it('should return inventory details for a specific product', async () => {
|
||||
const mockInventory = {
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 15,
|
||||
minimum_level: 5,
|
||||
maximum_level: 50,
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 15,
|
||||
minimum_level: 5,
|
||||
maximum_level: 50
|
||||
})
|
||||
};
|
||||
|
||||
Inventory.getByProductId.mockResolvedValue(mockInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/1')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.product_id).toBe(1);
|
||||
expect(response.body.data.current_level).toBe(15);
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent inventory', async () => {
|
||||
Inventory.getByProductId.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/999')
|
||||
.expect(404);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Inventory not found');
|
||||
});
|
||||
|
||||
it('should return 400 for invalid product ID', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/invalid')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid product ID');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/inventory/product/:productId/level', () => {
|
||||
it('should return current inventory level', async () => {
|
||||
Inventory.getCurrentLevel.mockResolvedValue(25);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/1/level')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.product_id).toBe(1);
|
||||
expect(response.body.data.current_level).toBe(25);
|
||||
});
|
||||
|
||||
it('should return 400 for invalid product ID', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/invalid/level')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid product ID');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/inventory/product/:productId/history', () => {
|
||||
it('should return inventory history with default pagination', async () => {
|
||||
const mockHistory = [
|
||||
{
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
old_level: 10,
|
||||
new_level: 15,
|
||||
change_reason: 'Stock received',
|
||||
updated_at: '2023-01-01T10:00:00Z'
|
||||
}
|
||||
];
|
||||
|
||||
Inventory.getInventoryHistory.mockResolvedValue(mockHistory);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/1/history')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data).toHaveLength(1);
|
||||
expect(response.body.pagination.limit).toBe(50);
|
||||
expect(response.body.pagination.offset).toBe(0);
|
||||
expect(Inventory.getInventoryHistory).toHaveBeenCalledWith(1, {
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
startDate: undefined,
|
||||
endDate: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it('should return inventory history with custom pagination', async () => {
|
||||
const mockHistory = [];
|
||||
Inventory.getInventoryHistory.mockResolvedValue(mockHistory);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/1/history?limit=10&offset=20')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.pagination.limit).toBe(10);
|
||||
expect(response.body.pagination.offset).toBe(20);
|
||||
expect(Inventory.getInventoryHistory).toHaveBeenCalledWith(1, {
|
||||
limit: 10,
|
||||
offset: 20,
|
||||
startDate: undefined,
|
||||
endDate: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 for invalid limit', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/1/history?limit=2000')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid limit');
|
||||
});
|
||||
|
||||
it('should return 400 for negative offset', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/inventory/product/1/history?offset=-1')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid offset');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /api/inventory/product/:productId/level', () => {
|
||||
it('should update inventory level successfully', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockUpdatedInventory = {
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 20,
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 20
|
||||
})
|
||||
};
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.updateInventoryLevel.mockResolvedValue(mockUpdatedInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1/level')
|
||||
.send({
|
||||
newLevel: 20,
|
||||
changeReason: 'Stock adjustment',
|
||||
updatedBy: 'test-user'
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.current_level).toBe(20);
|
||||
expect(response.body.message).toBe('Inventory level updated successfully');
|
||||
expect(Inventory.updateInventoryLevel).toHaveBeenCalledWith(
|
||||
1,
|
||||
20,
|
||||
'Stock adjustment',
|
||||
'test-user'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 400 for invalid new level', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1/level')
|
||||
.send({ newLevel: 'invalid' })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid new level');
|
||||
});
|
||||
|
||||
it('should return 400 for negative new level', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1/level')
|
||||
.send({ newLevel: -5 })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid new level');
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent product', async () => {
|
||||
Product.findById.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/999/level')
|
||||
.send({ newLevel: 10 })
|
||||
.expect(404);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Product not found');
|
||||
});
|
||||
|
||||
it('should return 409 for concurrent update conflict', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.updateInventoryLevel.mockRejectedValue(
|
||||
new Error('Concurrent update detected. Please refresh and try again.')
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1/level')
|
||||
.send({ newLevel: 10 })
|
||||
.expect(409);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Concurrent update conflict');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /api/inventory/product/:productId', () => {
|
||||
it('should update inventory settings successfully', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockInventory = {
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 15,
|
||||
minimum_level: 5,
|
||||
maximum_level: 50,
|
||||
version: 1,
|
||||
validate: jest.fn().mockReturnValue({ isValid: true, errors: [] }),
|
||||
save: jest.fn().mockResolvedValue(true),
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 15,
|
||||
minimum_level: 10,
|
||||
maximum_level: 100
|
||||
})
|
||||
};
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getByProductId.mockResolvedValue(mockInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1')
|
||||
.send({
|
||||
minimum_level: 10,
|
||||
maximum_level: 100,
|
||||
updatedBy: 'test-user'
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.message).toBe('Inventory settings updated successfully');
|
||||
expect(mockInventory.minimum_level).toBe(10);
|
||||
expect(mockInventory.maximum_level).toBe(100);
|
||||
expect(mockInventory.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent inventory', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getByProductId.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1')
|
||||
.send({ minimum_level: 10 })
|
||||
.expect(404);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Inventory not found');
|
||||
});
|
||||
|
||||
it('should return 400 for invalid minimum level', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockInventory = { id: 1, product_id: 1 };
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getByProductId.mockResolvedValue(mockInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/1')
|
||||
.send({ minimum_level: -5 })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid minimum level');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/inventory/product/:productId', () => {
|
||||
it('should create inventory record successfully', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockInventory = {
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 10,
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 10
|
||||
})
|
||||
};
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getByProductId.mockResolvedValue(null); // No existing inventory
|
||||
Inventory.createForProduct.mockResolvedValue(mockInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/product/1')
|
||||
.send({
|
||||
initialLevel: 10,
|
||||
minimumLevel: 5,
|
||||
maximumLevel: 50,
|
||||
updatedBy: 'test-user'
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.message).toBe('Inventory record created successfully');
|
||||
expect(Inventory.createForProduct).toHaveBeenCalledWith(1, 10, 5, 50, 'test-user');
|
||||
});
|
||||
|
||||
it('should return 409 if inventory already exists', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockExistingInventory = { id: 1, product_id: 1 };
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getByProductId.mockResolvedValue(mockExistingInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/product/1')
|
||||
.send({ initialLevel: 10 })
|
||||
.expect(409);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Inventory already exists');
|
||||
});
|
||||
|
||||
it('should return 400 for invalid initial level', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getByProductId.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/product/1')
|
||||
.send({ initialLevel: -5 })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid initial level');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/inventory/bulk-update', () => {
|
||||
it('should bulk update inventory levels successfully', async () => {
|
||||
const updates = [
|
||||
{ productId: 1, newLevel: 20, changeReason: 'Restock', updatedBy: 'user1' },
|
||||
{ productId: 2, newLevel: 15, changeReason: 'Adjustment', updatedBy: 'user1' }
|
||||
];
|
||||
|
||||
const mockUpdatedInventories = [
|
||||
{
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 20,
|
||||
toJSON: jest.fn().mockReturnValue({ id: 1, product_id: 1, current_level: 20 })
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
product_id: 2,
|
||||
current_level: 15,
|
||||
toJSON: jest.fn().mockReturnValue({ id: 2, product_id: 2, current_level: 15 })
|
||||
}
|
||||
];
|
||||
|
||||
Inventory.bulkUpdateInventory.mockResolvedValue(mockUpdatedInventories);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/bulk-update')
|
||||
.send({ updates })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.count).toBe(2);
|
||||
expect(response.body.message).toBe('Successfully updated 2 inventory records');
|
||||
expect(Inventory.bulkUpdateInventory).toHaveBeenCalledWith(updates);
|
||||
});
|
||||
|
||||
it('should return 400 for empty updates array', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/bulk-update')
|
||||
.send({ updates: [] })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid input');
|
||||
});
|
||||
|
||||
it('should return 400 for invalid update data', async () => {
|
||||
const updates = [
|
||||
{ productId: 'invalid', newLevel: 20 }
|
||||
];
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/bulk-update')
|
||||
.send({ updates })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid update data');
|
||||
});
|
||||
|
||||
it('should return 409 for concurrent update conflict', async () => {
|
||||
const updates = [{ productId: 1, newLevel: 20 }];
|
||||
Inventory.bulkUpdateInventory.mockRejectedValue(
|
||||
new Error('Concurrent update detected for product 1. Please refresh and try again.')
|
||||
);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/bulk-update')
|
||||
.send({ updates })
|
||||
.expect(409);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Concurrent update conflict');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/inventory/adjust/:productId', () => {
|
||||
it('should adjust inventory level positively', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockUpdatedInventory = {
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 25,
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 25
|
||||
})
|
||||
};
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getCurrentLevel.mockResolvedValue(20);
|
||||
Inventory.updateInventoryLevel.mockResolvedValue(mockUpdatedInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/adjust/1')
|
||||
.send({
|
||||
adjustment: 5,
|
||||
changeReason: 'Stock received',
|
||||
updatedBy: 'test-user'
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.current_level).toBe(25);
|
||||
expect(response.body.data.adjustment).toBe(5);
|
||||
expect(response.body.data.previous_level).toBe(20);
|
||||
expect(Inventory.updateInventoryLevel).toHaveBeenCalledWith(
|
||||
1,
|
||||
25,
|
||||
'Stock received',
|
||||
'test-user'
|
||||
);
|
||||
});
|
||||
|
||||
it('should adjust inventory level negatively', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
const mockUpdatedInventory = {
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 15,
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
id: 1,
|
||||
product_id: 1,
|
||||
current_level: 15
|
||||
})
|
||||
};
|
||||
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getCurrentLevel.mockResolvedValue(20);
|
||||
Inventory.updateInventoryLevel.mockResolvedValue(mockUpdatedInventory);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/adjust/1')
|
||||
.send({ adjustment: -5 })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.current_level).toBe(15);
|
||||
expect(response.body.data.adjustment).toBe(-5);
|
||||
expect(Inventory.updateInventoryLevel).toHaveBeenCalledWith(
|
||||
1,
|
||||
15,
|
||||
'Inventory adjustment: -5',
|
||||
'api-user'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 400 for adjustment that would cause negative inventory', async () => {
|
||||
const mockProduct = { id: 1, name: 'Test Product' };
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
Inventory.getCurrentLevel.mockResolvedValue(5);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/adjust/1')
|
||||
.send({ adjustment: -10 })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid adjustment');
|
||||
expect(response.body.message).toContain('would result in negative inventory');
|
||||
});
|
||||
|
||||
it('should return 400 for invalid adjustment type', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/inventory/adjust/1')
|
||||
.send({ adjustment: 'invalid' })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Invalid adjustment');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle database connection errors', async () => {
|
||||
Inventory.getInventorySummary.mockRejectedValue(new Error('Database connection failed'));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/inventory')
|
||||
.expect(500);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Failed to retrieve inventory summary');
|
||||
});
|
||||
|
||||
it('should handle inventory not found errors', async () => {
|
||||
Inventory.updateInventoryLevel.mockRejectedValue(
|
||||
new Error('Inventory record for product ID 999 not found')
|
||||
);
|
||||
|
||||
const mockProduct = { id: 999, name: 'Test Product' };
|
||||
Product.findById.mockResolvedValue(mockProduct);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/api/inventory/product/999/level')
|
||||
.send({ newLevel: 10 })
|
||||
.expect(404);
|
||||
|
||||
expect(response.body.success).toBe(false);
|
||||
expect(response.body.error).toBe('Inventory not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user