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 tests for functions.ts #59

Merged
merged 4 commits into from
Jan 16, 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
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ module.exports = {
roots: ['<rootDir>/test', '<rootDir>/src'],
testRegex: '((\\.|/*.)(test))\\.ts?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
restoreMocks: true,
};
20 changes: 14 additions & 6 deletions src/db/functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import mongoose from 'mongoose';

import { Project as DbProject } from './schema/Project';
import { Project } from '../models/Project';
import { ObjectId } from 'mongodb';
Expand All @@ -22,7 +23,7 @@ function makeObjectId(id: string) {
*/
export async function connectToDatabase() {
const dbUri = process.env.MONGODB_URI;
if (dbUri == undefined) {
if (!dbUri) {
throw new Error('Please define the MONGODB_URI environment variable');
}
await mongoose.connect(dbUri);
Expand All @@ -35,8 +36,8 @@ export async function connectToDatabase() {
* @param proj - project to save to database
*/
export async function updateProjectInDb(proj: Project) {
if (!proj.presentInDatabase()) {
throw new Error('Project not present in database');
if (!proj.hasBeenSavedBefore()) {
throw new Error('Project has not been saved to database yet');
}
const id = makeObjectId(proj.getId());
const userId = proj.getUserId();
Expand All @@ -48,6 +49,8 @@ export async function updateProjectInDb(proj: Project) {
project.members = proj.getPersons();
project.relationGraph = proj.getAdjMatrix();
await project.save();
} else {
throw new Error('Project not found in database');
}
}

Expand Down Expand Up @@ -82,6 +85,11 @@ export async function loadProjectFromDb(projectId: string): Promise<Project> {
* @returns Project id as a string
*/
export async function createProjectInDb(project: Project): Promise<string> {
if (project.hasBeenSavedBefore()) {
throw new Error(
'Project has already been saved to database. Use the update function instead.',
);
}
const dbProject = await DbProject.create({
userId: project.getUserId(),
name: project.getName(),
Expand All @@ -105,9 +113,9 @@ export async function getProjectsFromDb(
const projects = await DbProject.find({ userId: userId }).exec();
const map = new Map();
for (const proj of projects) {
map.set(proj.name, proj._id);
map.set(proj.name, proj._id.toString());
}
return map;
return Promise.resolve(map);
}

/**
Expand All @@ -117,5 +125,5 @@ export async function getProjectsFromDb(
*/
export async function deleteProjectInDb(projectId: string) {
const id = makeObjectId(projectId);
await DbProject.deleteOne({ _id: id }).exec();
await DbProject.findByIdAndDelete(id).exec();
}
50 changes: 39 additions & 11 deletions src/models/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// do not have a valid id until they are saved to the database.

export class Project {
public static readonly NOT_SET = 'NOT_SET';
private id: string;
private userid: number;
private name: string;
Expand All @@ -21,10 +22,6 @@
adjMatrix: number[][],
stringPersonArr: string[],
) {
Project.validateUserId(userid);
Project.validateName(name);
Project.validateDescription(description);
Project.validatePeople(stringPersonArr);
this.id = id;
this.userid = userid;
this.name = name;
Expand All @@ -34,12 +31,31 @@
this.adjMatrix = adjMatrix;
}

public static _Project(
id: string,
userid: number,
name: string,
description: string,
adjMatrix: number[][],
stringPersonArr: string[],
): Project {
return new Project(
id,
userid,
name,
description,
adjMatrix,
stringPersonArr,
);
}

public static createBlankProject(userId: number): Project {
Project.validateUserId(userId);

Check warning on line 53 in src/models/Project.ts

View check run for this annotation

Codecov / codecov/patch

src/models/Project.ts#L53

Added line #L53 was not covered by tests
return new Project(
'NOT SET',
Project.NOT_SET,

Check warning on line 55 in src/models/Project.ts

View check run for this annotation

Codecov / codecov/patch

src/models/Project.ts#L55

Added line #L55 was not covered by tests
userId,
'NOT SET',
'No description',
Project.NOT_SET,
Project.NOT_SET,

Check warning on line 58 in src/models/Project.ts

View check run for this annotation

Codecov / codecov/patch

src/models/Project.ts#L57-L58

Added lines #L57 - L58 were not covered by tests
[],
[],
);
Expand All @@ -54,6 +70,11 @@
stringPersonArr: string[],
): Project {
Project.validateId(id);
Project.validateUserId(userid);
Project.validateName(name);
Project.validateDescription(description);
Project.validatePeople(stringPersonArr);
Project.validateAdjMatrix(stringPersonArr, adjMatrix);
return new Project(
id,
userid,
Expand Down Expand Up @@ -227,15 +248,22 @@
public static validatePeople(persons: string[]): void {
persons.forEach((person) => Project.validatePerson(person));
}

private validateAdjMatrix(adjMatrix: number[][]): void {
if (adjMatrix.length !== this.personArr.length) {
Project.validateAdjMatrix(this.personArr, adjMatrix);
}
private static validateAdjMatrix(
personArr: string[],
adjMatrix: number[][],
): void {
if (adjMatrix.length !== personArr.length) {
throw new Error(
'Invalid adjacency matrix. The number of rows in the adjacency matrix must be equal' +
'to the number of people in the project.',
);
}
adjMatrix.forEach((row) => {
if (row.length !== this.personArr.length) {
if (row.length !== personArr.length) {

Check warning on line 266 in src/models/Project.ts

View check run for this annotation

Codecov / codecov/patch

src/models/Project.ts#L266

Added line #L266 was not covered by tests
throw new Error(
'Invalid adjacency matrix. The number of columns in the adjacency ' +
'matrix must be equal to the number of people in the project.',
Expand All @@ -251,8 +279,8 @@
});
}

public presentInDatabase(): boolean {
return this.id !== 'NOT SET';
public hasBeenSavedBefore(): boolean {
return this.id !== Project.NOT_SET;
}

public incrementInteractions(person1: number, person2: number): void {
Expand Down
174 changes: 168 additions & 6 deletions test/db/functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
import { connectToDatabase } from '../../src/db/functions';
import mongoose from 'mongoose';

import { Project as DbProject } from '../../src/db/schema/Project';
import { Project } from '../../src/models/Project';
import {
connectToDatabase,
createProjectInDb,
deleteProjectInDb,
getProjectsFromDb,
loadProjectFromDb,
updateProjectInDb,
} from '../../src/db/functions';
import { ObjectId } from 'mongodb';

const mockingoose = require('mockingoose'); // ES6 style is bugged

const VALID_PROJECT_ID_1 = '65801fa288cd5362ab740b57';
const VALID_LOCAL_PROJECT_1 = Project._Project(
VALID_PROJECT_ID_1,
0,
'name',
'description',
[],
[],
);
const VALID_DB_PROJECT_1 = new DbProject({
_id: new ObjectId(VALID_PROJECT_ID_1),
userId: 0,
name: 'name',
description: 'description',
members: [],
relationGraph: [],
});

const INVALID_LOCAL_PROJECT_NO_ID = Project._Project(
Project.NOT_SET,
0,
'name',
'description',
[],
[],
);

describe('connectToDatabase()', () => {
const OLD_ENV = process.env;

Expand Down Expand Up @@ -33,13 +73,135 @@ describe('connectToDatabase()', () => {
);
expect(spy).toHaveBeenCalledTimes(0);
});
it('should throw an error with invalid URI', async () => {
});

describe('updateProjectInDb()', () => {
it('should succeed with a valid project', async () => {
const dbProj = new DbProject(VALID_DB_PROJECT_1);
const project = VALID_LOCAL_PROJECT_1;
mockingoose(DbProject)
.toReturn(dbProj, 'findOne')
.toReturn(VALID_DB_PROJECT_1, 'save');
expect.assertions(1);
await updateProjectInDb(project).then((_) =>
expect(true).toEqual(true),
);
});
it('should throw an error if project does not have a valid ID', async () => {
const project = INVALID_LOCAL_PROJECT_NO_ID;
mockingoose(DbProject).toReturn(null, 'findOne');
expect.assertions(1);
process.env.MONGODB_URI = 'invalidUri';
connectToDatabase().catch((err) =>
expect(err.toString()).toEqual(
'MongoParseError: Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"',
updateProjectInDb(project).catch((err) =>
expect(err).toEqual(
Error('Project has not been saved to database yet'),
),
);
});
it('should throw an error if project does not exist in database', async () => {
const project = VALID_LOCAL_PROJECT_1;
mockingoose(DbProject).toReturn(null, 'findOne');
expect.assertions(1);
updateProjectInDb(project).catch((err) =>
expect(err).toEqual(Error('Project not found in database')),
);
});
});

describe('loadProjectFromDb()', () => {
it('should succeed with a valid project id and project is in database', async () => {
const dbProj = new DbProject(VALID_DB_PROJECT_1);
mockingoose(DbProject).toReturn(dbProj, 'findOne'); // findOne is alias for findById
expect.assertions(1);
const idString = VALID_DB_PROJECT_1._id.toString();
await loadProjectFromDb(idString).then((proj) =>
expect(proj).toEqual(VALID_LOCAL_PROJECT_1),
);
});
it('should throw an error if given id is not proper', async () => {
expect.assertions(1);
const invalidId = 'invalidId';
loadProjectFromDb(invalidId).catch((err) =>
expect(err).toEqual(Error('Invalid project id: ' + invalidId)),
);
});
it('should throw an error if project does not exist in database', async () => {
mockingoose(DbProject).toReturn(null, 'findOne'); // findOne is alias for findById
expect.assertions(1);
const idString = VALID_DB_PROJECT_1._id.toString();
loadProjectFromDb(idString).catch((err) =>
expect(err).toEqual(
Error('Project cannot be found. Project Id: ' + idString),
),
);
});
});

describe('createProjectInDb()', () => {
it('should succeed with a valid project', async () => {
mockingoose(DbProject).toReturn(VALID_DB_PROJECT_1, 'save'); // prevent actual function from being called
expect.assertions(1);
await createProjectInDb(INVALID_LOCAL_PROJECT_NO_ID).then(
(id) => expect(ObjectId.isValid(id)).toBeTruthy(), // we can't check the actual id because it's randomly generated
);
});
it('should throw an error if project already has an ID', async () => {
const project = VALID_LOCAL_PROJECT_1;
expect.assertions(1);
createProjectInDb(project).catch((err) =>
expect(err).toEqual(
Error(
'Project has already been saved to database. Use the update function instead.',
),
),
);
});
});

describe('getProjectsFromDb()', () => {
it('should succeed with a valid user id', async () => {
const userId = VALID_LOCAL_PROJECT_1.getUserId();
const dbProj = new DbProject(VALID_DB_PROJECT_1);
const dbId = dbProj._id.toString();
const dbName = dbProj.name;
const map = new Map<string, string>([[dbName, dbId]]);
mockingoose(DbProject).toReturn([VALID_DB_PROJECT_1], 'find');
expect.assertions(1);
await getProjectsFromDb(userId).then((output) =>
expect(output).toEqual(map),
);
});
it('should throw an error with an invalid user id', async () => {
const invalidUserId = -1;
expect.assertions(1);
await getProjectsFromDb(invalidUserId).catch((err) =>
expect(err).toEqual(new Error('Invalid user id.')),
);
});
});

describe('deleteProjectInDb()', () => {
it('should succeed with a valid project id', async () => {
const dbProj = new DbProject(VALID_DB_PROJECT_1);
const idString = dbProj._id.toString();
mockingoose(DbProject).toReturn(dbProj, 'findOneAndDelete');
expect.assertions(1);
await deleteProjectInDb(idString).then((_) =>
expect(true).toEqual(true),
);
});
it('should throw an error with an invalid project id', async () => {
const invalidId = 'invalidId';
expect.assertions(1);
await deleteProjectInDb(invalidId).catch((err) =>
expect(err).toEqual(new Error('Invalid project id: ' + invalidId)),
);
});
it('should not throw an error if project does not exist in database', async () => {
const idString = VALID_DB_PROJECT_1._id.toString();
mockingoose(DbProject).toReturn(null, 'findOneAndDelete');
expect.assertions(1);
await deleteProjectInDb(idString).then((_) =>
expect(true).toEqual(true),
);
});
});