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

Server tests. #288 #289

Merged
merged 45 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4514ac2
install jest and supertest
romanstetsyk Jan 20, 2023
355fb8d
separate app and server into different files
romanstetsyk Jan 20, 2023
a129715
add jest coverage folder to gitignore
romanstetsyk Jan 22, 2023
68418f5
allow the use of mock user in test environment
romanstetsyk Jan 22, 2023
49bc2a8
change validateBody to be a sync function
romanstetsyk Jan 22, 2023
ead33ee
add 'use strict', fix variable declarations
romanstetsyk Jan 22, 2023
f46d3da
extract constants
romanstetsyk Jan 22, 2023
b1ce0ec
remove console logs
romanstetsyk Jan 23, 2023
ae0a56d
add mock user to db
romanstetsyk Jan 23, 2023
7a1da5a
refactor, fix validation bugs in createEventSchema
romanstetsyk Jan 23, 2023
955feb6
test httpError
romanstetsyk Jan 23, 2023
07c1606
test validateObjectId
romanstetsyk Jan 23, 2023
96a7d68
test validateBody
romanstetsyk Jan 23, 2023
b66295a
refactor unit tests
romanstetsyk Jan 24, 2023
54f4ce4
export STRING_MAX_LENGTH to be used in tests
romanstetsyk Jan 24, 2023
97fe831
fix dotenv path to work in tests
romanstetsyk Jan 24, 2023
ab1da48
modify mock request
romanstetsyk Jan 24, 2023
48bb000
test routes
romanstetsyk Jan 24, 2023
41e2826
refactor maxEvents middleware
romanstetsyk Jan 25, 2023
62a9794
add tests for maxEvents
romanstetsyk Jan 25, 2023
2487a42
allow event end time (not date) to be before start time
romanstetsyk Jan 25, 2023
16b21ea
test create event with valid data
romanstetsyk Jan 25, 2023
fe7e81b
Merge branch 'development' into server-tests
romanstetsyk Jan 25, 2023
a362786
timezone bug fix: add offset to days
romanstetsyk Jan 26, 2023
be3122c
test createEventArray
romanstetsyk Jan 27, 2023
36e9965
create date range with Date instead of datefns lib
romanstetsyk Jan 27, 2023
4209cd4
test createEventsArray in JST timezone
romanstetsyk Jan 27, 2023
2714185
refactor createEventsArray and tests
romanstetsyk Jan 28, 2023
718db49
Merge branch 'development' into server-tests
romanstetsyk Jan 29, 2023
e03b97b
fix test JST time
romanstetsyk Jan 29, 2023
4c0dece
use Temporal to work with dates
romanstetsyk Jan 30, 2023
faaa8aa
fix event duration using Temporal
romanstetsyk Jan 31, 2023
eebae6e
test cases for createEventsArray
romanstetsyk Feb 7, 2023
6ae950b
test validateBody
romanstetsyk Feb 7, 2023
70fa9a6
fix acceptance tests
romanstetsyk Feb 7, 2023
c91f50e
Merge branch 'development' into server-tests
romanstetsyk Feb 7, 2023
aea5b3e
change hardcoded timezone to machine timezone
romanstetsyk Feb 7, 2023
8cbd957
Merge branch 'development' into server-tests
romanstetsyk Feb 13, 2023
099302d
update test script
romanstetsyk Feb 13, 2023
a9c9ee1
Merge branch 'development' into server-tests
romanstetsyk Feb 14, 2023
cea7784
remove irrelevant test (maxEvents)
romanstetsyk Feb 14, 2023
99359df
update front-end test
romanstetsyk Feb 14, 2023
af596a6
add script to run all tests together
romanstetsyk Feb 14, 2023
ce73c30
add route tests
romanstetsyk Feb 14, 2023
1eb314e
added formidable, jsbi, moved jest and supertest to dev dependencies
Caleb-Cohen Feb 15, 2023
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build
.DS_Store
.mongo
formLogic.txt
/coverage/
9,337 changes: 4,218 additions & 5,119 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"dev-concurrent-mockServer": "concurrently \"npm:start-client\" \"npm:start-mockServer\"",
"lint": "eslint .",
"lint:fix": "eslint --fix",
"format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
"format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc",
"test": "jest --coverage"
},
"author": "",
"license": "MIT",
Expand All @@ -25,20 +26,21 @@
"express-async-errors": "^3.1.1",
"express-flash": "^0.0.2",
"express-session": "^1.17.1",
"jest": "^27.5.1",
"joi": "^17.7.0",
"mongodb": "^3.7.3",
"mongoose": "^5.12.3",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"nanoid": "^3.3.4",
"passport": "^0.6.0",
"passport-discord": "^0.1.4"
"passport-discord": "^0.1.4",
"supertest": "^6.3.3"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"framer-motion": "^8.5.0",
"autoprefixer": "^10.4.13",
"axios": "^1.1.3",
"concurrently": "^7.5.0",
Expand All @@ -47,6 +49,7 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"framer-motion": "^8.5.0",
"http-proxy-middleware": "^2.0.6",
"json-server": "^0.17.1",
"mongodb-memory-server": "^8.10.1",
Expand Down
82 changes: 82 additions & 0 deletions server/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const express = require("express");
const path = require("path");
const app = express();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Express app and server needed to be separated into separate files so that app could be imported into the tests without starting the server

const mongoose = require("mongoose");
const passport = require("passport");
const session = require("express-session");
const MongoStore = require("connect-mongo")(session);
const flash = require("express-flash");
const logger = require("morgan");
//Use .env file in config folder
require("dotenv").config({ path: path.resolve(__dirname, "config", ".env") });
const mainRoutes = require("./routes/main");
const eventsRoutes = require("./routes/events");
const mockUser = require("./config/mockUser.json");
const User = require("./models/User");

// Passport config
require("./config/passport")(passport);

//Body Parsing
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

//Logging
app.use(logger("dev"));

// Setup Sessions - stored in MongoDB
app.use(
session({
secret: process.env.SESSION_SECRET || "keyboard cat",
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);

// Render React as View
app.use(express.static(path.join(__dirname, "..", "client", "build")));

// Passport middleware
app.use(passport.initialize());
app.use(passport.session());

// Allows to use a mock user in development and testing environments
if (
["development", "test"].includes(process.env.NODE_ENV) &&
process.env.MOCK_USER === "true"
) {
console.log("In development - using mocked user");
app.use(async (req, res, next) => {
req.user = mockUser;
let user = await User.findOne({ _id: mockUser._id }).exec();
if (!user) {
await User.create(mockUser);
}
next();
});
}

//Use flash messages for errors, info, ect...
app.use(flash());

//Setup Routes For Which The Server Is Listening
app.use("/", mainRoutes);
app.use("/events", eventsRoutes);
app.get("'", (req, res) => {
res.sendFile(path.join(__dirname, "..", "client", "build", "index.html"));
});

// 404 handler
app.use((req, res) => {
res.status(404).json({ message: "Not found" });
});

// error handler
app.use((err, req, res, next) => {
const { status = 500, message = "Server error", stack } = err;
// console.log(stack);
res.status(status).json({ message });
});

module.exports = app;
4 changes: 2 additions & 2 deletions server/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module.exports = {
ensureAuth: function (req, res, next) {
if (req.isAuthenticated()) {
console.log("user is authenticated, proceeeding")
// console.log("user is authenticated, proceeeding")
return next();
} else {
console.log("user is not authenticated")
// console.log("user is not authenticated")
res.sendStatus(401);
}
},
Expand Down
42 changes: 23 additions & 19 deletions server/middleware/maxEvents.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
"use strict";

require("express-async-errors");
const httpError = require("../utilities/httpError");
const { Event } = require("../models/Event");

const MAX_EVENTS_PER_USER = 5;

/**
* Checks if the number of different future recurring events
* (with distinct groupId) and nonrecurring events exceeds 5.
* (with distinct groupId) and nonrecurring events exceeds MAX_EVENTS_PER_USER.
*/
const maxEvents = async (req, _, next) => {
try {
const existingRecurringEvents = await Event.distinct("groupId", {
user: req.user._id,
groupId: { $ne: null },
startAt: { $gte: Date.now() },
}).exec();
const existingRecurringEvents = await Event.distinct("groupId", {
user: req.user._id,
groupId: { $ne: null },
startAt: { $gte: Date.now() },
}).exec();

const existingNonrecurringEvents = await Event.countDocuments({
user: req.user._id,
groupId: null,
startAt: { $gte: Date.now() },
}).exec();
const existingNonrecurringEvents = await Event.countDocuments({
user: req.user._id,
groupId: null,
startAt: { $gte: Date.now() },
}).exec();

if (existingRecurringEvents.length + existingNonrecurringEvents >= 5) {
throw httpError(403, "Exceeded maximum allowed events per user.");
}

next();
} catch (err) {
next(err);
if (
existingRecurringEvents.length + existingNonrecurringEvents >=
MAX_EVENTS_PER_USER
) {
throw httpError(403, "Exceeded maximum allowed events per user.");
}

next();
};

module.exports = maxEvents;
16 changes: 6 additions & 10 deletions server/middleware/validateBody.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
const httpError = require("../utilities/httpError");

const validateBody = schema => {
const func = async (req, _, next) => {
try {
const formData = req.body;
const { error } = schema.validate(formData);
if (error) {
throw httpError(400, error.message);
}
next();
} catch (err) {
next(err);
const func = (req, _, next) => {
const formData = req.body;
const { error } = schema.validate(formData);
if (error) {
throw httpError(400, error.message);
}
next();
};

return func;
Expand Down
92 changes: 51 additions & 41 deletions server/models/Event.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
"use strict";

const mongoose = require("mongoose");
const Joi = require("joi");

STRING_MAX_LENGTH = 280;
const STRING_MAX_LENGTH = 280;
// Event's starting date should be less than (strictly) EVENT_MAX_DATE
const EVENT_MAX_DATE = "2024-01-01";
// Recurring events should span no more than MAX_RECURRENCE_PERIOD number of days
const MAX_RECURRENCE_PERIOD = 90;
const DAYS_OF_WEEK = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
];

const EventSchema = new mongoose.Schema(
{
Expand All @@ -22,9 +37,9 @@ const EventSchema = new mongoose.Schema(
required: true,
validate: {
validator: function (value) {
greateThanToday = value > new Date() - 1000 * 60 * 60 * 26;
limitTo2023 = value < new Date("2024-01-01");
return greateThanToday && limitTo2023;
const greaterThanToday = value > new Date() - 1000 * 60 * 60 * 26;
const limitTo2023 = value < new Date(EVENT_MAX_DATE);
return greaterThanToday && limitTo2023;
},
},
},
Expand Down Expand Up @@ -66,45 +81,47 @@ const createEventSchema = Joi.object({
location: Joi.string().trim().min(1).max(STRING_MAX_LENGTH).required(),
discordName: Joi.string().trim().min(1).max(STRING_MAX_LENGTH).required(),
firstEventStart: Joi.date()
// Subtract one day because time on server may differ from client
.min(new Date() - 60 * 60 * 24 * 1000)
.timestamp()
// Event should be in the future
.min("now")
.required(),
firstEventEnd: Joi.date()
// time on server may differ from the time on client
// the most extreme offsets are +12 and -14 hours from utc
.min(new Date() - 1000 * 60 * 60 * 26)
.timestamp()
// .greater(Joi.ref("firstEventStart"))
.required(),
lastEventStart: Joi.date()
// last event start date should not be earlier than first event start date
.min(Joi.ref("firstEventStart"))
// at most 90 days from firstEventStart
.max(
Joi.ref("firstEventStart", {
adjust: val => {
let date = new Date(val);
date.setDate(date.getDate() + 90);
return date;
},
})
)
// Limit events to 2023
.less("2024-01-01")
.timestamp()
// If recurring rate is 'noRecurr' lastEventStart should be equal to firstEventStart
.when(Joi.ref("/recurring.rate"), {
is: Joi.valid("noRecurr"),
then: Joi.ref("firstEventStart"),
})
// If recurring rate is 'weekly' then
.when(Joi.ref("/recurring.rate"), {
is: Joi.valid("weekly"),
then: Joi.date()
// lastEventStart should be greater than or equal to firstEventStart
.min(Joi.ref("firstEventStart"))
// and at most MAX_RECURRENCE_PERIOD days from firstEventStart
.max(
Joi.ref("firstEventStart", {
adjust: function (value) {
const date = new Date(value);
date.setDate(date.getDate() + MAX_RECURRENCE_PERIOD);
return date;
},
})
),
})
// Limit events to EVENT_MAX_DATE
.less(EVENT_MAX_DATE)
.required()
.messages({
"date.max":
'"lastEventStart" must be within 90 days of "ref:firstEventStart"',
"date.max": `"lastEventStart" must be within ${MAX_RECURRENCE_PERIOD} days of "ref:firstEventStart"`,
}),
recurring: Joi.object({
// Rate is either "noRecurr" or "weekly"
rate: Joi.string()
.valid("noRecurr", "weekly")
.max(STRING_MAX_LENGTH)
.required(),
rate: Joi.string().valid("noRecurr", "weekly").required(),
days: Joi.when(Joi.ref("rate"), {
// if rate is noRecurr
is: Joi.valid("noRecurr"),
Expand All @@ -114,22 +131,15 @@ const createEventSchema = Joi.object({
otherwise: Joi.array()
.min(1)
.max(7)
.items(
Joi.string().valid(
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
)
),
.items(Joi.string().valid(...DAYS_OF_WEEK)),
}).required(),
}),
}).required(),
});

module.exports = {
Event,
createEventSchema,
STRING_MAX_LENGTH,
MAX_RECURRENCE_PERIOD,
EVENT_MAX_DATE,
};
Loading