Skip to content

Commit

Permalink
Merge pull request #65 from PermanentOrg/revert-63-revert-59-dependab…
Browse files Browse the repository at this point in the history
…ot/npm_and_yarn/jsonwebtoken-and-firebase-admin-9.0.0

Revert "Revert "Bump jsonwebtoken and firebase-admin""
  • Loading branch information
liam-lloyd authored May 3, 2023
2 parents e8ddb37 + afc128f commit e86cd5d
Show file tree
Hide file tree
Showing 14 changed files with 1,658 additions and 1,664 deletions.
26 changes: 13 additions & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: unit tests
on: [pull_request, workflow_dispatch]
jobs:
run_tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [ '12', '14' ]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: npm install --production=false
- run: npm run lint
- run: npm run test
run_tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: ["14"]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: npm install --production=false
- run: npm run lint
- run: npm run test
3,051 changes: 1,503 additions & 1,548 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
},
"homepage": "https://permanent.org",
"engines": {
"node": ">=12.0"
"node": ">=14.0"
},
"dependencies": {
"body-parser": "^1.20.1",
"dotenv": "^8.2.0",
"express": "^4.17.3",
"express-winston": "^4.1.0",
"firebase-admin": "^10.3.0",
"firebase-admin": "^11.5.0",
"joi": "^17.2.1",
"pg": "^8.5.1",
"postgres-migrations": "^5.1.1",
Expand All @@ -43,21 +43,21 @@
"@babel/node": "^7.14.2",
"@babel/preset-env": "^7.14.4",
"@babel/preset-typescript": "^7.13.0",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.3",
"@types/express": "^4.17.8",
"@types/jest": "^26.0.23",
"@types/node": "^12.20.7",
"@types/node": "^14.18.3",
"@types/pg": "^7.14.10",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^4.1.1",
"@typescript-eslint/parser": "^4.1.1",
"eslint": "^7.9.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-airbnb-typescript": "^10.0.0",
"eslint-plugin-import": "^2.22.0",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"eslint": "^8.27.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"jest": "^27.0.3",
"supertest": "^5.0.0",
"ts-jest": "^27.0.2",
"typescript": "^4.0.2"
"typescript": "^4.8.4"
}
}
32 changes: 22 additions & 10 deletions src/controllers/device.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import type {
Response,
} from 'express';
import { deviceService } from '../services';
import { validateCreateDeviceParams } from '../validators';
import { validateCreateDeviceParams, isValidationError } from '../validators';

interface Devices{
interface Devices {
userId: number;
deviceToken: string;
}
Expand All @@ -15,10 +15,16 @@ const addDevice: Handler = (
req: Request,
res: Response,
): void => {
const validation = validateCreateDeviceParams(req.body);
if (validation.error) {
res.status(400).json({ error: validation.error });
} else {
try {
validateCreateDeviceParams(req.body);
} catch (err) {
if (isValidationError(err)) {
res.status(400).json({ error: err });
} else {
throw err;
}
}
if (validateCreateDeviceParams(req.body)) {
deviceService.addDevice(req.body)
.then(() => res.json(req.body))
.catch((err: unknown) => res.status(500).json({
Expand All @@ -30,10 +36,16 @@ const deleteDevice: Handler = async (
req: Request,
res: Response,
): Promise<void> => {
const validation = validateCreateDeviceParams(req.body);
if (validation.error) {
res.status(400).json({ error: validation.error });
} else {
try {
validateCreateDeviceParams(req.body);
} catch (err) {
if (isValidationError(err)) {
res.status(400).json({ error: err });
} else {
throw err;
}
}
if (validateCreateDeviceParams(req.body)) {
const device = req.body as Devices;
if ((await deviceService.getDeviceTokensForUser(device.userId)).includes(device.deviceToken)) {
deviceService.removeDeviceToken(device.deviceToken)
Expand Down
16 changes: 11 additions & 5 deletions src/controllers/notification.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import type {
Response,
} from 'express';
import { notificationService } from '../services';
import { validateCreateNotificationParams } from '../validators';
import { validateCreateNotificationParams, isValidationError } from '../validators';

const createNotification: Handler = (
req: Request,
res: Response,
): void => {
const validation = validateCreateNotificationParams(req.body);
if (validation.error) {
res.status(400).json({ error: validation.error });
} else {
try {
validateCreateNotificationParams(req.body);
} catch (err) {
if (isValidationError(err)) {
res.status(400).json({ error: err });
} else {
throw err;
}
}
if (validateCreateNotificationParams(req.body)) {
notificationService.createNotification(req.body)
.then((data) => res.json(data))
.catch((err: unknown) => res.status(500).json({
Expand Down
4 changes: 2 additions & 2 deletions src/services/message.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Notification } from './notification.service';
import { logger } from '../log';

const credentials: unknown = JSON.parse(
process.env.FIREBASE_CREDENTIALS as string,
process.env.FIREBASE_CREDENTIALS ?? '',
);
const firebaseApp = admin.initializeApp({
credential: admin.credential.cert(credentials as ServiceAccount),
Expand Down Expand Up @@ -37,7 +37,7 @@ const isInvalidTokenError = (err: unknown): boolean => (
const sendMessageToDevice = async (
deviceToken: string,
notificationType: string,
context: { [key: string]: string },
context: Record<string, string>,
): Promise<string> => {
logger.silly('Sending message', { deviceToken, notificationType, context });
try {
Expand Down
8 changes: 3 additions & 5 deletions src/services/notification.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ import { logger } from '../log';
export interface Notification {
toUserId: number;
notificationType: string;
context: {
[key: string]: string;
};
context: Record<string, string>;
}

interface InsertNotificationResult{
interface InsertNotificationResult {
notification_id: number;
}

interface NotificationResponse{
interface NotificationResponse {
notificationId: number;
message: string;
}
Expand Down
2 changes: 1 addition & 1 deletion src/types/notification-params.test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export {};
declare global {
namespace jest {
interface Matchers<R> {
toHaveErrorMessage: (message: string) => R;
toGenerateErrorMessage: (arg: unknown, message: string) => R;
}
}
}
1 change: 1 addition & 0 deletions src/validators/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { validateCreateDeviceParams } from './validateCreateDeviceParams';
export { validateCreateNotificationParams } from './notification-params';
export { isValidationError } from './utils';
102 changes: 53 additions & 49 deletions src/validators/notification-params.test.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,79 @@
import type { ValidationResult } from 'joi';
import {
forbiddenKeys,
validateCreateNotificationParams as validate,
} from './notification-params';
import { isValidationError } from './utils';

/* eslint-disable @typescript-eslint/no-base-to-string --
* `Error` does have a valid toString method
* https://github.com/microsoft/TypeScript/issues/38347
*/

expect.extend({
toHaveErrorMessage: (received: ValidationResult, message: string) => {
const { error } = received;
if (error === undefined) {
return {
pass: false,
message: (): string => 'expected validation result to have an error',
};
}
const hasMatchingErrorMessage = error.details
.map((item) => item.message)
.includes(message);
if (hasMatchingErrorMessage) {
return {
pass: true,
message: (): string => [
`expected {${error.toString()}} not to have error message ${message}:`,
error.annotate(),
].join('\n'),
};
toGenerateErrorMessage: (func: (data: unknown) => boolean, arg: unknown, message: string) => {
try {
func(arg);
} catch (error) {
if (isValidationError(error)) {
const hasMatchingErrorMessage = error.details
.map((item) => item.message)
.includes(message);
const errorString = error.toString();
const errorAnnotations = error.annotate();
if (hasMatchingErrorMessage) {
return {
pass: true,
message: (): string => [
`expected {${errorString}} not to have error message ${message}:`,
errorAnnotations,
].join('\n'),
};
}
return {
pass: false,
message: (): string => [
`expected {${errorString}} to have error message ${message}:`,
errorAnnotations,
].join('\n'),
};
}
throw error;
}
return {
pass: false,
message: (): string => [
`expected {${error.toString()}} to have error message ${message}:`,
error.annotate(),
].join('\n'),
message: (): string => 'expected validation result to have an error',
};
},
});

describe('Notification parameter validator', () => {
it('should accept a valid notification (without context)', () => (
it('should accept a valid notification (without context)', () => {
expect(validate({
notificationType: 'test',
toUserId: 1,
})).not.toHaveProperty('error')
));
})).not.toHaveProperty('error');
});

it('should require an object', () => {
expect(validate(42)).toHaveErrorMessage('"value" must be of type object');
expect(validate).toGenerateErrorMessage(42, '"value" must be of type object');
});

it('should require the notification type', () => {
expect(validate({
toUserId: 1,
})).toHaveErrorMessage('"notificationType" is required');
expect(validate).toGenerateErrorMessage({ toUserId: 1 }, '"notificationType" is required');
});

it('should require the to user ID', () => {
expect(validate({
expect(validate).toGenerateErrorMessage({
notificationType: 'test',
})).toHaveErrorMessage('"toUserId" is required');
}, '"toUserId" is required');
});

it('should reject other keys', () => {
expect(validate({
expect(validate).toGenerateErrorMessage({
notificationType: 'test',
toUserId: 1,
unexpected: true,
})).toHaveErrorMessage('"unexpected" is not allowed');
}, '"unexpected" is not allowed');
});

describe('context', () => {
Expand All @@ -93,24 +97,24 @@ describe('Notification parameter validator', () => {
});

it('should require string values', () => {
expect(validate({
expect(validate).toGenerateErrorMessage({
notificationType: 'test',
toUserId: 1,
context: {
forbidden: true,
},
})).toHaveErrorMessage('"context.forbidden" must be a string');
}, '"context.forbidden" must be a string');
});

forbiddenKeys.forEach((forbiddenKey) => {
it(`should forbid key "${forbiddenKey}"`, () => {
expect(validate({
expect(validate).toGenerateErrorMessage({
notificationType: 'test',
toUserId: 1,
context: {
[forbiddenKey]: 'forbidden',
},
})).toHaveErrorMessage(`"context.${forbiddenKey}" is not allowed`);
}, `"context.${forbiddenKey}" is not allowed`);
});

it(`should allow value "${forbiddenKey}"`, () => {
Expand All @@ -124,29 +128,29 @@ describe('Notification parameter validator', () => {
});
});

['google', 'google-test', 'googleTest'].forEach((forbiddenKey) => (
['google', 'google-test', 'googleTest'].forEach((forbiddenKey) => {
it(`should forbid key starting with "google" ("${forbiddenKey}")`, () => {
expect(validate({
expect(validate).toGenerateErrorMessage({
notificationType: 'test',
toUserId: 1,
context: {
[forbiddenKey]: 'forbidden',
},
})).toHaveErrorMessage(`"context.${forbiddenKey}" is not allowed`);
})
));
}, `"context.${forbiddenKey}" is not allowed`);
});
});

['gcm', 'gcm-test', 'gcmTest'].forEach((forbiddenKey) => (
['gcm', 'gcm-test', 'gcmTest'].forEach((forbiddenKey) => {
it(`should forbid key starting with "gcm" ("${forbiddenKey}")`, () => {
expect(validate({
expect(validate).toGenerateErrorMessage({
notificationType: 'test',
toUserId: 1,
context: {
[forbiddenKey]: 'forbidden',
},
})).toHaveErrorMessage(`"context.${forbiddenKey}" is not allowed`);
})
));
}, `"context.${forbiddenKey}" is not allowed`);
});
});

it('should accept a key containing "google"', () => {
expect(validate({
Expand Down
Loading

0 comments on commit e86cd5d

Please sign in to comment.