Skip to content

Commit

Permalink
Merge pull request #144 from Carifio24/more-tests
Browse files Browse the repository at this point in the history
Add more tests
  • Loading branch information
Carifio24 authored Oct 3, 2024
2 parents f72a7d1 + f882606 commit 8f27529
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 24 deletions.
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

0 comments on commit 8f27529

Please sign in to comment.