IEEE.org     |     IEEE Xplore Digital Library     |     IEEE Standards     |     IEEE Spectrum     |     More Sites

Unverified Commit 490abdf3 authored by tilley14's avatar tilley14 Committed by GitHub
Browse files

Conservation projects (#36)

* Added skeleton tests/funcs for projects feature

All tests currently run and FAIL

Added TODOs that will need to be addressed before being feature
complete.

Added project modules to indexes.

Beginning implementation of projects find functionality

* Fixing copy and paste error.

* Added projectName validator and tests.

Projects find now can take a name parameter to find projects with a
specified name. This search is case insensitive.

Tests for the new validator pass

* Using && instead of & to evaluate validation

If the type of the variable we are evaluating is undefined, we do not
want to continue. Trying to call length on something that is not a
string can cause failures

* Completing projects.db find tests

* Added controller tests for project find

All project find tests pass

* Adding descriptions to sample projects

* Fixing table name

* Projects response code 200 tests pass

* Added Tests for finding projects with parameters

* Added test for invalid query

* Created Project enpoint and Validator

Added test for Validator

* Added Create Project Controller Module and Tests

* Added Project create db module and tests

* Adding semicolon

* Fixing property error

* Added Client Create Project Tests

Seperated GET and POST Tests

* Added tests and implementation for PUT/ update

All tests pass

* Updating tests to pass bad requests

* Adding API documentation

* Fixed some formatting. Removed old Todos.

Updated some comments

* using querybuilder for url testing

removed todos and some commented out code

* Fixing Grammar Error

* fixing more spelling mistakes
parent 94ca409a
......@@ -4,7 +4,7 @@ const { setup, teardown, loadSQL } = require('../setup');
describe('GET/POST assetDefinitions', () => {
beforeAll(async () => {
jest.setTimeout(30000)
jest.setTimeout(30000);
await setup();
// create some default asset definitions
......
/**
* Tests for the Projects enpoint layer.
*/
const request = require('supertest');
const querystring = require('querystring');
const app = require('../../app');
const { setup, teardown, loadSQL } = require('../setup');
const ENDPOINT = '/api/v1/projects';
const EXPECTED_PROJECT1 = { id: 1, sponsor_id: 1, name: 'Madagascar Reforesting Project', description: 'Replanting Trees in Madagascar' };
const EXPECTED_PROJECT2 = { id: 2, sponsor_id: 1, name: 'Lemur Protection', description: 'Save the Lemurs! Long live Zooboomafu!' };
const EXPECTED_PROJECT3 = { id: 3, sponsor_id: 2, name: 'Bison Protection', description: 'Rebuilding the Bison population in North America.' };
describe('GET Projects', () => {
// Projects Test Setup
beforeAll(async () => {
jest.setTimeout(30000);
await setup();
// Load in Test SQL file
await loadSQL('../schema/sample-data-emptyProjects.sql');
});
// Clean up after the tests are finished.
afterAll(async () => {
await teardown();
});
it('returns HTTP 200 response', () => {
return request(app)
.get(ENDPOINT)
.expect(200)
.then((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(EXPECTED_PROJECT1),
expect.objectContaining(EXPECTED_PROJECT2),
expect.objectContaining(EXPECTED_PROJECT3)
])
);
});
});
it('able to get all Projects', async () => {
await request(app)
.get(ENDPOINT)
.expect(200)
.then((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(EXPECTED_PROJECT1),
expect.objectContaining(EXPECTED_PROJECT2),
expect.objectContaining(EXPECTED_PROJECT3)
])
);
expect(res.body).toHaveLength(3);
});
});
it('able to filter by Sponsor ID', async () => {
const idQuery = querystring.encode({sponsor_id: '2'});
await request(app)
.get(ENDPOINT + `?${idQuery}`)
.expect(200)
.then((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(EXPECTED_PROJECT3)
])
);
expect(res.body).toHaveLength(1);
});
});
it('returns bad request (HTTP 400) when finding with an invalid argument', async () => {
const badIdQuery = querystring.encode({sponsor_id: 'a'});
await request(app)
.get(ENDPOINT + `?${badIdQuery}`)
.expect(400)
.then((res) => {
expect(res.body['errors']).toHaveLength(1);
expect(res.body['errors'][0]).toHaveProperty('problem', 'Failed to validate the argument "a" for the parameter "sponsor_id"');
expect(res.body['errors'][0]).toHaveProperty('reason', 'Expected a number between 1 and 2147483647');
});
});
it('able to filter by Project Name', async () => {
const nameQuery = querystring.encode({name: 'Lemur Protection'});
await request(app)
.get(ENDPOINT + `?${nameQuery}`)
.expect(200)
.then((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(EXPECTED_PROJECT2)
])
);
expect(res.body).toHaveLength(1);
});
});
it('filters by Project Name is case insensitive', async () => {
const nameQuery = querystring.encode({name: 'lEmUR PRoTECtIon'});
await request(app)
.get(ENDPOINT + `?${nameQuery}`)
.expect(200)
.then((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(EXPECTED_PROJECT2)
])
);
expect(res.body).toHaveLength(1);
});
});
it('able to filter by Sponsor ID and Project Name', async () => {
const comboQuery = querystring.encode({sponsor_id: '1', name: 'Madagascar Reforesting Project' });
await request(app)
.get(ENDPOINT + `?${comboQuery}`)
.expect(200)
.then((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(EXPECTED_PROJECT1)
])
);
expect(res.body).toHaveLength(1);
});
});
});
describe('POST Project', () => {
// Projects Test Setup
beforeAll(async () => {
jest.setTimeout(30000);
await setup();
await loadSQL('../schema/sample-data-emptyProjects.sql');
});
// Clean up after the tests are finished.
afterAll(async () => {
await teardown();
});
it('returns HTTP 200 and creates a new Project', async () => {
const project1 = {
'project': {
'sponsor_id': '1',
'name': 'tname',
'description': 'tdesc'
}
};
let expected_id;
// We should get the ID of the project just created
await request(app)
.post(ENDPOINT)
.send(project1)
.expect(200)
.then((response) => {
const data = response.body;
expected_id = data;
expect(data).toBeTruthy();
expect(typeof data).toBe('number');
});
// Use the ID to find the record and validate that the project has the correct information
const idQuery = querystring.encode({id: expected_id});
await request(app)
.get(ENDPOINT + `?${idQuery}`)
.expect(200)
.then((res) => {
let expected_new_project = {
id: expected_id,
sponsor_id: parseInt(project1.project.sponsor_id, 10),
name: project1.project.name,
description: project1.project.description
};
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(expected_new_project)
])
);
expect(res.body).toHaveLength(1);
});
});
it('returns a bad request (HTTP 400) when creating with an invalid argument ', async () => {
const project1 = {
'project': {
'sponsor_id': 'a',
'name': 'tname',
'description': 'tdesc'
}
};
await request(app)
.post(ENDPOINT)
.send(project1)
.expect(400)
.then((res) => {
// Check to see that we got an error
expect(res.body['errors']).toHaveLength(1);
expect(res.body['errors'][0]).toHaveProperty('problem', 'Failed to validate the argument "[object Object]" for the parameter "project"');
expect(res.body['errors'][0]).toHaveProperty('reason', 'Expected a number between 1 and 2147483647');
});
});
});
describe('PUT Projects', () => {
// The ID of the project we are manipulating
let expected_id;
const projectBefore = {
'project': {
'sponsor_id': '1',
'name': 'nameBefore',
'description': 'descriptionBefore'
}
};
const projectAfter = {
'project': {
'sponsor_id': '2',
'name': 'nameAfter',
'description': 'descriptionAfter'
}
};
// Projects Test Setup
beforeAll(async () => {
jest.setTimeout(30000);
await setup();
await loadSQL('../schema/sample-data-emptyProjects.sql');
});
// Clean up after the tests are finished.
afterAll(async () => {
await teardown();
});
it('returns HTTP 200 and updates an existing Project', async () => {
// Insert a new project into the database and get its ID
await request(app)
.post(ENDPOINT)
.send(projectBefore)
.expect(200)
.then((response) => {
const data = response.body;
expected_id = data;
expect(data).toBeTruthy();
expect(typeof data).toBe('number');
});
// Make sure the Insertion was successful
const idQuery = querystring.encode({id: expected_id});
await request(app)
.get(ENDPOINT + `?${idQuery}`)
.expect(200)
.then((res) => {
let expected_new_project = {
id: expected_id,
sponsor_id: parseInt(projectBefore.project.sponsor_id, 10),
name: projectBefore.project.name,
description: projectBefore.project.description
};
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(expected_new_project)
])
);
expect(res.body).toHaveLength(1);
});
// Update Project with New information
await request(app)
.put(ENDPOINT + `/${expected_id}`)
.send(projectAfter)
.expect(200)
.then((response) => {
const data = response.body;
expect(data).toBe(expected_id);
expect(typeof data).toBe('number');
});
// Make sure the update was successful
await request(app)
.get(ENDPOINT + `?${idQuery}`)
.expect(200)
.then((res) => {
let expected_updated_project = {
id: expected_id,
sponsor_id: parseInt(projectAfter.project.sponsor_id, 10),
name: projectAfter.project.name,
description: projectAfter.project.description
};
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(expected_updated_project)
])
);
expect(res.body).toHaveLength(1);
});
});
it('returns a bad request (HTTP 400) when updating with an invaid project ', async () => {
// A Project that should fail validation
const badProject = {
'project': {
'sponsor_id': 'a',
'name': 'badName',
'description': 'badDescription'
}
};
// Update an existing project that should fail validation
await request(app)
.put(ENDPOINT + `/${expected_id}`)
.send(badProject)
.expect(400)
.then((res) => {
// Check to see that we got an error
expect(res.body['errors']).toHaveLength(1);
expect(res.body['errors'][0]).toHaveProperty('problem', 'Failed to validate the argument "[object Object]" for the parameter "project"');
expect(res.body['errors'][0]).toHaveProperty('reason', 'Expected a number between 1 and 2147483647');
});
// Make sure the project has not been updated
const idQuery = querystring.encode({id: expected_id});
await request(app)
.get(ENDPOINT + `?${idQuery}`)
.expect(200)
.then((res) => {
let expected_updated_project = {
id: expected_id,
sponsor_id: parseInt(projectAfter.project.sponsor_id, 10),
name: projectAfter.project.name,
description: projectAfter.project.description
};
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining(expected_updated_project)
])
);
expect(res.body).toHaveLength(1);
});
});
it('returns a bad request (HTTP 400) when updating with an id param', async () => {
const project1 = {
'project': {
'sponsor_id': 'a',
'name': 'tname',
'description': 'tdesc'
}
};
await request(app)
.put(ENDPOINT + '/a')
.send(project1)
.expect(400)
.then((res) => {
// Check to see that we got an error
expect(res.body['errors']).toHaveLength(1);
expect(res.body['errors'][0]).toHaveProperty('problem', 'Failed to validate the argument "a" for the parameter "id"');
expect(res.body['errors'][0]).toHaveProperty('reason', 'Expected a number between 1 and 2147483647');
});
});
});
\ No newline at end of file
/**
* Tests for Projects controller layer.
*/
const { find, create, update } = require('../projects.controller');
const projectsDb = require('../../db/projects.db');
// Testing the Projets controller Module
describe('projects.controller.find', () => {
let req;
let res;
let next;
let expected_projects;
// Reset variables before each test
beforeEach(() => {
// Clear the request
req = {
valid: {},
query: {}
};
// Clear the response
res = {
json: jest.fn(),
send: jest.fn(),
status: jest.fn(() => res)
};
next = jest.fn();
expected_projects = [{}];
projectsDb.find = jest.fn(async () => expected_projects);
});
it('accesses DB and sends JSON response when no parameters are provided', async () => {
await find(req, res, next);
expect(projectsDb.find).toHaveBeenCalledWith(undefined, undefined, undefined);
expect(res.json).toHaveBeenCalledWith(expected_projects);
});
it('accesses DB and sends JSON response when project id is provided', async () => {
req.valid['id'] = 2;
await find(req, res, next);
expect(projectsDb.find).toHaveBeenCalledWith(2, undefined, undefined);
expect(res.json).toHaveBeenCalledWith(expected_projects);
});
it('accesses DB and sends JSON response when sponsor_id is provided', async () => {
req.valid['sponsor_id'] = 8;
await find(req, res, next);
expect(projectsDb.find).toHaveBeenCalledWith(undefined, 8, undefined);
expect(res.json).toHaveBeenCalledWith(expected_projects);
});
it('accesses DB and sends JSON response when project name is provided', async () => {
req.valid['name'] = 'Madagascar Reforestation';
await find(req, res, next);
expect(projectsDb.find).toHaveBeenCalledWith(undefined, undefined, 'Madagascar Reforestation');
expect(res.json).toHaveBeenCalledWith(expected_projects);
});
it('accesses DB and sends JSON response when sponsor_id and name is provided', async () => {
req.valid['sponsor_id'] = 8;
req.valid['name'] = 'Madagascar Reforestation';
await find(req, res, next);
expect(projectsDb.find).toHaveBeenCalledWith(undefined, 8, 'Madagascar Reforestation');
expect(res.json).toHaveBeenCalledWith(expected_projects); });
it('ignores unvalidated id', async () => {
const getProjectId = jest.fn(() => 'a');
Object.defineProperty(req.query, 'id', { get: getProjectId });
await find(req, res, next);
expect(getProjectId).not.toHaveBeenCalled();
});
it('ignores unvalidated sponsor_id', async () => {
const getSponsorId = jest.fn(() => 'a');
Object.defineProperty(req.query, 'sponsor_id', { get: getSponsorId });
await find(req, res, next);
expect(getSponsorId).not.toHaveBeenCalled();
});
it('ignores null name', async () => {
const getName = jest.fn(() => null);
Object.defineProperty(req.query, 'name', { get: getName });
await find(req, res, next);
expect(getName).not.toHaveBeenCalled();
});
it('ignores empty name', async () => {
const getName = jest.fn(() => '');
Object.defineProperty(req.query, 'name', { get: getName });
await find(req, res, next);
expect(getName).not.toHaveBeenCalled();
});
});
describe('projects.controller.create', () => {
let req;
let res;
let next;
let project;
// Reset variables before each test
beforeEach(() => {
// Clear the request
req = {
valid: {},
body: {}
};
// Clear the response
res = {
json: jest.fn(),
send: jest.fn(),
status: jest.fn(() => res)
};
next = jest.fn();
project = { sponsor_id : '1', name: 'testProject', description: 'A description'};
projectsDb.create = jest.fn(async () => 4);
});
it('accesses DB and sends JSON response when a valid project is provided', async () => {
req.valid.project = project;
await create(req, res, next);
expect(projectsDb.create).toHaveBeenCalledWith(project);
expect(res.json).toHaveBeenCalledWith(4);
});
it('ignores an undefined/invalidated project', async () => {
const getUndefinedProject = jest.fn(() => undefined);
Object.defineProperty(req.body, 'project', { get: getUndefinedProject });
await create(req, res, next);
expect(projectsDb.create).toHaveBeenCalledWith(undefined);
expect(getUndefinedProject).not.toHaveBeenCalled();
});
it('catches DB access exceptions to pass them to the Express error handler', async () => {
const DB_ERROR = new Error();
projectsDb.create = jest.fn(async () => { throw DB_ERROR; });
await create(req, res, next);
expect(next).toHaveBeenCalledWith(DB_ERROR);
expect(res.json).not.toHaveBeenCalled();
});
});
describe('projects.controller.update', () => {
let req;
let res;
let next;
let project;
let id;
// Reset variables before each test
beforeEach(() => {
// Clear the request
req = {
params: {},
valid: {},
body: {}
};
// Clear the response
res = {
json: jest.fn(),
send: jest.fn(),
status: jest.fn(() => res)
};
id = 4;
next = jest.fn();
project = { sponsor_id : '1', name: 'testProject', description: 'A description'};
projectsDb.update = jest.fn(async () => 4);
});
it('accesses DB and sends JSON response when a valid project and project ID is provided', async () => {
req.valid.project = project;
req.valid.id = id;
await update(req, res, next);
expect(projectsDb.update).toHaveBeenCalledWith(id, project);
expect(res.json).toHaveBeenCalledWith(4);
});
it('ignores an undefined/invalidated project', async () => {
const getUndefinedProject = jest.fn(() => undefined);
req.valid['id'] = id;
Object.defineProperty(req.body, 'project', { get: getUndefinedProject });