'use strict'; /** * Integration tests – job controller (controllers/job.js) * * The job controller is a factory: require('../../controllers/job')({}) * Job._id is an auto-increment numeric field. * deleteJob wraps transaction errors with try/catch so it works on * standalone MongoDB (no replica-set needed). */ const { connectDB, disconnectDB, clearCollection } = require('./jest.setup'); const { mockApplicator, mockClient, mockPilot, mockVehicle, mockJob, mockReq, mockRes, newId, } = require('./mock_data'); let Job, Customer, Client, Pilot, Vehicle; beforeAll(async () => { await connectDB(); Customer = require('../../model/customer'); Client = require('../../model/client'); Pilot = require('../../model/pilot'); Vehicle = require('../../model/vehicle'); Job = require('../../model/job'); }); afterAll(async () => { await disconnectDB(); }); // Job controller is a factory const jobCtl = require('../../controllers/job')({}); describe('job controller – data methods', () => { let applicator, client, pilot, vehicle; beforeAll(async () => { await clearCollection(Job); applicator = await Customer.create(mockApplicator()); client = await Client.create(mockClient(applicator._id)); pilot = await Pilot.create(mockPilot(applicator._id)); vehicle = await Vehicle.create(mockVehicle(applicator._id)); }); afterAll(async () => { await clearCollection(Job); await Client.deleteMany({ _id: client._id }); await Pilot.deleteMany({ _id: pilot._id }); await Vehicle.deleteMany({ _id: vehicle._id }); await Customer.deleteMany({ _id: applicator._id }); }); const makeReq = (extra = {}) => mockReq({ uid: applicator._id, puid: applicator._id, ut: '1', userInfo: { puid: applicator._id, kind: '1', premium: 0, membership: null, markedDelete: false, }, ...extra, }); // ------------------------------------------------------------------------- describe('createJob_post', () => { it('creates a job and returns it', async () => { const body = mockJob(applicator._id); body.client = client._id; body.pilot = pilot._id; body.vehicle = vehicle._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data._id).toBeDefined(); expect(typeof res._data._id).toBe('number'); }); it('throws when puid is missing', async () => { const req = makeReq({ puid: undefined, body: mockJob(applicator._id) }); const res = mockRes(); await expect(jobCtl.createJob_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('getJobs_get', () => { it('returns jobs for the applicator', async () => { const req = makeReq({ query: {} }); const res = mockRes(); await jobCtl.getJobs_get(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); expect(res._data.length).toBeGreaterThan(0); }); it('returns empty array when puid has no jobs', async () => { const unknownId = newId(); const req = mockReq({ uid: unknownId, puid: unknownId, ut: '1', userInfo: { puid: unknownId, kind: '1', premium: 0, membership: null, markedDelete: false }, query: {}, }); const res = mockRes(); await jobCtl.getJobs_get(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.length).toBe(0); }); }); // ------------------------------------------------------------------------- describe('getJob_get', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; body.pilot = pilot._id; body.vehicle = vehicle._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns a job by numeric id', async () => { const req = makeReq({ params: { job_id: String(jobId) } }); const res = mockRes(); await jobCtl.getJob_get(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data._id).toBe(jobId); }); }); // ------------------------------------------------------------------------- describe('updateJob_put', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; body.pilot = pilot._id; body.vehicle = vehicle._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('updates a job field', async () => { const req = makeReq({ params: { job_id: String(jobId) }, body: { job: { name: 'Updated Job Name' } }, }); const res = mockRes(); await jobCtl.updateJob_put(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.name).toBe('Updated Job Name'); }); }); // ------------------------------------------------------------------------- describe('deleteJob', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; body.pilot = pilot._id; body.vehicle = vehicle._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('deletes a job (transaction error handled on standalone MongoDB)', async () => { const req = makeReq({ params: { id: String(jobId) } }); const res = mockRes(); // deleteJob wraps the transaction with try/catch – it either succeeds // fully or returns a soft-success; either way res.json is called. await jobCtl.deleteJob(req, res); expect(res.json).toHaveBeenCalled(); }); }); // ------------------------------------------------------------------------- describe('getData_post', () => { it('throws when jobId is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.getData_post(req, res)).rejects.toThrow(); }); it('returns data for a valid job id', async () => { const body = mockJob(applicator._id); body.client = client._id; const createReq = makeReq({ body }); const createRes = mockRes(); await jobCtl.createJob_post(createReq, createRes); const jobId = createRes._data._id; const req = makeReq({ body: { jobId } }); const res = mockRes(); await jobCtl.getData_post(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data).toBeTruthy(); }); }); // ------------------------------------------------------------------------- describe('getReportOps_get', () => { it('returns null when jobId is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await jobCtl.getReportOps_get(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data).toBeNull(); }); }); // ------------------------------------------------------------------------- describe('preAppReport_post', () => { it('throws when job is not found', async () => { const req = makeReq({ body: { jobId: 99999999 } }); const res = mockRes(); await expect(jobCtl.preAppReport_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('getRptVars_post', () => { it('returns null when no vars exist for the given rpt', async () => { const req = makeReq({ body: { rpt: `test_rpt_${Date.now()}` } }); const res = mockRes(); await jobCtl.getRptVars_post(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data).toBeNull(); }); }); // ------------------------------------------------------------------------- describe('setRptVars_post', () => { it('throws when rpt is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.setRptVars_post(req, res)).rejects.toThrow(); }); it('saves report vars when rpt is provided', async () => { const req = makeReq({ body: { rpt: 'test_rpt', vars: { foo: 'bar' } } }); const res = mockRes(); await jobCtl.setRptVars_post(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.ok).toBe(true); }); }); // ------------------------------------------------------------------------- describe('saveReport_post', () => { it('throws or handles missing REPORT_DIR gracefully', async () => { // saveReport_post writes to env.REPORT_DIR – in test it may not exist, so we test that the call happens const req = makeReq({ body: { rid: 'test_report_id', content: '' } }); const res = mockRes(); // Either saves successfully (REPORT_DIR exists) or throws (REPORT_DIR missing) try { await jobCtl.saveReport_post(req, res); expect(res.json).toHaveBeenCalled(); } catch (_err) { // Expected in CI without REPORT_DIR configured expect(_err).toBeDefined(); } }); }); // ------------------------------------------------------------------------- describe('preLoadReport_post', () => { it('calls next with error when jobId is missing from body', (done) => { const req = makeReq({ body: {} }); const res = mockRes(); const next = (err) => { expect(err).toBeDefined(); done(); }; jobCtl.preLoadReport_post(req, res, next); }); }); // ------------------------------------------------------------------------- describe('getUploadedFiles_post', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns empty array of uploaded files for a valid job', async () => { const req = makeReq({ body: { jobId } }); const res = mockRes(); await jobCtl.getUploadedFiles_post(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); }); }); // ------------------------------------------------------------------------- describe('importStatus_post', () => { it('throws when appId is not a valid ObjectId', async () => { const req = makeReq({ body: { appId: 'invalid-id' } }); const res = mockRes(); await expect(jobCtl.importStatus_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('importingStatus_post', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns empty array for a valid jobId with no importing files', async () => { const req = makeReq({ body: { jobId } }); const res = mockRes(); await jobCtl.importingStatus_post(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); }); }); // ------------------------------------------------------------------------- describe('deleteAppFile_post', () => { it('responds after calling delete (even with invalid appId)', async () => { const req = makeReq({ body: { appId: String(newId()) } }); const res = mockRes(); await jobCtl.deleteAppFile_post(req, res); expect(res.json).toHaveBeenCalled(); }); }); // ------------------------------------------------------------------------- describe('getJobLogs_post', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns empty array of logs for a new job', async () => { const req = makeReq({ body: { jobId } }); const res = mockRes(); await jobCtl.getJobLogs_post(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); }); }); // ------------------------------------------------------------------------- describe('assign_post', () => { it('throws when required params are missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.assign_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('assignments_post', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns { avUsers, asUsers } for a valid job', async () => { const req = makeReq({ body: { jobId } }); const res = mockRes(); await jobCtl.assignments_post(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data).toHaveProperty('avUsers'); expect(res._data).toHaveProperty('asUsers'); }); it('throws when jobId is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.assignments_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('countByClient_post', () => { it('returns the job count for a client', async () => { const req = makeReq({ body: { clientId: String(client._id) } }); const res = mockRes(); await jobCtl.countByClient_post(req, res); expect(res.json).toHaveBeenCalled(); expect(typeof res._data).toBe('number'); }); it('throws when clientId is invalid', async () => { const req = makeReq({ body: { clientId: 'bad-id' } }); const res = mockRes(); await expect(jobCtl.countByClient_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('saveMapOps_post', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('throws when mapOps params are missing', async () => { const req = makeReq({ body: { jobId, mapOps: {} } }); const res = mockRes(); await expect(jobCtl.saveMapOps_post(req, res)).rejects.toThrow(); }); it('saves map ops for a valid job', async () => { const req = makeReq({ body: { jobId, mapOps: { width: 800, height: 600, center: { lat: 0, lng: 0 }, zoom: 10 }, }, }); const res = mockRes(); await jobCtl.saveMapOps_post(req, res); expect(res.json).toHaveBeenCalled(); }); }); // ------------------------------------------------------------------------- describe('appFiles_post', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns empty array for a job with no app files', async () => { const req = makeReq({ body: { jobId } }); const res = mockRes(); await jobCtl.appFiles_post(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); }); it('throws when jobId is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.appFiles_post(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('filesdata_post', () => { it('throws when fileId is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.filesdata_post(req, res)).rejects.toThrow(); }); it('returns paginated result for a valid fileId', async () => { const req = makeReq({ body: { fileId: String(newId()) } }); const res = mockRes(); await jobCtl.filesdata_post(req, res); expect(res.json).toHaveBeenCalled(); }); }); // ------------------------------------------------------------------------- describe('getAppDataByJobId', () => { let jobId; beforeAll(async () => { const body = mockJob(applicator._id); body.client = client._id; const req = makeReq({ body }); const res = mockRes(); await jobCtl.createJob_post(req, res); jobId = res._data._id; }); it('returns null or empty result for a job with no app data', async () => { const result = await jobCtl.getAppDataByJobId(jobId, '-_id lat lon', { wApps: false, dataOp: 0, wFileId: false, withJob: false }); // No app data seeded – result should be null or empty array expect(result === null || Array.isArray(result)).toBe(true); }); }); // ------------------------------------------------------------------------- describe('fetchInvReadyJobs_post', () => { it('returns empty array when no invoice-ready jobs exist', async () => { const req = makeReq({ body: {}, userInfo: { puid: String(applicator._id), kind: '1', premium: 0, membership: null, markedDelete: false, }, }); const res = mockRes(); await jobCtl.fetchInvReadyJobs_post(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); }); }); // ------------------------------------------------------------------------- describe('searchJobs_post', () => { it('throws when byPuid is missing', async () => { const req = makeReq({ body: {} }); const res = mockRes(); await expect(jobCtl.searchJobs_post(req, res)).rejects.toThrow(); }); it('returns matching jobs for a valid byPuid', async () => { const req = makeReq({ body: { byPuid: String(applicator._id) } }); const res = mockRes(); await jobCtl.searchJobs_post(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); }); }); });