Skip to content

Commit

Permalink
Merge branch 'master' into feat/831-ignore-zoom-deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
LomyW authored Jul 27, 2023
2 parents 5707e2b + 397cc95 commit becf730
Show file tree
Hide file tree
Showing 13 changed files with 588 additions and 100 deletions.
14 changes: 14 additions & 0 deletions common/migration/1690212671204-RenameRedactedUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class RenameRedactedUsers1690212671204 implements MigrationInterface {
name = 'RenameRedactedUsers1690212671204';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`UPDATE "pupil" SET firstname = 'Account', lastname = 'gelöscht' WHERE firstname = 'REDACTED' AND lastname = 'REDACTED'`);
await queryRunner.query(`UPDATE "student" SET firstname = 'Account', lastname = 'gelöscht' WHERE firstname = 'REDACTED' AND lastname = 'REDACTED'`);
await queryRunner.query(`UPDATE "mentor" SET firstname = 'Account', lastname = 'gelöscht' WHERE firstname = 'REDACTED' AND lastname = 'REDACTED'`);
await queryRunner.query(`UPDATE "screener" SET firstname = 'Account', lastname = 'gelöscht' WHERE firstname = 'REDACTED' AND lastname = 'REDACTED'`);
}

public async down(_queryRunner: QueryRunner): Promise<void> {}
}
4 changes: 4 additions & 0 deletions common/notification/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ const _notificationActions = {
appointment: sampleAppointment,
},
},
person_inactivity_reminder: {
description: 'Person / Inactive Reminder. User will soon be deleted.',
sampleContext: {},
},

user_authenticate: DEPRECATED,
user_login_email: DEPRECATED,
Expand Down
54 changes: 27 additions & 27 deletions integration-tests/base.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
import { randomBytes } from "crypto";
import { GraphQLClient } from "graphql-request";
import { randomBytes } from 'crypto';
import { GraphQLClient } from 'graphql-request';

import "./mock";
import './mock';

import * as WebServer from "../web";
import { expectNoFetchMockLeft } from "./mock";
import * as WebServer from '../web';
import { expectNoFetchMockLeft } from './mock';

/* -------------- Configuration ------------------- */

const APP = "lernfair-backend-dev";
const APP = 'lernfair-backend-dev';
const URL = process.env.INTEGRATION_TARGET ?? `http://localhost:${process.env.PORT ?? 5000}/apollo`;
const ADMIN_TOKEN = process.env.ADMIN_AUTH_TOKEN;

const silent = process.env.INTEGRATION_SILENT === "true";
const silent = process.env.INTEGRATION_SILENT === 'true';

/* -------------- Utils --------------------------- */

export const blue = (msg: string) => '\u001b[94m' + msg + '\u001b[39m';
export const red = (msg: string) => '\u001b[31m' + msg + '\u001b[39m';
export const green = (msg: string) => '\u001b[32m' + msg + '\u001b[39m';

console.log(
blue(`\n\nBackend Integration Tests\n`) +
` testing ${URL}\n\n`
);
console.log(blue(`\n\nBackend Integration Tests\n`) + ` testing ${URL}\n\n`);

/* -------------- GraphQL Client Wrapper ------------------ */

// This wrapper provides assertions and logging around a GraphQLClient of the graphql-request package
function wrapClient(client: GraphQLClient) {
async function request(query: string) {
const name = query.match(/(mutation|query) [A-Za-z]+/)?.[0] ?? "(unnamed)";
const name = query.match(/(mutation|query) [A-Za-z]+/)?.[0] ?? '(unnamed)';
console.log(blue(`+ ${name}`));
if (!silent) {
console.log(` request:`, query.trim());
Expand All @@ -43,7 +40,7 @@ function wrapClient(client: GraphQLClient) {
}

async function requestShallFail(query: string): Promise<never> {
const name = query.match(/(mutation|query) [A-Za-z]+/)?.[0] ?? "(unnamed)";
const name = query.match(/(mutation|query) [A-Za-z]+/)?.[0] ?? '(unnamed)';
console.log(blue(`+ ${name}`));

if (!silent) {
Expand All @@ -54,7 +51,7 @@ function wrapClient(client: GraphQLClient) {
await client.request(query);
} catch (error) {
if (!silent) {
console.log(` successfully failed with ${error.message.split(":")[0]}`);
console.log(` successfully failed with ${error.message.split(':')[0]}`);
}
return;
}
Expand All @@ -76,24 +73,28 @@ function wrapClient(client: GraphQLClient) {

export const defaultClient = wrapClient(new GraphQLClient(URL));

const adminAuthorization = `Basic ${Buffer.from("admin:" + ADMIN_TOKEN).toString("base64")}`;
const adminAuthorization = `Basic ${Buffer.from('admin:' + ADMIN_TOKEN).toString('base64')}`;

export const adminClient = wrapClient(new GraphQLClient(URL, {
headers: {
authorization: adminAuthorization,
}
}));
export const adminClient = wrapClient(
new GraphQLClient(URL, {
headers: {
authorization: adminAuthorization,
},
})
);

export function createUserClient() {
return wrapClient(new GraphQLClient(URL, {
headers: {
authorization: `Bearer ${randomBytes(36).toString("base64")}`
}
}));
return wrapClient(
new GraphQLClient(URL, {
headers: {
authorization: `Bearer ${randomBytes(36).toString('base64')}`,
},
})
);
}

/* -------------- Test Runner ------------------- */
const tests: { name: string, runner: () => Promise<any>, resolve: (value: any) => void, reject: (error: Error) => void }[] = [];
const tests: { name: string; runner: () => Promise<any>; resolve: (value: any) => void; reject: (error: Error) => void }[] = [];

/* test(...) has the following guarantees:
- Runners are executed in order of definition
Expand Down Expand Up @@ -161,4 +162,3 @@ export async function finalizeTests() {
console.error(red(`\n\n\n${failureCount} tests FAILED`));
process.exit(1); // A non-zero return code indicates a failure to the pipeline
}

215 changes: 215 additions & 0 deletions integration-tests/deactivation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { pupilOneWithPassword } from './auth';
import { test } from './base';
import { createNewPupil, createNewStudent, createInactivityMockNotification } from './user';
import { adminClient } from './base';
import { prisma } from '../common/prisma';
import { DEACTIVATE_ACCOUNTS_INACTIVITY_DAYS, deactivateInactiveAccounts } from '../jobs/periodic/redact-inactive-accounts/deactivate-inactive-accounts';
import { sendInactivityNotification, NOTIFY_AFTER_DAYS } from '../jobs/periodic/redact-inactive-accounts/send-inactivity-notification';
import moment from 'moment';
import assert from 'assert';

void test('Pupil Account Deactivation', async () => {
const { client, pupil, password } = await pupilOneWithPassword;
Expand All @@ -11,3 +18,211 @@ void test('Pupil Account Deactivation', async () => {

// requestToken will fail silently, so we cannot test this
});

// ------------------------------------
// Notify inactive accounts
// ------------------------------------
void test('Pupil should be notified after inactivity', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const pupil = await createNewPupil();
await prisma.pupil.update({
where: { id: pupil.pupil.pupil.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').subtract(1, 'day').toDate(),
active: true,
},
});

await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: pupil.pupil.userID } });
assert.strictEqual(notifications.length, 1);
});

void test('Pupil not should be notified as it is not inactive for long enough', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const pupil = await createNewPupil();
await prisma.pupil.update({
where: { id: pupil.pupil.pupil.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').add(1, 'day').toDate(),
active: true,
},
});

await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: pupil.pupil.userID } });
assert.strictEqual(notifications.length, 0);
});

void test('Pupil not should be notified as it is deactivated already', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const pupil = await createNewPupil();
await prisma.pupil.update({
where: { id: pupil.pupil.pupil.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').subtract(NOTIFY_AFTER_DAYS, 'days').toDate(),
active: false,
},
});

await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: pupil.pupil.userID } });
assert.strictEqual(notifications.length, 0);
});

void test('Pupil should not get notification twice', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const pupil = await createNewPupil();
await prisma.pupil.update({
where: { id: pupil.pupil.pupil.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').subtract(1, 'days').toDate(),
active: true,
},
});

await sendInactivityNotification();
await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: pupil.pupil.userID } });
assert.strictEqual(notifications.length, 1);
});

void test('Student should be notified after inactivity', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const student = await createNewStudent();
await prisma.student.update({
where: { id: student.student.student.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').subtract(1, 'day').toDate(),
active: true,
},
});

await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: student.student.userID } });
assert.strictEqual(notifications.length, 1);
});

void test('Student not should be notified as it is not inactive for long enough', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const student = await createNewStudent();
await prisma.student.update({
where: { id: student.student.student.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').add(1, 'day').toDate(),
active: true,
},
});

await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: student.student.userID } });
assert.strictEqual(notifications.length, 0);
});

void test('Student not should be notified as it is deactivated already', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const notification = await createInactivityMockNotification;

const student = await createNewStudent();
await prisma.student.update({
where: { id: student.student.student.id },
data: {
lastLogin: moment().subtract(NOTIFY_AFTER_DAYS, 'days').subtract(NOTIFY_AFTER_DAYS, 'days').toDate(),
active: false,
},
});

await sendInactivityNotification();

const notifications = await prisma.concrete_notification.findMany({ where: { notificationID: notification.id, userId: student.student.userID } });
assert.strictEqual(notifications.length, 0);
});

// ------------------------------------
// Deactivate inactive accounts
// ------------------------------------
void test('Pupil should be deactivated after inactivity', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const pupil = await createNewPupil();
await prisma.pupil.update({
where: { id: pupil.pupil.pupil.id },
data: {
lastLogin: moment().subtract(DEACTIVATE_ACCOUNTS_INACTIVITY_DAYS, 'days').subtract(1, 'day').toDate(),
active: true,
},
});

await deactivateInactiveAccounts();
const dbPupilNew = await prisma.pupil.findUnique({ where: { id: pupil.pupil.pupil.id } });

assert.strictEqual(dbPupilNew?.active, false);
});

void test('Pupil not should be deactivated as it is not inactive for long enough', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const pupil = await createNewPupil();
await prisma.pupil.update({
where: { id: pupil.pupil.pupil.id },
data: {
lastLogin: moment().subtract(DEACTIVATE_ACCOUNTS_INACTIVITY_DAYS, 'days').add(1, 'day').toDate(),
active: true,
},
});

await deactivateInactiveAccounts();
const dbPupilNew = await prisma.pupil.findUnique({ where: { id: pupil.pupil.pupil.id } });

assert.strictEqual(dbPupilNew?.active, true);
});

void test('Student should be deactivated after inactivity', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const student = await createNewStudent();
await prisma.student.update({
where: { id: student.student.student.id },
data: {
lastLogin: moment().subtract(DEACTIVATE_ACCOUNTS_INACTIVITY_DAYS, 'days').subtract(1, 'day').toDate(),
active: true,
},
});

await deactivateInactiveAccounts();
const dbStudentNew = await prisma.student.findUnique({ where: { id: student.student.student.id } });

assert.strictEqual(dbStudentNew?.active, false);
});

void test('Student not should be deactivated as it is not inactive for long enough', async () => {
await adminClient.request(`mutation ResetRateLimits { _resetRateLimits }`);
const student = await createNewStudent();
await prisma.student.update({
where: { id: student.student.student.id },
data: {
lastLogin: moment().subtract(DEACTIVATE_ACCOUNTS_INACTIVITY_DAYS, 'days').add(1, 'day').toDate(),
active: true,
},
});

await deactivateInactiveAccounts();
const dbStudentNew = await prisma.student.findUnique({ where: { id: student.student.student.id } });

assert.strictEqual(dbStudentNew?.active, true);
});
1 change: 1 addition & 0 deletions integration-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ import './admin';
import './registerPlusMany';
/* Account Deactivation - Independent, but needs to be last */
import './deactivation';
import './redaction';

void finalizeTests();
Loading

0 comments on commit becf730

Please sign in to comment.