Skip to content

Commit 5f896a0

Browse files
committed
Add unit tests for Queue service
1 parent 06d7b69 commit 5f896a0

File tree

3 files changed

+384
-0
lines changed

3 files changed

+384
-0
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// ===== IMPORTS =====
2+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3+
import { QueueService } from '../../../../src/services/orchestrator/queues';
4+
import { ApiClient } from '../../../../src/core/http/api-client';
5+
import { PaginationHelpers } from '../../../../src/utils/pagination/helpers';
6+
import {
7+
createMockRawQueue,
8+
createMockTransformedQueueCollection
9+
} from '../../../utils/mocks/queues';
10+
import { createServiceTestDependencies, createMockApiClient } from '../../../utils/setup';
11+
import { createMockError } from '../../../utils/mocks/core';
12+
import {
13+
QueueGetAllOptions,
14+
QueueGetByIdOptions
15+
} from '../../../../src/models/orchestrator/queues.types';
16+
import { QUEUE_TEST_CONSTANTS } from '../../../utils/constants/queues';
17+
import { TEST_CONSTANTS } from '../../../utils/constants/common';
18+
import { QUEUE_ENDPOINTS } from '../../../../src/utils/constants/endpoints';
19+
import { FOLDER_ID } from '../../../../src/utils/constants/headers';
20+
21+
// ===== MOCKING =====
22+
// Mock the dependencies
23+
vi.mock('../../../../src/core/http/api-client');
24+
25+
// Import mock objects using vi.hoisted() - this ensures they're available before vi.mock() calls
26+
const mocks = vi.hoisted(() => {
27+
// Import/re-export the mock utilities from core
28+
return import('../../../utils/mocks/core');
29+
});
30+
31+
// Setup mocks at module level
32+
// NOTE: We do NOT mock transformData
33+
vi.mock('../../../../src/utils/pagination/helpers', async () => (await mocks).mockPaginationHelpers);
34+
35+
// ===== TEST SUITE =====
36+
describe('QueueService Unit Tests', () => {
37+
let queueService: QueueService;
38+
let mockApiClient: any;
39+
40+
beforeEach(() => {
41+
// Create mock instances using centralized setup
42+
const { config, executionContext, tokenManager } = createServiceTestDependencies();
43+
mockApiClient = createMockApiClient();
44+
45+
// Mock the ApiClient constructor
46+
vi.mocked(ApiClient).mockImplementation(() => mockApiClient);
47+
48+
// Reset pagination helpers mock before each test
49+
vi.mocked(PaginationHelpers.getAll).mockReset();
50+
51+
queueService = new QueueService(config, executionContext, tokenManager);
52+
});
53+
54+
afterEach(() => {
55+
vi.clearAllMocks();
56+
});
57+
58+
describe('getById', () => {
59+
it('should get queue by ID successfully with all fields mapped correctly', async () => {
60+
const mockQueue = createMockRawQueue();
61+
62+
mockApiClient.get.mockResolvedValue(mockQueue);
63+
64+
const result = await queueService.getById(
65+
QUEUE_TEST_CONSTANTS.QUEUE_ID,
66+
TEST_CONSTANTS.FOLDER_ID
67+
);
68+
69+
// Verify the result
70+
expect(result).toBeDefined();
71+
expect(result.id).toBe(QUEUE_TEST_CONSTANTS.QUEUE_ID);
72+
expect(result.name).toBe(QUEUE_TEST_CONSTANTS.QUEUE_NAME);
73+
expect(result.riskSlaInMinutes).toBe(QUEUE_TEST_CONSTANTS.RISK_SLA_IN_MINUTES);
74+
75+
// Verify the API call has correct endpoint and headers
76+
expect(mockApiClient.get).toHaveBeenCalledWith(
77+
QUEUE_ENDPOINTS.GET_BY_ID(QUEUE_TEST_CONSTANTS.QUEUE_ID),
78+
expect.objectContaining({
79+
headers: expect.objectContaining({
80+
[FOLDER_ID]: TEST_CONSTANTS.FOLDER_ID.toString()
81+
})
82+
})
83+
);
84+
85+
// Verify field transformations
86+
// CreationTime -> createdTime
87+
expect(result.createdTime).toBe(QUEUE_TEST_CONSTANTS.CREATED_TIME);
88+
expect((result as any).CreationTime).toBeUndefined(); // Original field should be removed
89+
90+
// OrganizationUnitId -> folderId
91+
expect(result.folderId).toBe(TEST_CONSTANTS.FOLDER_ID);
92+
expect((result as any).OrganizationUnitId).toBeUndefined(); // Original field should be removed
93+
94+
// OrganizationUnitFullyQualifiedName -> folderName
95+
expect(result.folderName).toBe(TEST_CONSTANTS.FOLDER_NAME);
96+
expect((result as any).OrganizationUnitFullyQualifiedName).toBeUndefined(); // Original field should be removed
97+
});
98+
99+
it('should get queue with options successfully', async () => {
100+
const mockQueue = createMockRawQueue();
101+
mockApiClient.get.mockResolvedValue(mockQueue);
102+
103+
const options: QueueGetByIdOptions = {
104+
select: QUEUE_TEST_CONSTANTS.ODATA_SELECT_FIELDS
105+
};
106+
107+
await queueService.getById(
108+
QUEUE_TEST_CONSTANTS.QUEUE_ID,
109+
TEST_CONSTANTS.FOLDER_ID,
110+
options
111+
);
112+
113+
// Verify API call has options with OData prefix
114+
expect(mockApiClient.get).toHaveBeenCalledWith(
115+
QUEUE_ENDPOINTS.GET_BY_ID(QUEUE_TEST_CONSTANTS.QUEUE_ID),
116+
expect.objectContaining({
117+
params: expect.objectContaining({
118+
'$select': QUEUE_TEST_CONSTANTS.ODATA_SELECT_FIELDS
119+
})
120+
})
121+
);
122+
});
123+
124+
it('should handle API errors', async () => {
125+
const error = createMockError(QUEUE_TEST_CONSTANTS.ERROR_QUEUE_NOT_FOUND);
126+
mockApiClient.get.mockRejectedValue(error);
127+
128+
await expect(queueService.getById(
129+
QUEUE_TEST_CONSTANTS.QUEUE_ID,
130+
TEST_CONSTANTS.FOLDER_ID
131+
)).rejects.toThrow(QUEUE_TEST_CONSTANTS.ERROR_QUEUE_NOT_FOUND);
132+
});
133+
});
134+
135+
describe('getAll', () => {
136+
it('should return all queues with proper field transformations', async () => {
137+
const mockResponse = createMockTransformedQueueCollection();
138+
139+
vi.mocked(PaginationHelpers.getAll).mockResolvedValue(mockResponse);
140+
141+
const result = await queueService.getAll();
142+
143+
// Verify PaginationHelpers.getAll was called
144+
expect(PaginationHelpers.getAll).toHaveBeenCalledWith(
145+
expect.objectContaining({
146+
serviceAccess: expect.any(Object),
147+
getEndpoint: expect.any(Function),
148+
transformFn: expect.any(Function),
149+
pagination: expect.any(Object)
150+
}),
151+
undefined
152+
);
153+
154+
expect(result).toEqual(mockResponse);
155+
156+
// Verify the endpoint function returns the correct endpoint
157+
const paginationConfig = vi.mocked(PaginationHelpers.getAll).mock.calls[0][0];
158+
const endpoint = paginationConfig.getEndpoint();
159+
expect(endpoint).toBe(QUEUE_ENDPOINTS.GET_ALL);
160+
});
161+
162+
it('should return queues filtered by folder ID', async () => {
163+
const mockResponse = createMockTransformedQueueCollection();
164+
165+
vi.mocked(PaginationHelpers.getAll).mockResolvedValue(mockResponse);
166+
167+
const options: QueueGetAllOptions = {
168+
folderId: TEST_CONSTANTS.FOLDER_ID
169+
};
170+
171+
const result = await queueService.getAll(options);
172+
173+
// Verify PaginationHelpers.getAll was called with folder options
174+
expect(PaginationHelpers.getAll).toHaveBeenCalledWith(
175+
expect.any(Object),
176+
expect.objectContaining({
177+
folderId: TEST_CONSTANTS.FOLDER_ID
178+
})
179+
);
180+
181+
expect(result).toEqual(mockResponse);
182+
183+
// Verify the endpoint function returns the correct endpoint when called with a folderId
184+
const paginationConfig = vi.mocked(PaginationHelpers.getAll).mock.calls[0][0];
185+
const endpoint = paginationConfig.getEndpoint(TEST_CONSTANTS.FOLDER_ID);
186+
expect(endpoint).toBe(QUEUE_ENDPOINTS.GET_BY_FOLDER);
187+
});
188+
189+
it('should return paginated queues when pagination options provided', async () => {
190+
const mockResponse = createMockTransformedQueueCollection(100, {
191+
totalCount: 100,
192+
hasNextPage: true,
193+
nextCursor: TEST_CONSTANTS.NEXT_CURSOR,
194+
previousCursor: null,
195+
currentPage: 1,
196+
totalPages: 10
197+
});
198+
199+
vi.mocked(PaginationHelpers.getAll).mockResolvedValue(mockResponse);
200+
201+
const options: QueueGetAllOptions = {
202+
pageSize: TEST_CONSTANTS.PAGE_SIZE
203+
};
204+
205+
const result = await queueService.getAll(options) as any;
206+
207+
// Verify PaginationHelpers.getAll was called with pagination options
208+
expect(PaginationHelpers.getAll).toHaveBeenCalledWith(
209+
expect.any(Object),
210+
expect.objectContaining({
211+
pageSize: TEST_CONSTANTS.PAGE_SIZE
212+
})
213+
);
214+
215+
expect(result).toEqual(mockResponse);
216+
expect(result.hasNextPage).toBe(true);
217+
expect(result.nextCursor).toBe(TEST_CONSTANTS.NEXT_CURSOR);
218+
});
219+
220+
it('should handle API errors', async () => {
221+
const error = createMockError(TEST_CONSTANTS.ERROR_MESSAGE);
222+
vi.mocked(PaginationHelpers.getAll).mockRejectedValue(error);
223+
224+
await expect(queueService.getAll()).rejects.toThrow(TEST_CONSTANTS.ERROR_MESSAGE);
225+
});
226+
});
227+
});

tests/utils/constants/queues.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Queue service test constants
3+
* Queue-specific constants only
4+
*/
5+
6+
export const QUEUE_TEST_CONSTANTS = {
7+
// Queue IDs
8+
QUEUE_ID: 456,
9+
10+
// Queue Metadata
11+
QUEUE_NAME: 'InvoiceProcessing',
12+
QUEUE_KEY: '87654321-4321-4321-4321-cba987654321',
13+
QUEUE_DESCRIPTION: 'Queue for processing invoices',
14+
15+
// Queue Configuration
16+
MAX_NUMBER_OF_RETRIES: 1,
17+
ACCEPT_AUTOMATICALLY_RETRY: true,
18+
RETRY_ABANDONED_ITEMS: false,
19+
ENFORCE_UNIQUE_REFERENCE: false,
20+
ENCRYPTED: false,
21+
SLA_IN_MINUTES: 60,
22+
RISK_SLA_IN_MINUTES: 30,
23+
FOLDERS_COUNT: 1,
24+
25+
// IDs and References
26+
PROCESS_SCHEDULE_ID: 789,
27+
RELEASE_ID: 321,
28+
29+
// Flags
30+
IS_PROCESS_IN_CURRENT_FOLDER: true,
31+
32+
// JSON Schemas
33+
SPECIFIC_DATA_JSON_SCHEMA: '{"type": "object", "properties": {"invoiceNumber": {"type": "string"}}}',
34+
OUTPUT_DATA_JSON_SCHEMA: '{"type": "object", "properties": {"status": {"type": "string"}}}',
35+
ANALYTICS_DATA_JSON_SCHEMA: null,
36+
37+
// Timestamps
38+
CREATED_TIME: '2023-11-10T09:00:00Z',
39+
40+
// Error Messages
41+
ERROR_QUEUE_NOT_FOUND: 'Queue not found',
42+
43+
// OData Parameters
44+
ODATA_SELECT_FIELDS: 'id,name,description',
45+
} as const;

tests/utils/mocks/queues.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Queue service mock utilities - Queue-specific mocks only
3+
* Uses generic utilities from core.ts for base functionality
4+
*/
5+
import { QueueGetResponse } from '../../../src/models/orchestrator/queues.types';
6+
import { createMockBaseResponse, createMockCollection } from './core';
7+
import { QUEUE_TEST_CONSTANTS } from '../constants/queues';
8+
import { TEST_CONSTANTS } from '../constants/common';
9+
10+
/**
11+
* Creates a mock queue with RAW API format (before transformation)
12+
* Uses PascalCase field names and raw API timestamp fields that need transformation
13+
*
14+
* @param overrides - Optional overrides for specific fields
15+
* @returns Raw queue data as it comes from the API (before transformation)
16+
*/
17+
export const createMockRawQueue = (overrides: Partial<any> = {}): any => {
18+
return createMockBaseResponse({
19+
Id: QUEUE_TEST_CONSTANTS.QUEUE_ID,
20+
Name: QUEUE_TEST_CONSTANTS.QUEUE_NAME,
21+
Key: QUEUE_TEST_CONSTANTS.QUEUE_KEY,
22+
Description: QUEUE_TEST_CONSTANTS.QUEUE_DESCRIPTION,
23+
MaxNumberOfRetries: QUEUE_TEST_CONSTANTS.MAX_NUMBER_OF_RETRIES,
24+
AcceptAutomaticallyRetry: QUEUE_TEST_CONSTANTS.ACCEPT_AUTOMATICALLY_RETRY,
25+
RetryAbandonedItems: QUEUE_TEST_CONSTANTS.RETRY_ABANDONED_ITEMS,
26+
EnforceUniqueReference: QUEUE_TEST_CONSTANTS.ENFORCE_UNIQUE_REFERENCE,
27+
Encrypted: QUEUE_TEST_CONSTANTS.ENCRYPTED,
28+
SpecificDataJsonSchema: QUEUE_TEST_CONSTANTS.SPECIFIC_DATA_JSON_SCHEMA,
29+
OutputDataJsonSchema: QUEUE_TEST_CONSTANTS.OUTPUT_DATA_JSON_SCHEMA,
30+
AnalyticsDataJsonSchema: QUEUE_TEST_CONSTANTS.ANALYTICS_DATA_JSON_SCHEMA,
31+
ProcessScheduleId: QUEUE_TEST_CONSTANTS.PROCESS_SCHEDULE_ID,
32+
SlaInMinutes: QUEUE_TEST_CONSTANTS.SLA_IN_MINUTES,
33+
RiskSlaInMinutes: QUEUE_TEST_CONSTANTS.RISK_SLA_IN_MINUTES,
34+
ReleaseId: QUEUE_TEST_CONSTANTS.RELEASE_ID,
35+
IsProcessInCurrentFolder: QUEUE_TEST_CONSTANTS.IS_PROCESS_IN_CURRENT_FOLDER,
36+
FoldersCount: QUEUE_TEST_CONSTANTS.FOLDERS_COUNT,
37+
// Using raw API field names that should be transformed
38+
CreationTime: QUEUE_TEST_CONSTANTS.CREATED_TIME,
39+
OrganizationUnitId: TEST_CONSTANTS.FOLDER_ID,
40+
OrganizationUnitFullyQualifiedName: TEST_CONSTANTS.FOLDER_NAME,
41+
}, overrides);
42+
};
43+
44+
/**
45+
* Creates a basic queue object with TRANSFORMED data (not raw API format)
46+
*
47+
* @param overrides - Optional overrides for specific fields
48+
* @returns Queue with transformed field names (camelCase)
49+
*/
50+
export const createBasicQueue = (overrides: Partial<QueueGetResponse> = {}): QueueGetResponse => {
51+
return createMockBaseResponse({
52+
id: QUEUE_TEST_CONSTANTS.QUEUE_ID,
53+
name: QUEUE_TEST_CONSTANTS.QUEUE_NAME,
54+
key: QUEUE_TEST_CONSTANTS.QUEUE_KEY,
55+
description: QUEUE_TEST_CONSTANTS.QUEUE_DESCRIPTION,
56+
maxNumberOfRetries: QUEUE_TEST_CONSTANTS.MAX_NUMBER_OF_RETRIES,
57+
acceptAutomaticallyRetry: QUEUE_TEST_CONSTANTS.ACCEPT_AUTOMATICALLY_RETRY,
58+
retryAbandonedItems: QUEUE_TEST_CONSTANTS.RETRY_ABANDONED_ITEMS,
59+
enforceUniqueReference: QUEUE_TEST_CONSTANTS.ENFORCE_UNIQUE_REFERENCE,
60+
encrypted: QUEUE_TEST_CONSTANTS.ENCRYPTED,
61+
specificDataJsonSchema: QUEUE_TEST_CONSTANTS.SPECIFIC_DATA_JSON_SCHEMA,
62+
outputDataJsonSchema: QUEUE_TEST_CONSTANTS.OUTPUT_DATA_JSON_SCHEMA,
63+
analyticsDataJsonSchema: QUEUE_TEST_CONSTANTS.ANALYTICS_DATA_JSON_SCHEMA,
64+
processScheduleId: QUEUE_TEST_CONSTANTS.PROCESS_SCHEDULE_ID,
65+
slaInMinutes: QUEUE_TEST_CONSTANTS.SLA_IN_MINUTES,
66+
riskSlaInMinutes: QUEUE_TEST_CONSTANTS.RISK_SLA_IN_MINUTES,
67+
releaseId: QUEUE_TEST_CONSTANTS.RELEASE_ID,
68+
isProcessInCurrentFolder: QUEUE_TEST_CONSTANTS.IS_PROCESS_IN_CURRENT_FOLDER,
69+
foldersCount: QUEUE_TEST_CONSTANTS.FOLDERS_COUNT,
70+
// Using transformed field names (camelCase)
71+
createdTime: QUEUE_TEST_CONSTANTS.CREATED_TIME,
72+
folderId: TEST_CONSTANTS.FOLDER_ID,
73+
folderName: TEST_CONSTANTS.FOLDER_NAME,
74+
}, overrides);
75+
};
76+
77+
78+
/**
79+
* Creates a mock transformed queue collection response as returned by PaginationHelpers.getAll
80+
*
81+
* @param count - Number of queues to include (defaults to 1)
82+
* @param options - Additional options like totalCount, pagination details
83+
* @returns Mock transformed queue collection with items array
84+
*/
85+
export const createMockTransformedQueueCollection = (
86+
count: number = 1,
87+
options?: {
88+
totalCount?: number;
89+
hasNextPage?: boolean;
90+
nextCursor?: string;
91+
previousCursor?: string | null;
92+
currentPage?: number;
93+
totalPages?: number;
94+
}
95+
): any => {
96+
const items = createMockCollection(count, (index) => createBasicQueue({
97+
id: QUEUE_TEST_CONSTANTS.QUEUE_ID + index,
98+
name: `${QUEUE_TEST_CONSTANTS.QUEUE_NAME}${index + 1}`,
99+
// Generate unique GUIDs for each queue
100+
key: `${index}-${QUEUE_TEST_CONSTANTS.QUEUE_KEY}`
101+
}));
102+
103+
return createMockBaseResponse({
104+
items,
105+
totalCount: options?.totalCount || count,
106+
...(options?.hasNextPage !== undefined && { hasNextPage: options.hasNextPage }),
107+
...(options?.nextCursor && { nextCursor: options.nextCursor }),
108+
...(options?.previousCursor !== undefined && { previousCursor: options.previousCursor }),
109+
...(options?.currentPage !== undefined && { currentPage: options.currentPage }),
110+
...(options?.totalPages !== undefined && { totalPages: options.totalPages })
111+
});
112+
};

0 commit comments

Comments
 (0)