Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more tests #144

Merged
merged 3 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions tests/educators.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* eslint-disable @typescript-eslint/no-floating-promises */

import { beforeAll, afterAll, describe, it, expect } from "@jest/globals";
import request from "supertest";
import type { InferAttributes, Sequelize } from "sequelize";
import type { Express } from "express";

import { authorize, getTestDatabaseConnection } from "./utils";
import { setupApp } from "../src/app";
import { Class, Educator, Student, StudentsClasses } from "../src/models";
import { createApp } from "../src/server";
import { v4 } from "uuid";

let testDB: Sequelize;
let testApp: Express;
beforeAll(async () => {
testDB = await getTestDatabaseConnection();
testApp = createApp(testDB);
setupApp(testApp, testDB);
});

afterAll(() => {
testDB.close();
});

describe("Test educator routes", () => {

it("Should sign up an educator (minimal info)", async () => {
const data = {
first_name: v4(),
last_name: v4(),
password: v4(),
email: v4(),
username: v4(),
};

await authorize(request(testApp).post("/educators/create"))
.send(data)
.expect(201)
.expect("Content-Type", /json/)
.expect({
success: true,
status: "ok",
educator_info: data,
});

const educator = await Educator.findOne({ where: { email: data.email } });
expect(educator).not.toBeNull();

await educator?.destroy();

});

it("Should sign up an educator (full info)", async () => {
const data = {
first_name: v4(),
last_name: v4(),
password: v4(),
email: v4(),
username: v4(),
institution: "Test School",
age: 5,
gender: "Male",
};

await authorize(request(testApp).post("/educators/create"))
.send(data)
.expect(201)
.expect("Content-Type", /json/)
.expect({
success: true,
status: "ok",
educator_info: data,
});

const educator = await Educator.findOne({ where: { email: data.email } });
expect(educator).not.toBeNull();

await educator?.destroy();

});

it("Should return the correct educator by ID", async () => {
const educator = await Educator.create({
first_name: v4(),
last_name: v4(),
password: v4(),
email: v4(),
username: v4(),
verified: 1,
verification_code: "abcde",
});

const json: Partial<InferAttributes<Educator>> = educator.toJSON();
// The Sequelize object will return the `CURRENT_TIMESTAMP` literals,
// not the actual date values
delete json.profile_created;
delete json.last_visit;

await authorize(request(testApp).get(`/educators/${educator.id}`))
.expect(200)
.expect("Content-Type", /json/)
.then((res) => {
const resEducator = res.body.educator;
expect(resEducator).toMatchObject(json);

// Check that the timestamp fields are present
expect(resEducator).toHaveProperty("profile_created");
expect(typeof resEducator.profile_created).toBe("string");
expect(resEducator).toHaveProperty("last_visit");
expect(typeof resEducator.last_visit).toBe("string");
});

await educator.destroy();

});

it("Should return the correct educator by username", async () => {
const educator = await Educator.create({
first_name: v4(),
last_name: v4(),
password: v4(),
email: v4(),
username: v4(),
verified: 1,
verification_code: v4(),
});

const json: Partial<InferAttributes<Educator>> = educator.toJSON();
// The Sequelize object will return the `CURRENT_TIMESTAMP` literals,
// not the actual date values
delete json.profile_created;
delete json.last_visit;

await authorize(request(testApp).get(`/educators/${educator.username}`))
.expect(200)
.expect("Content-Type", /json/)
.then((res) => {
const resEducator = res.body.educator;
expect(resEducator).toMatchObject(json);

// Check that the timestamp fields are present
expect(resEducator).toHaveProperty("profile_created");
expect(typeof resEducator.profile_created).toBe("string");
expect(resEducator).toHaveProperty("last_visit");
expect(typeof resEducator.last_visit).toBe("string");
});

await educator.destroy();

});

});
171 changes: 149 additions & 22 deletions tests/students.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,62 @@ import type { Express } from "express";

import { authorize, getTestDatabaseConnection } from "./utils";
import { setupApp } from "../src/app";
import { Student } from "../src/models";
import { Class, Educator, Student, StudentsClasses } from "../src/models";
import { createApp } from "../src/server";
import { v4 } from "uuid";

// This is only used inside this test file,
// so we can just let TS infer the return type
async function setupStudentInClasses() {
const educator = await Educator.create({
first_name: v4(),
last_name: v4(),
password: v4(),
email: v4(),
verified: 1,
verification_code: v4(),
username: v4(),
});
const class1 = await Class.create({
name: v4(),
educator_id: educator.id,
code: v4(),
});
const class2 = await Class.create({
name: v4(),
educator_id: educator.id,
code: v4(),
});
const student = await Student.create({
email: v4(),
username: v4(),
password: v4(),
verification_code: class1.code,
verified: 0,
});

const sc1 = await StudentsClasses.create({
student_id: student.id,
class_id: class1.id,
});
const sc2 = await StudentsClasses.create({
student_id: student.id,
class_id: class2.id,
});

const cleanup = async () => {
// Sometimes we want to remove the class-student associations during the test
// In which case we can check whether it exists first
(await StudentsClasses.findOne({ where: { student_id: student.id, class_id: class1.id } }))?.destroy();
(await StudentsClasses.findOne({ where: { student_id: student.id, class_id: class2.id } }))?.destroy();
await class1.destroy();
await class2.destroy();
await educator.destroy();
await student.destroy();
};

return { student, educator, class1, class2, sc1, sc2, cleanup };
}

let testDB: Sequelize;
let testApp: Express;
Expand All @@ -26,10 +80,10 @@ describe("Test student routes", () => {

it("Should sign up a student", async () => {
const data = {
email: "[email protected]",
username: "abcde",
password: "fghij",
verification_code: "verification",
email: v4(),
username: v4(),
password: v4(),
verification_code: v4(),
};

await authorize(request(testApp).post("/students/create"))
Expand All @@ -42,19 +96,19 @@ describe("Test student routes", () => {
student_info: data,
});

const student = await Student.findOne({ where: { username: "abcde" } });
const student = await Student.findOne({ where: { username: data.username } });
expect(student).not.toBeNull();

student?.destroy();
await student?.destroy();

});

it("Should return the correct student", async () => {
it("Should return the correct student by ID", async () => {
const student = await Student.create({
email: "[email protected]",
username: "abcde",
password: "fghij",
verification_code: "verification",
email: v4(),
username: v4(),
password: v4(),
verification_code: v4(),
verified: 0,
});

Expand All @@ -63,19 +117,92 @@ describe("Test student routes", () => {
// not the actual date values
delete json.profile_created;
delete json.last_visit;
const res = await authorize(request(testApp).get(`/students/${student.id}`))
await authorize(request(testApp).get(`/students/${student.id}`))
.expect(200)
.expect("Content-Type", /json/);
.expect("Content-Type", /json/)
.then((res) => {
const resStudent = res.body.student;
expect(resStudent).toMatchObject(json);

const resStudent = res.body.student;
expect(resStudent).toMatchObject(json);
// Check that the timestamp fields are present
expect(resStudent).toHaveProperty("profile_created");
expect(typeof resStudent.profile_created).toBe("string");
expect(resStudent).toHaveProperty("last_visit");
expect(typeof resStudent.last_visit).toBe("string");
});

// Check that the timestamp fields are present
expect(resStudent).toHaveProperty("profile_created");
expect(typeof resStudent.profile_created).toBe("string");
expect(resStudent).toHaveProperty("last_visit");
expect(typeof resStudent.last_visit).toBe("string");
await student.destroy();
});

it("Should return the correct student by username", async () => {
const student = await Student.create({
email: v4(),
username: v4(),
password: v4(),
verification_code: v4(),
verified: 0,
});

student.destroy();
const json: Partial<InferAttributes<Student>> = student.toJSON();
// The Sequelize object will return the `CURRENT_TIMESTAMP` literals,
// not the actual date values
delete json.profile_created;
delete json.last_visit;
await authorize(request(testApp).get(`/students/${student.username}`))
.expect(200)
.expect("Content-Type", /json/)
.then((res) => {
const resStudent = res.body.student;
expect(resStudent).toMatchObject(json);

// Check that the timestamp fields are present
expect(resStudent).toHaveProperty("profile_created");
expect(typeof resStudent.profile_created).toBe("string");
expect(resStudent).toHaveProperty("last_visit");
expect(typeof resStudent.last_visit).toBe("string");
});

await student.destroy();
});

it("Should return the correct classes", async () => {

const { student, educator, class1, class2, cleanup } = await setupStudentInClasses();

await authorize(request(testApp).get(`/students/${student.id}/classes`))
.expect(200)
.expect("Content-Type", /json/)
.then((res) => {
expect(res.body.student_id).toBe(student.id);
const classes = res.body.classes;
expect(classes.length).toBe(2);

expect(classes.map((cls: Class) => cls.id)).toEqual([class1.id, class2.id]);
expect(classes.map((cls: Class) => cls.name)).toEqual([class1.name, class2.name]);
expect(classes.every((cls: Class) => cls.educator_id === educator.id));
});

await cleanup();
});

it("Should properly delete student-class associations", async () => {
const { student, class1, class2, cleanup } = await setupStudentInClasses();

await authorize(request(testApp).delete(`/students/${student.id}/classes/${class1.id}`))
.expect(204);

expect(await StudentsClasses.findOne({ where: { student_id: student.id, class_id: class1.id } })).toBeNull();

await authorize(request(testApp).delete(`/students/${student.id}/classes/${class2.id}`))
.expect(204);

expect(await StudentsClasses.findOne({ where: { student_id: student.id, class_id: class2.id } })).toBeNull();

await authorize(request(testApp).delete(`/students/${student.id}/classes/-1`))
.expect(404);

await cleanup();

});

});
6 changes: 4 additions & 2 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import type { Test } from "supertest";
import type { Sequelize } from "sequelize";

import { setUpAssociations } from "../src/associations";
import { Educator, initializeModels } from "../src/models";
import { Educator, StudentsClasses, initializeModels } from "../src/models";
import { createApp } from "../src/server";
import { Student } from "../src/models";
import { Class, Student } from "../src/models";
import { APIKey } from "../src/models/api_key";
import { config } from "dotenv";
import { getDatabaseConnection } from "../src/database";
Expand Down Expand Up @@ -69,6 +69,8 @@ export async function syncTables(force=false): Promise<void> {
await APIKey.sync(options);
await Student.sync(options);
await Educator.sync(options);
await Class.sync(options);
await StudentsClasses.sync(options);
}

export async function addAPIKey(): Promise<APIKey | void> {
Expand Down
Loading