Skip to content

Commit aaf0e08

Browse files
authored
Merge pull request #53 from SpaIns/LCS-82/Add-Server-Problem-Route-Tests
Lcs 82/add server problem route tests
2 parents 407072d + fdc7fca commit aaf0e08

File tree

9 files changed

+1110
-110
lines changed

9 files changed

+1110
-110
lines changed

Readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ Both tests and coverage can be run for specific subsections - for example, to ru
165165
| > Auth | 92% coverage - remaining is defensive |
166166
| > Users| 82% coverage - remaining is defensive |
167167
| > Lists | 0% |
168-
| > Problems | 0% |
168+
| > Problems | 86% coverage - remaining is defensive |
169169
| > ProblemStatuses | 0% |
170170
| > Submissions | 0% |
171171
| > Middleware | 0% |

server/config/db.js

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,42 @@ const mongoose = require('mongoose') // For DB access
22
const dotenv = require('dotenv') // For environment var access
33
dotenv.config({path: '../.env'}) // Load our environ vars
44

5-
// Set the enviroment variable MONGO_DB access to the string value of the access point to the DB,
6-
// with credentials
7-
85
/*
9-
The mongoDB connection string, broken up to be readable
10-
11-
Generic version from docs:
12-
'mongodb://username:password@host:port/database?options'
13-
14-
For this project
15-
'mongodb://${process.env.MONGODB_USER}:${process.env.MONGODB_PASS}'
16-
@${process.env.MONGODB_HOST}:${process.env.MONGODB_PORT}/${process.env.DATABASE_NAME}
17-
?authSource=admin
18-
196
Due to some issues reported online, changed to have options in connect function
207
See: https://github.com/Automattic/mongoose/issues/8180, https://github.com/Automattic/mongoose/issues/8381
218
229
May need &tlsInsecure=true option
2310
*/
24-
let db = ''
25-
// Connect to test database if started with Testing var enabled
11+
// Connect to test database if started with Testing var enabled, otherwise prod db
2612
const TEST = process.env.TESTING
27-
if (TEST) {
28-
db = `mongodb://${process.env.TEST_MONGODB_HOST}:${process.env.TEST_MONGODB_PORT}?retryWrites=true&w=majority`
29-
}
30-
else {
31-
db = `mongodb://${process.env.MONGODB_HOST}:${process.env.MONGODB_PORT}?retryWrites=true&w=majority`
32-
}
13+
const HOST = (TEST ? process.env.TEST_MONGODB_HOST : process.env.MONGODB_HOST)
14+
const PORT = (TEST ? process.env.TEST_MONGODB_PORT : process.env.MONGODB_PORT)
15+
// The MongoDB connection string
16+
const db = `mongodb://${HOST}:${PORT}?retryWrites=true&w=majority`
3317

3418
// Connect to our MongoDB instance
3519
const connectDB = async () => {
3620
try {
37-
// Use test parameters if we're connecting to the test DB
21+
// Use test parameters if we're connecting to the test DB otherwise use production params
22+
const USER = (TEST ? process.env.TEST_MONGODB_USER : process.env.MONGODB_USER)
23+
const PASS = (TEST ? process.env.TEST_MONGODB_PASS : process.env.MONGODB_PASS)
24+
const DB_NAME = process.env.DATABASE_NAME
3825
if (TEST) {
3926
console.log('Connecting to test database...')
40-
await mongoose.connect(db, {
41-
// These are options to get rid of depreciation messages
42-
useNewUrlParser: true,
43-
useUnifiedTopology: true,
44-
useCreateIndex: true,
45-
user: `${process.env.TEST_MONGODB_USER}`,
46-
pass: `${process.env.TEST_MONGODB_PASS}`,
47-
dbName: `${process.env.DATABASE_NAME}`,
48-
})
49-
console.log(`MongoDB connected on ${process.env.TEST_MONGODB_HOST} via port ${process.env.TEST_MONGODB_PORT}...`)
50-
}
51-
else {
52-
await mongoose.connect(db, {
53-
// These are options to get rid of depreciation messages
54-
useNewUrlParser: true,
55-
useUnifiedTopology: true,
56-
useCreateIndex: true,
57-
user: `${process.env.MONGODB_USER}`,
58-
pass: `${process.env.MONGODB_PASS}`,
59-
dbName: `${process.env.DATABASE_NAME}`,
60-
})
61-
console.log(`MongoDB connected on ${process.env.MONGODB_HOST} via port ${process.env.MONGODB_PORT}...`)
6227
}
28+
await mongoose.connect(db, {
29+
// These are options to get rid of depreciation messages
30+
useNewUrlParser: true,
31+
useUnifiedTopology: true,
32+
useCreateIndex: true,
33+
user: `${USER}`,
34+
pass: `${PASS}`,
35+
dbName: `${DB_NAME}`,
36+
})
37+
console.log(`MongoDB connected on ${HOST} via port ${PORT}...`)
6338
} catch (error) {
64-
console.log('Failed to connect to the database with error: ')
65-
console.log(error.message)
39+
console.log('Failed to connect to the database with error: \n' + error)
40+
console.log('Message only:\n' + error.message)
6641
// Exit w/ failure on error
6742
process.exit(1)
6843
}

server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"authCoverage": "nyc --reporter=lcov --reporter=text-lcov npm run authTest",
3333
"userTest": "env TESTING=true mocha ./tests/Users/*.test.js --exit --inspect=0.0.0.0:9299 --timeout 120000",
3434
"userCoverage": "nyc --reporter=lcov --reporter=text-lcov npm run userTest",
35+
"problemTest": "env TESTING=true mocha ./tests/Problems/*.test.js --exit --inspect=0.0.0.0:9299 --timeout 120000",
36+
"problemCoverage": "nyc --reporter=lcov --reporter=text-lcov npm run problemTest",
3537

3638
"start": "node server",
3739
"server": "nodemon server",

server/routes/api/problems.js

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ const router = express.Router()
1111
dotenv.config({path: '../../.env'}) // for environ variables
1212

1313
// @route GET /api/problems/
14-
// @desc Get all problems
14+
// @desc Get all problems, or problems within an ID range
1515
// @access Public
1616
router.get('/', async (req, res) => {
1717
try {
18-
start = null
19-
end = null
18+
// Set defaults for range from 0 to max int
19+
let start = 0
20+
let end = Number.MAX_SAFE_INTEGER
21+
// Adjust range if range was provided
2022
if (req.query.start) {
2123
start = req.query.start
2224
}
@@ -25,23 +27,9 @@ router.get('/', async (req, res) => {
2527
}
2628
// NOTE: We're finding based on id, NOT _id! _id is the DB id, whereas id is the leetcode
2729
// problem ID!
28-
let problems = null
29-
// TODO: Figure out if there's a way to pass null into gte/lte w/o causing problems
30-
// such that this logic can be condensed to a single query again
31-
if (start && end){
32-
problems = await Problem.find().where('id').gte(start).lte(end)
33-
}
34-
else if (start) {
35-
problems = await Problem.find().where('id').gte(start)
36-
}
37-
else if (end) {
38-
problems = await Problem.find().where('id').lte(end)
39-
}
40-
else {
41-
problems = await Problem.find()
42-
}
30+
const problems = await Problem.find().where('id').gte(start).lte(end)
4331

44-
if (!problems) {
32+
if (!problems || problems.length === 0) {
4533
return res.status(404).json({errors: [{msg: 'No problems found.'}]})
4634
}
4735

@@ -63,29 +51,35 @@ router.get('/name/:search', async (req, res) => {
6351
// TODO: Figure out how/if RegExp objects can be used in mongo searchs
6452
// re = RegExp('\\b(' + req.params.search + ')\\b', 'i')
6553

66-
// If we don't pass a search term, don't try to match, just return
67-
if (!req.params.search) {
68-
return res.json({})
54+
// If we don't pass a search term, don't try to match, just return err
55+
if (!req.params.search || req.params.search === '') {
56+
return res.status(404).json({errors: [{msg: 'No problems found.'}]})
6957
}
7058
const problems = await Problem.find({$or:
7159
[
7260
{name: {$regex: req.params.search, $options: 'i'}},
7361
{problem_text: {$regex: req.params.search, $options: 'i'}},
7462
]}
7563
).sort({id: 1})
64+
65+
66+
if (!problems || problems.length === 0) {
67+
return res.status(404).json({errors: [{msg: 'No problems found.'}]})
68+
}
69+
7670
return res.json({problems})
7771
} catch (error) {
7872
console.error('Error when getting problems via search ' + error.message)
7973
return res.status(500).json({errors: [ {msg: 'Server error.'}]})
8074
}
8175
})
8276

83-
// @route GET /api/problems/:id
77+
// @route GET /api/problems/id/:id
8478
// @desc Get a problem by id
8579
// @access Public
8680
router.get('/id/:id', async (req, res) => {
8781
try {
88-
if (req.params.id === 'undefined') {
82+
if (!req.params.id || isNaN(parseInt(req.params.id))) {
8983
return res.json({})
9084
}
9185
// Try to get the problem by ID
@@ -94,10 +88,10 @@ router.get('/id/:id', async (req, res) => {
9488
// Check that the problem actually exists
9589
if (!problem) {
9690
// Doesn't exist, return bad request
97-
return res.status(404).json({msg: 'Problem not found.'})
91+
return res.status(404).json({errors: [{msg: 'Problem not found.'}]})
9892
}
9993

100-
return res.json({problem})
94+
return res.json(problem)
10195
} catch (error) {
10296
console.error('Get problem by id err: ' + error.message)
10397
return res.status(500).json({errors: [ {msg: 'Server error.'}]})
@@ -113,13 +107,13 @@ router.post('/', [auth, [
113107
check('id', 'Problem must have valid id.').not().isEmpty(),
114108
check('name', 'Problem must have a name').not().isEmpty(),
115109
check('problem_text', 'Problem must have accompanying text').not().isEmpty(),
116-
check('link', 'Must include link to problem').not().isEmpty(),
110+
check('link', 'Problem must include link to problem').not().isEmpty(),
117111
check('difficulty', 'Problem must have a difficulty level').isNumeric(),
118112
check('is_premium', 'Problem must be marked premium or not').isBoolean(),
119113
]], async (req, res) => {
120114
// Check our request contains required fields
121115
const validationErrors = validationResult(req)
122-
if (!validationErrors.isEmpty) {
116+
if (!validationErrors.isEmpty()) {
123117
// Something was missing, send an error
124118
return res.status(400).json({errors : validationErrors.array()})
125119
}
@@ -130,7 +124,7 @@ router.post('/', [auth, [
130124
const user = await User.findById(req.user.id)
131125
const admin_email = process.env.ADMIN_EMAIL
132126
if (!user || user.email != admin_email) {
133-
return res.status(401).json({msg: 'Access denied'})
127+
return res.status(401).json({errors: [{msg: 'Access denied'}]})
134128
}
135129

136130
// Valid admin - create the problem and post it.
@@ -176,16 +170,16 @@ router.post('/', [auth, [
176170
// @access Admin
177171
router.post('/bulk', [auth, [
178172
check('problems', 'Must submit array of problems.').isArray(),
179-
check('problems.*.id', 'Problem must have valid id.').not().isEmpty(),
180-
check('problems.*.name', 'Problem must have a name').not().isEmpty(),
181-
check('problems.*.problem_text', 'Problem must have accompanying text').not().isEmpty(),
182-
check('problems.*.link', 'Must include link to problem').not().isEmpty(),
183-
check('problems.*.difficulty', 'Problem must have a difficulty level').isNumeric(),
184-
check('problems.*.is_premium', 'Problem must be marked premium or not').isBoolean(),
173+
check('problems.*.id', 'Problem(s) must have valid id.').not().isEmpty(),
174+
check('problems.*.name', 'Problem(s) must have a name').not().isEmpty(),
175+
check('problems.*.problem_text', 'Problem(s) must have accompanying text').not().isEmpty(),
176+
check('problems.*.link', 'Problems(s) must include link to problem').not().isEmpty(),
177+
check('problems.*.difficulty', 'Problem(s) must have a difficulty level').isNumeric(),
178+
check('problems.*.is_premium', 'Problem(s) must be marked premium or not').isBoolean(),
185179
]], async (req, res) => {
186180
// Check our request contains required fields
187181
const validationErrors = validationResult(req)
188-
if (!validationErrors.isEmpty) {
182+
if (!validationErrors.isEmpty()) {
189183
// Something was missing, send an error
190184
return res.status(400).json({errors : validationErrors.array()})
191185
}
@@ -196,7 +190,7 @@ router.post('/bulk', [auth, [
196190
const user = await User.findById(req.user.id)
197191
const admin_email = process.env.ADMIN_EMAIL
198192
if (!user || user.email != admin_email) {
199-
return res.status(401).json({msg: 'Access denied'})
193+
return res.status(401).json({errors: [{msg: 'Access denied'}]})
200194
}
201195

202196
const {
@@ -249,7 +243,7 @@ router.post('/bulk', [auth, [
249243
}
250244
}
251245
// Return the results of which problems we could add and which we couldn't
252-
return res.json({'NotAdded' : notAdded, 'Added:' : added})
246+
return res.json({'NotAdded' : notAdded, 'Added' : added})
253247
} catch (error) {
254248
console.error('Error when posting new LC problems ' + error.message)
255249
return res.status(500).json({errors: [ {msg: 'Server error.'}]})
@@ -268,7 +262,7 @@ router.put('/bulk', [
268262
check('problems.*', 'All problem IDs must be a valid MongoID').isMongoId(),
269263
], async (req, res) => {
270264
const validationErrors = validationResult(req)
271-
if (!validationErrors.isEmpty) {
265+
if (!validationErrors.isEmpty()) {
272266
// Something was missing, send an error
273267
return res.status(400).json({errors : validationErrors.array()})
274268
}
@@ -279,9 +273,9 @@ router.put('/bulk', [
279273
const problems = await Problem.find({'_id' : {$in: object_ids}})
280274

281275
// Check that the problems actually exist
282-
if (!problems) {
276+
if (!problems || problems.length === 0) {
283277
// Doesn't exist, return bad request
284-
return res.status(404).json({msg: 'Problems not found.'})
278+
return res.status(404).json({errors: [ {msg: 'Problems not found.'} ]})
285279
}
286280
return res.json({problems})
287281
} catch (error) {
@@ -298,7 +292,7 @@ router.put('/', [auth, [
298292
]], async (req, res) => {
299293
// Check our request contains required fields
300294
const validationErrors = validationResult(req)
301-
if (!validationErrors.isEmpty) {
295+
if (!validationErrors.isEmpty()) {
302296
// Something was missing, send an error
303297
return res.status(400).json({errors : validationErrors.array()})
304298
}
@@ -309,7 +303,7 @@ router.put('/', [auth, [
309303
const user = await User.findById(req.user.id)
310304
const admin_email = process.env.ADMIN_EMAIL
311305
if (!user || user.email != admin_email) {
312-
return res.status(401).json({msg: 'Access denied'})
306+
return res.status(401).json({errors: [{msg: 'Access denied'}]})
313307
}
314308

315309
// Valid admin - parse out the updated fields
@@ -347,4 +341,28 @@ router.put('/', [auth, [
347341
}
348342
})
349343

344+
345+
// @route DELETE api/problems/:id
346+
// @desc Delete problem by LeetCode ID
347+
// @access Admin
348+
router.delete('/:id', [auth],
349+
async (req, res) => {
350+
try {
351+
// Get the User by the passed auth ID
352+
const user = await User.findById(req.user.id).select(['-password', '-__v'])
353+
// Ensure we could find them and that they're admin
354+
const admin_email = process.env.ADMIN_EMAIL
355+
if (!user || user.email != admin_email) {
356+
return res.status(401).json({errors: [{msg: 'Access denied'}]})
357+
}
358+
// Delete the problem
359+
await Problem.deleteOne({id: req.params.id})
360+
// Return true as JSON
361+
return res.json(true)
362+
} catch (error) {
363+
console.log('Error when deleting problem by LC id ' + error.message)
364+
return res.status(500).json({errors: [ {msg: 'Server error.'}]})
365+
}
366+
})
367+
350368
module.exports = router

server/routes/api/users.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ async (req, res) => {
243243
}
244244
// Delete the user
245245
await User.deleteOne({id: req.user.id})
246-
// Return the user as JSON
246+
// Return true as JSON
247247
return res.json(true)
248248
} catch (error) {
249249
console.log('Error when deleting user id ' + error.message)

0 commit comments

Comments
 (0)