Skip to content

Commit

Permalink
RN-502: Fixed bugs and cleaned up tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Rohan Port committed Oct 10, 2023
1 parent cfa9f86 commit 72d9f0b
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 192 deletions.
82 changes: 58 additions & 24 deletions packages/api-client/src/connections/mocks/MockAuthApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,65 @@
import { AuthApiInterface } from '..';
import { AccessPolicyObject } from '../../types';

type User = { email: string; password: string; accessPolicy: AccessPolicyObject };
type Session = { email: string; refreshToken: string; accessToken: string };

export class MockAuthApi implements AuthApiInterface {
public login(authDetails: {
emailAddress: string;
password: string;
deviceName: string;
devicePlatform?: string | undefined;
installId?: string | undefined;
}): Promise<{
accessToken: string;
refreshToken: string;
accessPolicy: AccessPolicyObject;
email: string;
user: { email: string; accessPolicy: AccessPolicyObject };
}> {
throw new Error('Method not implemented.');
private readonly users: User[];
private readonly sessions: Session[];

public constructor(users: User[] = [], sessions: Session[] = []) {
this.users = users;
this.sessions = sessions;
}
public refreshAccessToken(
refreshToken: string,
): Promise<{
accessToken: string;
refreshToken: string;
accessPolicy: AccessPolicyObject;
email: string;
user: { email: string; accessPolicy: AccessPolicyObject };
}> {
throw new Error('Method not implemented.');

public async login(authDetails: { emailAddress: string; password: string; deviceName: string }) {
const { emailAddress, password } = authDetails;
const user = this.users.find(
({ email, password: userPassword }) => emailAddress === email && password === userPassword,
);
if (!user) {
throw new Error('Incorrect username or password');
}

let session = this.sessions.find(({ email }) => emailAddress === email);
if (!session) {
session = {
email: emailAddress,
accessToken: `${emailAddress}_accessToken`,
refreshToken: `${emailAddress}_refreshToken`,
};
this.sessions.push(session);
}

return {
email: emailAddress,
accessPolicy: user.accessPolicy,
accessToken: session.accessToken,
refreshToken: session.refreshToken,
user: { email: user.email, accessPolicy: user.accessPolicy },
};
}

public async refreshAccessToken(refreshToken: string) {
const session = this.sessions.find(
({ refreshToken: sessionRefreshToken }) => refreshToken === sessionRefreshToken,
);
if (!session) {
throw new Error(`Refresh token expired, please login again`);
}

const user = this.users.find(({ email }) => email === session.email);
if (!user) {
throw new Error(`No user exists for session: ${session.email}`);
}

return {
email: session.email,
accessPolicy: user.accessPolicy,
accessToken: session.accessToken,
refreshToken: session.refreshToken,
user: { email: user.email, accessPolicy: user.accessPolicy },
};
}
}
78 changes: 67 additions & 11 deletions packages/api-client/src/connections/mocks/MockCentralApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,84 @@ import { CentralApiInterface } from '..';
import { RequestBody } from '../ApiConnection';

export class MockCentralApi implements CentralApiInterface {
private readonly user: { email: string; password: string } | undefined;
private readonly resources: Record<string, Record<string, unknown>[]>;

public constructor(
mockData: {
user?: { email: string; password: string };
resources?: Record<string, Record<string, unknown>[]>;
} = {},
) {
this.user = mockData.user;
this.resources = mockData.resources || {};
}

public getUser(): Promise<any> {
throw new Error('Method not implemented.');
}
public registerUserAccount(
userFields: Record<string, unknown>,
): Promise<{ userId: string; message: string }> {
throw new Error('Method not implemented.');

public async registerUserAccount(userFields: { id: string }) {
return { message: 'Successfully created user', userId: userFields.id };
}
public changeUserPassword(

public async changeUserPassword(
passwordChangeFields: Record<string, unknown>,
): Promise<{ message: string }> {
throw new Error('Method not implemented.');
if (!this.user) {
throw new Error(
'Must provide a user to the MockCentralApi in order to call changeUserPassword',
);
}

const { oldPassword, password, passwordConfirm } = passwordChangeFields;
if (oldPassword !== this.user.password) {
throw new Error('Incorrect old password');
}

if (password !== passwordConfirm) {
throw new Error('password != confirm');
}

return { message: 'Successfully changed password' };
}
public createSurveyResponses(responses: MeditrakSurveyResponseRequest[]): Promise<void> {
throw new Error('Method not implemented.');

public async createSurveyResponses(responses: MeditrakSurveyResponseRequest[]) {
// Do nothing
}
public fetchResources(

public async fetchResources(
endpoint: string,
params?: Record<string, unknown> | undefined,
params: { filter?: Record<string, unknown>; columns?: string[] } = {},
): Promise<any> {
throw new Error('Method not implemented.');
const resourcesOfType = this.resources[endpoint];
if (!resourcesOfType) {
throw new Error(`No resources of type ${endpoint} provided to MockCentralApi`);
}

const { filter, columns } = params;
if (!filter && !columns) {
return resourcesOfType;
}

let resourcesToReturn = [...resourcesOfType];
if (filter) {
resourcesToReturn = resourcesToReturn.filter(r =>
Object.entries(r).every(
([field, value]) => filter[field] === undefined || filter[field] === value,
),
);
}

if (columns) {
resourcesToReturn = resourcesToReturn.map(r =>
Object.fromEntries(columns.map(c => [c, r[c]])),
);
}

return resourcesToReturn;
}

public createResource(
endpoint: string,
params: Record<string, unknown>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generateId } from '@tupaia/database';
import { getTimezoneNameFromTimestamp } from '@tupaia/tsutils';
import { stripTimezoneFromDate } from '@tupaia/utils';
import { ValidationError, stripTimezoneFromDate } from '@tupaia/utils';
import keyBy from 'lodash.keyby';
import { upsertAnswers } from '../../dataAccessors';

Expand Down Expand Up @@ -66,7 +66,7 @@ function buildResponseRecord(user, entitiesByCode, body) {
if (value) return new Date(value).toISOString();
if (timestamp) return new Date(timestamp).toISOString();

throw new Error(`Must provide ${parameterName} or timestamp`);
throw new ValidationError(`Must provide ${parameterName} or timestamp`);
};

const startTime = defaultToTimestampOrThrow(inputStartTime, 'start_time');
Expand Down
37 changes: 19 additions & 18 deletions packages/central-server/src/tests/apiV2/surveyResponse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import moment from 'moment';
import { buildAndInsertSurveys, generateTestId, upsertDummyRecord } from '@tupaia/database';
import { oneSecondSleep, randomIntBetween } from '@tupaia/utils';
import {
expectError,
expectErrors,
expectSuccess,
setupDummySyncQueue,
Expand Down Expand Up @@ -34,7 +35,8 @@ const ENTITY_NON_CLINIC_ID = generateTestId();

const questionCode = key => `TEST-${key}`;

const expectError = (response, expectedError) => expectErrors(response, expectedError, 400);
const expectValidationErrors = (response, expectedError) =>
expectErrors(response, expectedError, 400);

let surveyId;

Expand Down Expand Up @@ -236,7 +238,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /survey_id/);
expectValidationErrors(response, /survey_id/);
});

it('Should throw if a submission has a missing entity code', async () => {
Expand All @@ -250,8 +252,8 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /entity_id/);
expectError(response, /Must provide one of/);
expectValidationErrors(response, /entity_id/);
expectValidationErrors(response, /Must provide one of/);
});

it('Should throw if a submission has an invalid entity id', async () => {
Expand All @@ -266,7 +268,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /No entity with id/);
expectValidationErrors(response, /No entity with id/);
});

it('Should throw if a submission has an invalid entity code', async () => {
Expand All @@ -281,7 +283,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /No entity with code/);
expectValidationErrors(response, /No entity with code/);
});

it('Should throw if a submission has an invalid question code', async () => {
Expand All @@ -296,7 +298,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /Could not find question/);
expectValidationErrors(response, /Could not find question/);
});

it('Should throw if a question is not present on the selected survey', async () => {
Expand All @@ -311,7 +313,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /Could not find question/);
expectValidationErrors(response, /Could not find question/);
});

it('Should allow a survey response with no answers', async () => {
Expand Down Expand Up @@ -344,7 +346,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /is missing value/);
expectValidationErrors(response, /is missing value/);
});

it('Should throw if a survey is missing a timestamp', async () => {
Expand All @@ -358,8 +360,7 @@ describe('surveyResponse endpoint', () => {
},
});

expectError(response, /timestamp/);
expectError(response, /Should not be empty/);
expectError(response, /Must provide .* or timestamp/, 400);
});

it('Should handle timezones correctly', async () => {
Expand Down Expand Up @@ -544,37 +545,37 @@ describe('surveyResponse endpoint', () => {

it('Should reject an invalid Binary answer', async () => {
const response = await postTypeCheck('Binary', 'Maybe');
expectError(response, /Maybe is not an accepted value/);
expectValidationErrors(response, /Maybe is not an accepted value/);
});

it('Should reject an invalid Instruction answer', async () => {
const response = await postTypeCheck('Instruction', 'Any text');
expectError(response, /Should be empty/);
expectValidationErrors(response, /Should be empty/);
});

it('Should reject an invalid Number answer', async () => {
const response = await postTypeCheck('Number', 'Three');
expectError(response, /Should contain a number/);
expectValidationErrors(response, /Should contain a number/);
});

it('Should reject an invalid Radio answer', async () => {
const response = await postTypeCheck('Radio', 'RadioX');
expectError(response, /RadioX is not an accepted value/);
expectValidationErrors(response, /RadioX is not an accepted value/);
});

it('Should reject an invalid Date answer', async () => {
const response = await postTypeCheck('Date', '1/1/2019');
expectError(response, /Dates should be in ISO 8601 format/);
expectValidationErrors(response, /Dates should be in ISO 8601 format/);
});

it('Should reject an invalid SubmissionDate answer', async () => {
const response = await postTypeCheck('SubmissionDate', '1/1/2019');
expectError(response, /Dates should be in ISO 8601 format/);
expectValidationErrors(response, /Dates should be in ISO 8601 format/);
});

it('Should reject an invalid DateOfData answer', async () => {
const response = await postTypeCheck('DateOfData', '1/1/2019');
expectError(response, /Dates should be in ISO 8601 format/);
expectValidationErrors(response, /Dates should be in ISO 8601 format/);
});
});
});
Loading

0 comments on commit 72d9f0b

Please sign in to comment.