'use strict'; /** * Integration tests – api_pub controller (controllers/api_pub.js) */ const { connectDB, disconnectDB, clearCollection } = require('./jest.setup'); const { mockApplicator, mockClient, mockJob, mockReq, mockRes } = require('./mock_data'); let Job, App, AppFile, Customer, Client; beforeAll(async () => { await connectDB(); ({ Job, App, AppFile } = require('../../model')); Customer = require('../../model/customer'); Client = require('../../model/client'); }); afterAll(async () => { await disconnectDB(); }); const apiPubCtl = require('../../controllers/api_pub'); /** Generate a unique integer job id that won't collide across tests */ let _nextJobId = 900001; function nextJobId() { return _nextJobId++; } describe('api_pub controller', () => { let applicator, client, jobRecord; beforeAll(async () => { await clearCollection(Job); await clearCollection(App); await clearCollection(AppFile); applicator = await Customer.create(mockApplicator()); client = await Client.create(mockClient(applicator._id)); const jobId = nextJobId(); jobRecord = await Job.create( mockJob(applicator._id, { _id: jobId, client: client._id, }) ); }); afterAll(async () => { await clearCollection(Job); await clearCollection(App); await clearCollection(AppFile); await Customer.deleteMany({ _id: { $in: [applicator._id, client._id] } }); }); const makeReq = (extra = {}) => mockReq({ uid: applicator._id, puid: applicator._id, ut: '1', ...extra }); // ------------------------------------------------------------------------- describe('getSessions', () => { it('throws AppParamError when jobId is not a number', async () => { const req = makeReq({ params: { jobId: 'not-a-number' } }); const res = mockRes(); await expect(apiPubCtl.getSessions(req, res)).rejects.toThrow(); }); it('throws JOB_NOT_FOUND for a job that does not exist', async () => { const req = makeReq({ params: { jobId: '999999' } }); const res = mockRes(); await expect(apiPubCtl.getSessions(req, res)).rejects.toThrow(); }); it('throws AppAuthError when uid does not own the job', async () => { const other = await Customer.create(mockApplicator()); const req = mockReq({ uid: other._id, puid: other._id, params: { jobId: String(jobRecord._id) }, }); const res = mockRes(); await expect(apiPubCtl.getSessions(req, res)).rejects.toThrow(); await Customer.deleteMany({ _id: other._id }); }); it('returns empty data array when job has no apps', async () => { const req = makeReq({ params: { jobId: String(jobRecord._id) } }); const res = mockRes(); await apiPubCtl.getSessions(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.jobId).toBe(jobRecord._id); expect(res._data.data).toEqual([]); }); it('returns sessions when apps exist for the job', async () => { const app = await App.create({ _id: require('mongoose').Types.ObjectId(), jobId: jobRecord._id, fileName: 'testflight.agn', fileSize: 0, markedDelete: false, }); const req = makeReq({ params: { jobId: String(jobRecord._id) } }); const res = mockRes(); await apiPubCtl.getSessions(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.data.length).toBe(1); expect(String(res._data.data[0].sessionId)).toBe(String(app._id)); await App.deleteMany({ _id: app._id }); }); }); // ------------------------------------------------------------------------- describe('getAreas', () => { it('throws when jobId is not a number', async () => { const req = makeReq({ params: { jobId: 'abc' } }); const res = mockRes(); await expect(apiPubCtl.getAreas(req, res)).rejects.toThrow(); }); it('throws JOB_NOT_FOUND when job does not exist', async () => { const req = makeReq({ params: { jobId: '999998' } }); const res = mockRes(); await expect(apiPubCtl.getAreas(req, res)).rejects.toThrow(); }); it('returns a GeoJSON FeatureCollection with empty features when job has no areas', async () => { const req = makeReq({ params: { jobId: String(jobRecord._id) } }); const res = mockRes(); await apiPubCtl.getAreas(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.type).toBe('FeatureCollection'); expect(Array.isArray(res._data.features)).toBe(true); expect(res._data.features.length).toBe(0); }); it('returns area features when sprayAreas are defined on the job', async () => { const area = { properties: { name: 'Block A', area: 2.5 }, geometry: { type: 'Polygon', coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], }, }; await Job.findByIdAndUpdate(jobRecord._id, { sprayAreas: [area] }); const req = makeReq({ params: { jobId: String(jobRecord._id) } }); const res = mockRes(); await apiPubCtl.getAreas(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.features.length).toBe(1); expect(res._data.features[0].properties.name).toBe('Block A'); // Cleanup await Job.findByIdAndUpdate(jobRecord._id, { sprayAreas: [] }); }); }); // ------------------------------------------------------------------------- describe('getSessionRecords', () => { it('throws when jobId is not a number', async () => { const mongoose = require('mongoose'); const req = makeReq({ params: { jobId: 'abc', fileId: new mongoose.Types.ObjectId().toHexString() }, }); const res = mockRes(); await expect(apiPubCtl.getSessionRecords(req, res)).rejects.toThrow(); }); it('throws when fileId is not a valid ObjectId', async () => { const req = makeReq({ params: { jobId: String(jobRecord._id), fileId: 'invalid' }, }); const res = mockRes(); await expect(apiPubCtl.getSessionRecords(req, res)).rejects.toThrow(); }); it('throws NOT_FOUND when AppFile does not exist', async () => { const mongoose = require('mongoose'); const req = makeReq({ params: { jobId: String(jobRecord._id), fileId: new mongoose.Types.ObjectId().toHexString(), }, }); const res = mockRes(); await expect(apiPubCtl.getSessionRecords(req, res)).rejects.toThrow(); }); }); });