Files
flyer-crawler.projectium.com/src/controllers/stats.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

337 lines
9.6 KiB
TypeScript

// src/controllers/stats.controller.test.ts
// ============================================================================
// STATS CONTROLLER UNIT TESTS
// ============================================================================
// Unit tests for the StatsController class. These tests verify controller
// logic in isolation by mocking the admin repository.
// ============================================================================
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: () => () => {},
Route: () => () => {},
Tags: () => () => {},
Query: () => () => {},
Request: () => () => {},
SuccessResponse: () => () => {},
}));
// Mock admin repository
vi.mock('../services/db/index.db', () => ({
adminRepo: {
getMostFrequentSaleItems: vi.fn(),
},
}));
// Import mocked modules after mock definitions
import { adminRepo } from '../services/db/index.db';
import { StatsController } from './stats.controller';
// Cast mocked modules for type-safe access
const mockedAdminRepo = adminRepo as Mocked<typeof adminRepo>;
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Creates a mock Express request object.
*/
function createMockRequest(overrides: Partial<ExpressRequest> = {}): ExpressRequest {
return {
body: {},
params: {},
query: {},
log: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
...overrides,
} as unknown as ExpressRequest;
}
/**
* Creates a mock most frequent sale item.
*/
function createMockSaleItem(overrides: Record<string, unknown> = {}) {
return {
master_item_id: 1,
item_name: 'Milk 2%',
category_name: 'Dairy & Eggs',
sale_count: 15,
avg_discount_percent: 25.5,
lowest_price_cents: 299,
highest_price_cents: 450,
...overrides,
};
}
// ============================================================================
// TEST SUITE
// ============================================================================
describe('StatsController', () => {
let controller: StatsController;
beforeEach(() => {
vi.clearAllMocks();
controller = new StatsController();
});
afterEach(() => {
vi.useRealTimers();
});
// ==========================================================================
// MOST FREQUENT SALES
// ==========================================================================
describe('getMostFrequentSales()', () => {
it('should return most frequent sale items with default parameters', async () => {
// Arrange
const mockItems = [
createMockSaleItem(),
createMockSaleItem({ master_item_id: 2, item_name: 'Bread', sale_count: 12 }),
];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
const result = await controller.getMostFrequentSales(undefined, undefined, request);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toHaveLength(2);
expect(result.data[0].item_name).toBe('Milk 2%');
expect(result.data[0].sale_count).toBe(15);
}
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
30, // default days
10, // default limit
expect.anything(),
);
});
it('should use custom days parameter', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(60, undefined, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
60,
10,
expect.anything(),
);
});
it('should use custom limit parameter', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(undefined, 25, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
30,
25,
expect.anything(),
);
});
it('should cap days at 365', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(500, undefined, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
365,
10,
expect.anything(),
);
});
it('should floor days at 1', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(0, undefined, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
1,
10,
expect.anything(),
);
});
it('should cap limit at 50', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(undefined, 100, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
30,
50,
expect.anything(),
);
});
it('should floor limit at 1', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(undefined, 0, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
30,
1,
expect.anything(),
);
});
it('should handle negative values', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(-10, -5, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
1, // floored to 1
1, // floored to 1
expect.anything(),
);
});
it('should return empty array when no sale items exist', async () => {
// Arrange
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue([]);
// Act
const result = await controller.getMostFrequentSales(undefined, undefined, request);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toHaveLength(0);
}
});
it('should handle decimal values by flooring them', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
await controller.getMostFrequentSales(45.7, 15.3, request);
// Assert
expect(mockedAdminRepo.getMostFrequentSaleItems).toHaveBeenCalledWith(
45, // floored
15, // floored
expect.anything(),
);
});
});
// ==========================================================================
// PUBLIC ACCESS (NO AUTH REQUIRED)
// ==========================================================================
describe('Public access', () => {
it('should work without user authentication', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest({ user: undefined });
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
const result = await controller.getMostFrequentSales(undefined, undefined, request);
// Assert
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toHaveLength(1);
}
});
});
// ==========================================================================
// BASE CONTROLLER INTEGRATION
// ==========================================================================
describe('BaseController integration', () => {
it('should use success helper for consistent response format', async () => {
// Arrange
const mockItems = [createMockSaleItem()];
const request = createMockRequest();
mockedAdminRepo.getMostFrequentSaleItems.mockResolvedValue(mockItems);
// Act
const result = await controller.getMostFrequentSales(undefined, undefined, request);
// Assert
expect(result).toHaveProperty('success', true);
expect(result).toHaveProperty('data');
});
});
});