Files
flyer-crawler.projectium.com/src/controllers/upc.controller.test.ts
Torben Sorensen 2d2cd52011
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 3m58s
Massive Dependency Modernization Project
2026-02-13 00:34:22 -08:00

516 lines
14 KiB
TypeScript

// src/controllers/upc.controller.test.ts
// ============================================================================
// UPC CONTROLLER UNIT TESTS
// ============================================================================
// Unit tests for the UpcController class. These tests verify controller
// logic in isolation by mocking the UPC service.
// ============================================================================
import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from 'vitest';
import type { Request as ExpressRequest } from 'express';
// ============================================================================
// MOCK SETUP
// ============================================================================
// Mock tsoa decorators and Controller class
vi.mock('tsoa', () => ({
Controller: class Controller {
protected setStatus(status: number): void {
this._status = status;
}
private _status = 200;
},
Get: () => () => {},
Post: () => () => {},
Route: () => () => {},
Tags: () => () => {},
Security: () => () => {},
Path: () => () => {},
Query: () => () => {},
Body: () => () => {},
Request: () => () => {},
SuccessResponse: () => () => {},
Response: () => () => {},
}));
// Mock UPC service
vi.mock('../services/upcService.server', () => ({
scanUpc: vi.fn(),
lookupUpc: vi.fn(),
getScanHistory: vi.fn(),
getScanById: vi.fn(),
getScanStats: vi.fn(),
linkUpcToProduct: vi.fn(),
}));
// Import mocked modules after mock definitions
import * as upcService from '../services/upcService.server';
import { UpcController } from './upc.controller';
// Cast mocked modules for type-safe access
const mockedUpcService = upcService as Mocked<typeof upcService>;
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Creates a mock Express request object with authenticated user.
*/
function createMockRequest(overrides: Partial<ExpressRequest> = {}): ExpressRequest {
return {
body: {},
params: {},
query: {},
user: createMockUserProfile(),
log: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
...overrides,
} as unknown as ExpressRequest;
}
/**
* Creates a mock user profile for testing.
*/
function createMockUserProfile() {
return {
full_name: 'Test User',
role: 'user' as const,
user: {
user_id: 'test-user-id',
email: 'test@example.com',
},
};
}
/**
* Creates a mock admin user profile.
*/
function createMockAdminProfile() {
return {
full_name: 'Admin User',
role: 'admin' as const,
user: {
user_id: 'admin-user-id',
email: 'admin@example.com',
},
};
}
/**
* Creates a mock scan result.
*/
function createMockScanResult() {
return {
scan_id: 1,
upc_code: '012345678901',
product: {
product_id: 100,
name: 'Test Product',
brand: 'Test Brand',
category: 'Grocery',
description: 'A test product',
size: '500g',
upc_code: '012345678901',
image_url: null,
master_item_id: 50,
},
external_lookup: null,
confidence: 0.95,
lookup_successful: true,
is_new_product: false,
scanned_at: '2024-01-01T00:00:00.000Z',
};
}
// ============================================================================
// TEST SUITE
// ============================================================================
describe('UpcController', () => {
let controller: UpcController;
beforeEach(() => {
vi.clearAllMocks();
controller = new UpcController();
});
afterEach(() => {
vi.useRealTimers();
});
// ==========================================================================
// SCAN ENDPOINTS
// ==========================================================================
describe('scanUpc()', () => {
it('should scan a UPC code successfully', async () => {
// Arrange
const mockResult = createMockScanResult();
const request = createMockRequest();
mockedUpcService.scanUpc.mockResolvedValue(mockResult);
// Act
const result = await controller.scanUpc(
{
upc_code: '012345678901',
scan_source: 'manual_entry',
},
request,
);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.upc_code).toBe('012345678901');
expect(result.data.lookup_successful).toBe(true);
}
});
it('should reject when neither upc_code nor image provided', async () => {
// Arrange
const request = createMockRequest();
// Act & Assert
await expect(controller.scanUpc({ scan_source: 'manual_entry' }, request)).rejects.toThrow(
'Either upc_code or image_base64 must be provided.',
);
});
it('should support image-based scanning', async () => {
// Arrange
const mockResult = createMockScanResult();
const request = createMockRequest();
mockedUpcService.scanUpc.mockResolvedValue(mockResult);
// Act
const result = await controller.scanUpc(
{
image_base64: 'base64encodedimage',
scan_source: 'image_upload',
},
request,
);
// Assert
expect(result.success).toBe(true);
expect(mockedUpcService.scanUpc).toHaveBeenCalledWith(
'test-user-id',
expect.objectContaining({
image_base64: 'base64encodedimage',
scan_source: 'image_upload',
}),
expect.anything(),
);
});
it('should log scan requests', async () => {
// Arrange
const mockResult = createMockScanResult();
const mockLog = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const request = createMockRequest({ log: mockLog });
mockedUpcService.scanUpc.mockResolvedValue(mockResult);
// Act
await controller.scanUpc({ upc_code: '012345678901', scan_source: 'manual_entry' }, request);
// Assert
expect(mockLog.info).toHaveBeenCalledWith(
expect.objectContaining({
userId: 'test-user-id',
scanSource: 'manual_entry',
}),
'UPC scan request received',
);
});
});
// ==========================================================================
// LOOKUP ENDPOINTS
// ==========================================================================
describe('lookupUpc()', () => {
it('should lookup a UPC code', async () => {
// Arrange
const mockResult = {
upc_code: '012345678901',
product: { product_id: 1, name: 'Test' },
external_lookup: null,
found: true,
from_cache: false,
};
const request = createMockRequest();
mockedUpcService.lookupUpc.mockResolvedValue(mockResult);
// Act
const result = await controller.lookupUpc(request, '012345678901');
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.upc_code).toBe('012345678901');
expect(result.data.found).toBe(true);
}
});
it('should support force refresh option', async () => {
// Arrange
const mockResult = {
upc_code: '012345678901',
product: null,
external_lookup: null,
found: false,
from_cache: false,
};
const request = createMockRequest();
mockedUpcService.lookupUpc.mockResolvedValue(mockResult);
// Act
await controller.lookupUpc(request, '012345678901', true, true);
// Assert
expect(mockedUpcService.lookupUpc).toHaveBeenCalledWith(
{ upc_code: '012345678901', force_refresh: true },
expect.anything(),
);
});
});
// ==========================================================================
// HISTORY ENDPOINTS
// ==========================================================================
describe('getScanHistory()', () => {
it('should return scan history with default pagination', async () => {
// Arrange
const mockResult = {
scans: [{ scan_id: 1, upc_code: '012345678901' }],
total: 1,
};
const request = createMockRequest();
mockedUpcService.getScanHistory.mockResolvedValue(mockResult);
// Act
const result = await controller.getScanHistory(request);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.scans).toHaveLength(1);
expect(result.data.total).toBe(1);
}
expect(mockedUpcService.getScanHistory).toHaveBeenCalledWith(
expect.objectContaining({
user_id: 'test-user-id',
limit: 50,
offset: 0,
}),
expect.anything(),
);
});
it('should cap limit at 100', async () => {
// Arrange
const mockResult = { scans: [], total: 0 };
const request = createMockRequest();
mockedUpcService.getScanHistory.mockResolvedValue(mockResult);
// Act
await controller.getScanHistory(request, 200);
// Assert
expect(mockedUpcService.getScanHistory).toHaveBeenCalledWith(
expect.objectContaining({ limit: 100 }),
expect.anything(),
);
});
it('should support filtering by scan source', async () => {
// Arrange
const mockResult = { scans: [], total: 0 };
const request = createMockRequest();
mockedUpcService.getScanHistory.mockResolvedValue(mockResult);
// Act
await controller.getScanHistory(request, 50, 0, undefined, 'camera_scan');
// Assert
expect(mockedUpcService.getScanHistory).toHaveBeenCalledWith(
expect.objectContaining({ scan_source: 'camera_scan' }),
expect.anything(),
);
});
});
describe('getScanById()', () => {
it('should return a specific scan record', async () => {
// Arrange
const mockScan = {
scan_id: 1,
user_id: 'test-user-id',
upc_code: '012345678901',
product_id: 100,
scan_source: 'manual_entry',
created_at: '2024-01-01T00:00:00.000Z',
};
const request = createMockRequest();
mockedUpcService.getScanById.mockResolvedValue(mockScan);
// Act
const result = await controller.getScanById(1, request);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.scan_id).toBe(1);
}
expect(mockedUpcService.getScanById).toHaveBeenCalledWith(
1,
'test-user-id',
expect.anything(),
);
});
});
// ==========================================================================
// STATISTICS ENDPOINTS
// ==========================================================================
describe('getScanStats()', () => {
it('should return scan statistics', async () => {
// Arrange
const mockStats = {
total_scans: 100,
successful_lookups: 85,
unique_products: 50,
scans_today: 5,
scans_this_week: 20,
};
const request = createMockRequest();
mockedUpcService.getScanStats.mockResolvedValue(mockStats);
// Act
const result = await controller.getScanStats(request);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.total_scans).toBe(100);
expect(result.data.successful_lookups).toBe(85);
}
});
});
// ==========================================================================
// ADMIN ENDPOINTS
// ==========================================================================
describe('linkUpcToProduct()', () => {
it('should link UPC to product (admin)', async () => {
// Arrange
const request = createMockRequest({ user: createMockAdminProfile() });
mockedUpcService.linkUpcToProduct.mockResolvedValue(undefined);
// Act
const result = await controller.linkUpcToProduct(
{ upc_code: '012345678901', product_id: 100 },
request,
);
// Assert
expect(result).toBeUndefined();
expect(mockedUpcService.linkUpcToProduct).toHaveBeenCalledWith(
100,
'012345678901',
expect.anything(),
);
});
it('should log link operations', async () => {
// Arrange
const mockLog = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const request = createMockRequest({
user: createMockAdminProfile(),
log: mockLog,
});
mockedUpcService.linkUpcToProduct.mockResolvedValue(undefined);
// Act
await controller.linkUpcToProduct({ upc_code: '012345678901', product_id: 100 }, request);
// Assert
expect(mockLog.info).toHaveBeenCalledWith(
expect.objectContaining({
productId: 100,
upcCode: '012345678901',
}),
'UPC link request received',
);
});
});
// ==========================================================================
// BASE CONTROLLER INTEGRATION
// ==========================================================================
describe('BaseController integration', () => {
it('should use success helper for consistent response format', async () => {
// Arrange
const mockStats = { total_scans: 0 };
const request = createMockRequest();
mockedUpcService.getScanStats.mockResolvedValue(mockStats);
// Act
const result = await controller.getScanStats(request);
// Assert
expect(result).toHaveProperty('success', true);
expect(result).toHaveProperty('data');
});
it('should use noContent helper for 204 responses', async () => {
// Arrange
const request = createMockRequest({ user: createMockAdminProfile() });
mockedUpcService.linkUpcToProduct.mockResolvedValue(undefined);
// Act
const result = await controller.linkUpcToProduct(
{ upc_code: '012345678901', product_id: 1 },
request,
);
// Assert
expect(result).toBeUndefined();
});
});
});