589 lines
19 KiB
JavaScript
589 lines
19 KiB
JavaScript
'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: '<report/>' } });
|
||
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);
|
||
});
|
||
});
|
||
});
|