Skip to content

Commit

Permalink
Add option to deep GET /proposals/{proposalId}
Browse files Browse the repository at this point in the history
Simplify implementation further based on review feedback.

Issue #101 Implement GET /proposals/{proposalId} endpoint
  • Loading branch information
bickelj committed Feb 2, 2023
1 parent c8097c0 commit 0d56542
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 354 deletions.
17 changes: 15 additions & 2 deletions src/__tests__/proposals.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ describe('/proposals', () => {
details: [
{
name: 'NotFoundError',
status: 404,
},
],
});
Expand Down Expand Up @@ -467,7 +466,6 @@ describe('/proposals', () => {
details: [
{
name: 'NotFoundError',
status: 404,
},
],
});
Expand Down Expand Up @@ -556,6 +554,21 @@ describe('/proposals', () => {
});
});

it('returns 500 UnknownError if a generic Error is thrown when selecting and includeFieldsAndValues=true', async () => {
jest.spyOn(db, 'sql')
.mockImplementationOnce(async () => {
throw new Error('This is unexpected');
});
const result = await agent
.get('/proposals/9004?includeFieldsAndValues=true')
.set(dummyApiKey)
.expect(500);
expect(result.body).toMatchObject({
name: 'UnknownError',
details: expect.any(Array) as unknown[],
});
});

describe('POST /', () => {
it('creates exactly one proposal', async () => {
await db.query(`
Expand Down
10 changes: 5 additions & 5 deletions src/errors/AuthenticationError.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ErrorWithStatus } from './ErrorWithStatus';

export class AuthenticationError extends ErrorWithStatus {
public constructor(message: string) {
super(message, 401);
export class AuthenticationError extends Error {
public constructor(
message: string,
) {
super(message);
this.name = this.constructor.name;
}
}
12 changes: 0 additions & 12 deletions src/errors/ErrorWithStatus.ts

This file was deleted.

10 changes: 5 additions & 5 deletions src/errors/NotFoundError.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ErrorWithStatus } from './ErrorWithStatus';

export class NotFoundError extends ErrorWithStatus {
public constructor(message: string) {
super(message, 404);
export class NotFoundError extends Error {
public constructor(
message: string,
) {
super(message);
this.name = this.constructor.name;
}
}
1 change: 0 additions & 1 deletion src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from './AuthenticationError';
export * from './DatabaseError';
export * from './ErrorWithStatus';
export * from './InputConflictError';
export * from './InputValidationError';
export * from './InternalValidationError';
Expand Down
244 changes: 52 additions & 192 deletions src/handlers/__tests__/proposals.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,21 @@
import {
mergeApplicationFormFields,
mergeProposalFieldValues,
joinApplicationFormFieldsToProposalFieldValues,
joinProposalFieldValuesToProposalVersion,
} from '../proposalsHandlers';
import type {
ApplicationFormField,
Proposal,
ProposalFieldValue,
ProposalVersion,
} from '../../types';

describe('mergeProposalFieldValues', () => {
it('should merge proposal values into proposal versions', () => {
const proposal: Proposal = {
id: 3019,
applicantId: 3023,
opportunityId: 3037,
externalId: '3041',
createdAt: new Date('2023-01-23T11:49:00-0600'),
versions: [
{
id: 3049,
proposalId: 3019,
applicationFormId: 3061,
version: 3067,
createdAt: new Date('2023-01-23T12:08:00-0600'),
},
{
id: 3079,
proposalId: 3019,
applicationFormId: 3083,
version: 3089,
createdAt: new Date('2023-01-23T12:09:00-0600'),
},
],
it('should merge proposal values into proposal version', () => {
const proposalVersion: ProposalVersion = {
id: 3049,
proposalId: 3019,
applicationFormId: 3061,
version: 3067,
createdAt: new Date('2023-01-23T12:08:00-0600'),
};
const fieldValues: ProposalFieldValue[] = [
{
Expand Down Expand Up @@ -68,183 +51,54 @@ describe('mergeProposalFieldValues', () => {
createdAt: new Date('2023-01-23T12:18:00-0600'),
},
];
const proposalWithFieldValues = mergeProposalFieldValues(proposal, fieldValues);
const expectedProposal = {
id: 3019,
applicantId: 3023,
opportunityId: 3037,
externalId: '3041',
createdAt: new Date('2023-01-23T11:49:00-0600'),
versions: [
const proposalVersionWithFieldValues = joinProposalFieldValuesToProposalVersion(
proposalVersion,
fieldValues,
);
const expectedProposalVersion = {
id: 3049,
proposalId: 3019,
applicationFormId: 3061,
version: 3067,
createdAt: new Date('2023-01-23T12:08:00-0600'),
fieldValues: [
{
id: 3049,
proposalId: 3019,
applicationFormId: 3061,
version: 3067,
createdAt: new Date('2023-01-23T12:08:00-0600'),
fieldValues: [
{
id: 3109,
proposalVersionId: 3049,
applicationFormFieldId: 3119,
position: 3121,
value: 'Three thousand one hundred thirty seven',
createdAt: new Date('2023-01-23T12:14:00-0600'),
},
{
id: 3163,
proposalVersionId: 3049,
applicationFormFieldId: 3167,
position: 3169,
value: 'Three thousand one hundred eighty one',
createdAt: new Date('2023-01-23T12:16:00-0600'),
},
],
id: 3109,
proposalVersionId: 3049,
applicationFormFieldId: 3119,
position: 3121,
value: 'Three thousand one hundred thirty seven',
createdAt: new Date('2023-01-23T12:14:00-0600'),
},
{
id: 3079,
proposalId: 3019,
applicationFormId: 3083,
version: 3089,
createdAt: new Date('2023-01-23T12:09:00-0600'),
fieldValues: [
{
id: 3187,
proposalVersionId: 3079,
applicationFormFieldId: 3191,
position: 3203,
value: 'Three thousand two hundred nine',
createdAt: new Date('2023-01-23T12:17:00-0600'),
},
{
id: 3217,
proposalVersionId: 3079,
applicationFormFieldId: 3251,
position: 3253,
value: 'Three thousand two hundred fifty seven',
createdAt: new Date('2023-01-23T12:18:00-0600'),
},
],
id: 3163,
proposalVersionId: 3049,
applicationFormFieldId: 3167,
position: 3169,
value: 'Three thousand one hundred eighty one',
createdAt: new Date('2023-01-23T12:16:00-0600'),
},
],
};
expect(proposalWithFieldValues).toEqual(expectedProposal);
expect(proposalVersionWithFieldValues).toEqual(expectedProposalVersion);
});

it('should return a proposal like the one given when no versions are present', () => {
const proposal: Proposal = {
it('should return a proposal version like the one given when no field values are given', () => {
const proposalVersion: ProposalVersion = {
id: 3413,
applicantId: 3433,
opportunityId: 3449,
externalId: 'Three thousand four hundred fifty seven',
proposalId: 3433,
applicationFormId: 3449,
version: 3457,
createdAt: new Date('2023-01-23T13:54:00-0600'),
};
expect(mergeProposalFieldValues(proposal, [])).toEqual({
expect(joinProposalFieldValuesToProposalVersion(proposalVersion, [])).toEqual({
id: 3413,
applicantId: 3433,
opportunityId: 3449,
externalId: 'Three thousand four hundred fifty seven',
proposalId: 3433,
applicationFormId: 3449,
version: 3457,
createdAt: new Date('2023-01-23T13:54:00-0600'),
});
});

it('should throw an Error when there are zero proposal versions but field values given.', () => {
const proposal: Proposal = {
id: 3461,
applicantId: 3463,
opportunityId: 3467,
externalId: 'Three thousand four hundred sixty nine',
createdAt: new Date('2023-01-23T13:55:00-0600'),
versions: [],
};
const fieldValue: ProposalFieldValue = {
id: 3491,
proposalVersionId: 3499,
applicationFormFieldId: 3511,
position: 3517,
value: 'Three thousand five hundred twenty seven',
createdAt: new Date('2023-01-23T13:57:00-0600'),
};
expect(() => { mergeProposalFieldValues(proposal, [fieldValue]); }).toThrow(Error);
});

it('should throw an Error when the proposal version mismatches field value.', () => {
const proposal: Proposal = {
id: 3527,
applicantId: 3529,
opportunityId: 3533,
externalId: 'Three thousand five hundred fifty nine',
createdAt: new Date('2023-01-23T14:05:00-0600'),
versions: [
{
// Here is the proposal version id
id: 3571,
proposalId: 3527,
applicationFormId: 3581,
version: 3583,
createdAt: new Date('2023-01-23T14:06:00-0600'),
},
],
};
const fieldValue: ProposalFieldValue = {
id: 3593,
// Here is the proposal version id that is supposed to match, but does not!
proposalVersionId: 3607,
applicationFormFieldId: 3613,
position: 3617,
value: 'Three thousand six hundred twenty three',
createdAt: new Date('2023-01-23T14:08:00-0600'),
};
expect(() => { mergeProposalFieldValues(proposal, [fieldValue]); }).toThrow(Error);
});

it('should throw an Error when there is a gap in the versions array.', () => {
const versions: ProposalVersion[] = [
{
id: 4079,
proposalId: 4049,
applicationFormId: 4091,
version: 4093,
createdAt: new Date('2023-01-24T09:55:00-0600'),
},
];
// Introduce a gap/undefined value in the versions array, e.g. [a, undefined, b].
versions[2] = {
id: 4099,
proposalId: 4049,
applicationFormId: 4111,
version: 4127,
createdAt: new Date('2023-01-24T11:02:00-0600'),
};
const proposal: Proposal = {
id: 4049,
applicantId: 4129,
opportunityId: 4133,
externalId: 'Four thousand one hundred thirty nine',
createdAt: new Date('2023-01-24T09:55:00-0600'),
versions,
};
const fieldValues: ProposalFieldValue[] = [
{
id: 4153,
proposalVersionId: 4079,
applicationFormFieldId: 4157,
position: 4159,
value: 'Four thousand one hundred seventy seven',
createdAt: new Date('2023-01-24T11:07:00-0600'),
},
{
id: 4201,
// This would match the second proposal version (were it at the right index).
proposalVersionId: 4099,
applicationFormFieldId: 4211,
position: 4217,
value: 'Four thousand two hundred nineteen',
createdAt: new Date('2023-01-24T11:09:00-0600'),
},
];
expect(() => { mergeProposalFieldValues(proposal, fieldValues); }).toThrow(Error);
});
});

describe('mergeApplicationFormFields', () => {
Expand Down Expand Up @@ -285,7 +139,7 @@ describe('mergeApplicationFormFields', () => {
createdAt: new Date('2023-01-23T13:43:00-0600'),
},
];
const valuesWithFields = mergeApplicationFormFields(values, fields);
const valuesWithFields = joinApplicationFormFieldsToProposalFieldValues(values, fields);
expect(valuesWithFields).toEqual([
{
id: 3271,
Expand Down Expand Up @@ -351,7 +205,9 @@ describe('mergeApplicationFormFields', () => {
createdAt: new Date('2023-01-23T14:16:00-0600'),
},
];
expect(() => { mergeApplicationFormFields(values, fields); }).toThrow(Error);
expect(() => {
joinApplicationFormFieldsToProposalFieldValues(values, fields);
}).toThrow(Error);
});

it('should throw an Error when a field is missing at the same index as value', () => {
Expand Down Expand Up @@ -415,7 +271,9 @@ describe('mergeApplicationFormFields', () => {
};
// We now have values and fields of equal length, defined either by max index or by element
// count, but with mismatches in position within those arrays. This should throw an Error.
expect(() => { mergeApplicationFormFields(values, fields); }).toThrow(Error);
expect(() => {
joinApplicationFormFieldsToProposalFieldValues(values, fields);
}).toThrow(Error);
});

it('should throw an Error when the application form field IDs do not match', () => {
Expand All @@ -441,6 +299,8 @@ describe('mergeApplicationFormFields', () => {
createdAt: new Date('2023-01-24T08:50:00-0600'),
},
];
expect(() => { mergeApplicationFormFields(values, fields); }).toThrow(Error);
expect(() => {
joinApplicationFormFieldsToProposalFieldValues(values, fields);
}).toThrow(Error);
});
});

0 comments on commit 0d56542

Please sign in to comment.