/** * Unit tests for GET /api/ivanti/todo-queue/ticket-links endpoint * Validates: Requirements 6.3, 6.4 */ const http = require('http'); const express = require('express'); // Mock auth middleware jest.mock('../middleware/auth', () => ({ requireAuth: () => (req, _res, next) => { req.user = { id: 7, username: 'testuser' }; next(); }, requireGroup: () => (_req, _res, next) => next(), })); // Mock audit log jest.mock('../helpers/auditLog', () => jest.fn()); // Mock the db pool jest.mock('../db', () => ({ query: jest.fn(() => Promise.resolve({ rows: [] })), connect: jest.fn(), })); const pool = require('../db'); const createIvantiTodoQueueRouter = require('../routes/ivantiTodoQueue'); /** * Helper: send an HTTP request and return { statusCode, body }. */ function request(server, method, path) { return new Promise((resolve, reject) => { const addr = server.address(); const options = { hostname: '127.0.0.1', port: addr.port, path, method, headers: { 'Content-Type': 'application/json' }, }; const req = http.request(options, (res) => { const chunks = []; res.on('data', (chunk) => chunks.push(chunk)); res.on('end', () => { const raw = Buffer.concat(chunks).toString(); let body; try { body = JSON.parse(raw); } catch { body = raw; } resolve({ statusCode: res.statusCode, body }); }); }); req.on('error', reject); req.end(); }); } describe('GET /api/ivanti/todo-queue/ticket-links', () => { let app; let server; beforeAll((done) => { app = express(); app.use(express.json()); app.use('/api/ivanti/todo-queue', createIvantiTodoQueueRouter()); server = app.listen(0, '127.0.0.1', done); }); afterAll((done) => { server.close(done); }); beforeEach(() => { jest.clearAllMocks(); }); it('returns an empty links object when no associations exist', async () => { pool.query.mockResolvedValueOnce({ rows: [] }); const res = await request(server, 'GET', '/api/ivanti/todo-queue/ticket-links'); expect(res.statusCode).toBe(200); expect(res.body).toEqual({ links: {} }); }); it('returns a map of queue_item_id to ticket info', async () => { pool.query.mockResolvedValueOnce({ rows: [ { queue_item_id: 12, ticket_key: 'VULN-789', jira_url: 'https://jira.example.com/browse/VULN-789' }, { queue_item_id: 15, ticket_key: 'VULN-789', jira_url: 'https://jira.example.com/browse/VULN-789' }, { queue_item_id: 22, ticket_key: 'VULN-801', jira_url: 'https://jira.example.com/browse/VULN-801' }, ], }); const res = await request(server, 'GET', '/api/ivanti/todo-queue/ticket-links'); expect(res.statusCode).toBe(200); expect(res.body).toEqual({ links: { '12': { ticket_key: 'VULN-789', jira_url: 'https://jira.example.com/browse/VULN-789' }, '15': { ticket_key: 'VULN-789', jira_url: 'https://jira.example.com/browse/VULN-789' }, '22': { ticket_key: 'VULN-801', jira_url: 'https://jira.example.com/browse/VULN-801' }, }, }); }); it('filters by the authenticated user ID', async () => { pool.query.mockResolvedValueOnce({ rows: [] }); await request(server, 'GET', '/api/ivanti/todo-queue/ticket-links'); const [sql, params] = pool.query.mock.calls[0]; expect(sql).toContain('q.user_id = $1'); expect(params).toEqual([7]); }); it('joins jira_ticket_queue_items with jira_tickets and ivanti_todo_queue', async () => { pool.query.mockResolvedValueOnce({ rows: [] }); await request(server, 'GET', '/api/ivanti/todo-queue/ticket-links'); const [sql] = pool.query.mock.calls[0]; expect(sql).toContain('jira_ticket_queue_items'); expect(sql).toContain('JOIN jira_tickets'); expect(sql).toContain('JOIN ivanti_todo_queue'); }); it('returns 500 on database error', async () => { pool.query.mockRejectedValueOnce(new Error('DB connection failed')); const res = await request(server, 'GET', '/api/ivanti/todo-queue/ticket-links'); expect(res.statusCode).toBe(500); expect(res.body).toEqual({ error: 'Internal server error.' }); }); });