Skip to content

Commit

Permalink
separate requests for active and expired alerts & rename manage users…
Browse files Browse the repository at this point in the history
… to admin page
  • Loading branch information
mauberti-bc committed Oct 18, 2024
1 parent b619a20 commit 2ff34ae
Show file tree
Hide file tree
Showing 37 changed files with 1,051 additions and 510 deletions.
14 changes: 11 additions & 3 deletions api/src/models/alert-view.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from 'zod';

// Define the alert schema
export const IAlert = z.object({
alert_id: z.number(),
alert_type_id: z.number().int(),
Expand All @@ -11,11 +12,18 @@ export const IAlert = z.object({
status: z.enum(['active', 'expired'])
});

// Infer types from the schema
export type IAlert = z.infer<typeof IAlert>;
export type IAlertCreateObject = Omit<IAlert, 'alert_id' | 'status'>;
export type IAlertUpdateObject = Omit<IAlert, 'status'>;

export type IAlertCreateObject = Omit<IAlert, 'alert_id'>;

// Filter object for viewing alerts
export interface IAlertFilterObject {
recordEndDate?: string;
expiresBefore?: string;
expiresAfter?: string;
types?: string[];
}

// Define severity and status types
export type IAlertSeverity = 'info' | 'success' | 'error' | 'warning';
export type IAlertStatus = 'active' | 'expired';
141 changes: 141 additions & 0 deletions api/src/paths/alert/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import chai, { expect } from 'chai';
import { afterEach, describe, it } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { SYSTEM_IDENTITY_SOURCE } from '../../constants/database';
import { SYSTEM_ROLE } from '../../constants/roles';
import * as db from '../../database/db';
import { HTTPError } from '../../errors/http-error';
import { IAlertSeverity, IAlertStatus } from '../../models/alert-view';
import { AlertService } from '../../services/alert-service';
import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db';
import { createAlert, getAlerts } from '../alert';

chai.use(sinonChai);

describe('getAlerts', () => {
afterEach(() => {
sinon.restore();
});

describe('as a system user', () => {
it('returns a list of system alerts', async () => {
const mockAlerts = [
{
alert_id: 1,
name: 'Alert 1',
message: 'Message 1',
alert_type_id: 1,
severity: 'error' as IAlertSeverity,
status: 'active' as IAlertStatus,
data: null,
record_end_date: null
},
{
alert_id: 2,
name: 'Alert 2',
message: 'Message 2',
alert_type_id: 2,
severity: 'error' as IAlertSeverity,
status: 'active' as IAlertStatus,
data: null,
record_end_date: null
}
];

const mockDBConnection = getMockDBConnection({ open: sinon.stub(), commit: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'getAlerts').resolves(mockAlerts);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.system_user = {
system_user_id: 2,
user_identifier: 'username',
identity_source: SYSTEM_IDENTITY_SOURCE.IDIR,
user_guid: '123-456-789',
record_end_date: null,
role_ids: [3],
role_names: [SYSTEM_ROLE.SYSTEM_ADMIN],
email: '[email protected]',
family_name: 'lname',
given_name: 'fname',
display_name: 'test user',
agency: null
};

const requestHandler = getAlerts();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockRes.jsonValue).to.eql({ alerts: mockAlerts });
expect(mockDBConnection.open).to.have.been.calledOnce;
expect(mockDBConnection.commit).to.have.been.calledOnce;
});

it('handles errors gracefully', async () => {
const mockDBConnection = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'getAlerts').rejects(new Error('a test error'));

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
const requestHandler = getAlerts();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(mockDBConnection.rollback).to.have.been.calledOnce;
expect(mockDBConnection.release).to.have.been.calledOnce;
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});
});

describe('createAlert', () => {
afterEach(() => {
sinon.restore();
});

it('creates a new alert', async () => {
const mockAlert = {
name: 'New Alert',
message: 'New alert message',
alert_type_id: 1,
severity: 'medium'
};

const mockDBConnection = getMockDBConnection({ open: sinon.stub(), commit: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'createAlert').resolves(1);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.body = mockAlert;

const requestHandler = createAlert();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockRes.jsonValue).to.eql({ alert_id: 1 });
expect(mockDBConnection.open).to.have.been.calledOnce;
expect(mockDBConnection.commit).to.have.been.calledOnce;
});

it('handles errors gracefully', async () => {
const mockDBConnection = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'createAlert').rejects(new Error('a test error'));

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
const requestHandler = createAlert();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(mockDBConnection.rollback).to.have.been.calledOnce;
expect(mockDBConnection.release).to.have.been.calledOnce;
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});
23 changes: 21 additions & 2 deletions api/src/paths/alert/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ GET.apiDoc = {
nullable: true
}
}
},
{
in: 'query',
name: 'expiresBefore',
required: false,
schema: {
type: 'string',
nullable: true
}
},
{
in: 'query',
name: 'expiresAfter',
required: false,
schema: {
type: 'string',
nullable: true
}
}
],
responses: {
Expand Down Expand Up @@ -96,7 +114,7 @@ export function getAlerts(): RequestHandler {

const alertService = new AlertService(connection);

const alerts = await alertService.getAllAlerts(filterObject);
const alerts = await alertService.getAlerts(filterObject);

await connection.commit();

Expand All @@ -119,7 +137,8 @@ export function getAlerts(): RequestHandler {
*/
function parseQueryParams(req: Request<unknown, unknown, unknown, IAlertFilterObject>): IAlertFilterObject {
return {
recordEndDate: req.query.recordEndDate ?? undefined,
expiresBefore: req.query.expiresBefore ?? undefined,
expiresAfter: req.query.expiresAfter ?? undefined,
types: req.query.types ?? []
};
}
Expand Down
177 changes: 177 additions & 0 deletions api/src/paths/alert/{alertId}/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import chai, { expect } from 'chai';
import { afterEach, describe, it } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { deleteAlert } from '.';
import { getAlerts } from '..';
import { SYSTEM_IDENTITY_SOURCE } from '../../../constants/database';
import { SYSTEM_ROLE } from '../../../constants/roles';
import * as db from '../../../database/db';
import { HTTPError } from '../../../errors/http-error';
import { IAlertSeverity, IAlertStatus } from '../../../models/alert-view';
import { AlertService } from '../../../services/alert-service';
import { getMockDBConnection, getRequestHandlerMocks } from '../../../__mocks__/db';
chai.use(sinonChai);

describe('getAlerts', () => {
afterEach(() => {
sinon.restore();
});

describe('as a system user', () => {
it('returns a single system alert', async () => {
const mockAlert = {
alert_id: 1,
name: 'Alert 1',
message: 'Message 1',
alert_type_id: 1,
severity: 'error' as IAlertSeverity,
status: 'active' as IAlertStatus,
data: null,
record_end_date: null
};

const mockDBConnection = getMockDBConnection({ open: sinon.stub(), commit: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'getAlertById').resolves(mockAlert);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.params.alertId = '1';
mockReq.system_user = {
system_user_id: 2,
user_identifier: 'username',
identity_source: SYSTEM_IDENTITY_SOURCE.IDIR,
user_guid: '123-456-789',
record_end_date: null,
role_ids: [3],
role_names: [SYSTEM_ROLE.SYSTEM_ADMIN],
email: '[email protected]',
family_name: 'lname',
given_name: 'fname',
display_name: 'test user',
agency: null
};

const requestHandler = getAlerts();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockRes.jsonValue).to.eql(mockAlert);
expect(mockDBConnection.open).to.have.been.calledOnce;
expect(mockDBConnection.commit).to.have.been.calledOnce;
});

it('handles errors gracefully', async () => {
const mockDBConnection = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'getAlertById').rejects(new Error('a test error'));

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.params.alertId = '1';
const requestHandler = getAlerts();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(mockDBConnection.rollback).to.have.been.calledOnce;
expect(mockDBConnection.release).to.have.been.calledOnce;
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});
});

describe('deleteAlert', () => {
afterEach(() => {
sinon.restore();
});

describe('as a system user', () => {
it('rejects an unauthorized request', async () => {
const mockDBConnection = getMockDBConnection({ open: sinon.stub(), commit: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'deleteAlert').resolves(1);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.params.alertId = '1';
mockReq.system_user = {
system_user_id: 2,
user_identifier: 'username',
identity_source: SYSTEM_IDENTITY_SOURCE.IDIR,
user_guid: '123-456-789',
record_end_date: null,
role_ids: [1],
role_names: [SYSTEM_ROLE.PROJECT_CREATOR], // Creators cannot delete alerts
email: '[email protected]',
family_name: 'lname',
given_name: 'fname',
display_name: 'test user',
agency: null
};

const requestHandler = deleteAlert();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(mockDBConnection.rollback).to.have.been.calledOnce;
expect(mockDBConnection.release).to.have.been.calledOnce;
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});

describe('as a system admin user', () => {
it('deletes an alert and returns the alert id', async () => {
const mockDBConnection = getMockDBConnection({ open: sinon.stub(), commit: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'deleteAlert').resolves(1);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.params.alertId = '1';
mockReq.system_user = {
system_user_id: 2,
user_identifier: 'username',
identity_source: SYSTEM_IDENTITY_SOURCE.IDIR,
user_guid: '123-456-789',
record_end_date: null,
role_ids: [1],
role_names: [SYSTEM_ROLE.SYSTEM_ADMIN],
email: '[email protected]',
family_name: 'lname',
given_name: 'fname',
display_name: 'test user',
agency: null
};

const requestHandler = deleteAlert();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockRes.jsonValue).to.eql({ alert_id: 1 });
expect(mockDBConnection.open).to.have.been.calledOnce;
expect(mockDBConnection.commit).to.have.been.calledOnce;
});

it('handles errors gracefully', async () => {
const mockDBConnection = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() });
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
sinon.stub(AlertService.prototype, 'deleteAlert').rejects(new Error('a test error'));

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.params.alertId = '1';
const requestHandler = deleteAlert();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(mockDBConnection.rollback).to.have.been.calledOnce;
expect(mockDBConnection.release).to.have.been.calledOnce;
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});
});
Loading

0 comments on commit 2ff34ae

Please sign in to comment.