feat(tests): add Jest+Supertest infrastructure with jump route suite (14 tests)

- Extract Express app creation into src/createApp.js to enable testing without starting the server
- Add Jest, Supertest, test helpers (createTestApp, auth token generator)
- Write 14 integration tests for skydive/jumps: auth protection, CRUD, ownership checks

Bugs caught and fixed by the test suite:
- MongoDB models: author field typed as Schema.Types.UUID (Mongoose 6 rejects string UUIDs) → changed to String
- Jump.toJSONFor: called toProfileJSONFor on a UUID string → guarded with typeof check
- exceptions.handler: used err.statusCode but express-jwt throws err.status → returns 500 on 401 errors
- Jump.count(query): deprecated Mongoose method was ignoring the filter → replaced with countDocuments
- jumps.routes GET /: $or query included non-schema field "title" stripped by strictQuery, leaving {} (match-all) → replaced with slug/lieu search
- $regex: was passing a JS RegExp object which serializes to {} in MongoDB → now passes raw string
This commit is contained in:
2026-04-26 01:23:01 +02:00
parent 4c765945b3
commit da1f9437ab
21 changed files with 4068 additions and 163 deletions
+35
View File
@@ -0,0 +1,35 @@
var express = require('express'),
session = require('express-session'),
cors = require('cors'),
errorhandler = require('errorhandler');
const config = require('./config'),
swaggerUi = require('swagger-ui-express'),
swaggerSpec = require('./config/swagger');
function createApp() {
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(require('method-override')());
app.use(express.static(__dirname + '/../public'));
app.use(session({
secret: config.secret,
cookie: { maxAge: config.session_lifetime },
resave: false,
saveUninitialized: false
}));
if (process.env.APP_ENV !== 'production') {
app.use(errorhandler());
}
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
swaggerOptions: { persistAuthorization: true },
}));
return app;
}
module.exports = createApp;
+1 -1
View File
@@ -5,7 +5,7 @@ var AeronefSchema = new mongoose.Schema({
slug: { type: String, lowercase: true, unique: true },
aeronef: String,
imat: String,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
AeronefSchema.pre('validate', function (next) {
+1 -1
View File
@@ -8,7 +8,7 @@ var ApplicationSchema = new mongoose.Schema({
slug: { type: String, lowercase: true, unique: true },
title: String,
description: String,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, { timestamps: true, toJSON: { virtuals: true } });
ApplicationSchema.plugin(uniqueValidator, { message: 'is already taken' });
+1 -1
View File
@@ -5,7 +5,7 @@ var CanopySchema = new mongoose.Schema({
slug: { type: String, lowercase: true, unique: true },
voile: String,
taille: Number,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
CanopySchema.pre('validate', function (next) {
+1 -1
View File
@@ -5,7 +5,7 @@ var DropzoneSchema = new mongoose.Schema({
slug: { type: String, lowercase: true, unique: true },
lieu: String,
oaci: String,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
DropzoneSchema.pre('validate', function (next) {
+4 -2
View File
@@ -25,7 +25,7 @@ var JumpSchema = new mongoose.Schema({
video: String,
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'JumpFile' }],
x2data: { type: mongoose.Schema.Types.ObjectId, ref: 'X2DataLog' },
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
JumpSchema.plugin(uniqueValidator, { message: 'is already taken' });
@@ -79,7 +79,9 @@ JumpSchema.methods.toJSONFor = function(user) {
x2data: this.x2data ? this.x2data.toJSONFor() : null,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
author: this.author.toProfileJSONFor(user)
author: this.author && typeof this.author.toProfileJSONFor === 'function'
? this.author.toProfileJSONFor(user)
: this.author ?? null
};
};
+1 -1
View File
@@ -11,7 +11,7 @@ var JumpFileSchema = new mongoose.Schema({
enum : ['csv', 'image', 'kml', 'video'],
default: 'video'
},
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
JumpFileSchema.plugin(uniqueValidator, { message: 'is already taken' });
+1 -1
View File
@@ -60,7 +60,7 @@ var X2DataLogSchema = new mongoose.Schema({
],
date: String,
createdOn: String,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
X2DataLogSchema.methods.toJSONFor = function() {
+1 -1
View File
@@ -24,7 +24,7 @@ var HWClanSchema = new mongoose.Schema({
title: String,
topActivity: String,
topDungeon: String,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
HWClanSchema.plugin(uniqueValidator, { message: 'is already taken' });
+2 -2
View File
@@ -47,7 +47,7 @@ var HWMemberSchemaOld = new mongoose.Schema({
scoreGifts: Number,
warGifts: Number,
rewards: Number,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
*/
var HWMemberSchema = new mongoose.Schema({
@@ -104,7 +104,7 @@ var HWMemberSchema = new mongoose.Schema({
scoreGifts: Number,
warGifts: Number,
rewards: Number,
author: { type: mongoose.Schema.Types.UUID, ref: 'User' }
author: { type: String }
}, {timestamps: true, toJSON: {virtuals: true}});
HWMemberSchema.plugin(uniqueValidator, { message: 'is already taken' });
+1 -1
View File
@@ -27,7 +27,7 @@ const exceptionHandler = {
},
// eslint-disable-next-line no-unused-vars
fianlErrorHandler: (err, req, res, next) => {
res.status(err.statusCode || 500);
res.status(err.statusCode || err.status || 500);
if (!isProduction) {
/* development error handler will print stacktrace */
res.json({errors: {
@@ -54,7 +54,7 @@ router.get('/', auth.required, function (req, res, next) {
.sort({ createdAt: 'desc' })
.populate('author')
.exec(),
Application.count(query).exec(),
Application.countDocuments(query).exec(),
req.payload ? UserService.getUserById(req.payload.id) : null
]).then(function (results) {
let applications = results[0];
+4 -5
View File
@@ -39,13 +39,12 @@ router.get('/', auth.required, function (req, res, next) {
offset = req.query.offset;
}
if (typeof req.query.title !== 'undefined') {
const rgx = new RegExp(`.*${req.query.title}.*`);
query = {
$or: [
{ title: { $regex: rgx, $options: "i" } },
{ slug: { $regex: rgx, $options: "i" } },
{ slug: { $regex: req.query.title, $options: 'i' } },
{ lieu: { $regex: req.query.title, $options: 'i' } },
],
}
};
}
if (typeof req.query.numeroRangeStart !== 'undefined' && typeof req.query.numeroRangeEnd !== 'undefined') {
query.numero = {
@@ -103,7 +102,7 @@ router.get('/', auth.required, function (req, res, next) {
.populate('files')
.populate('x2data')
.exec(),
Jump.count(query).exec(),
Jump.countDocuments(query).exec(),
req.payload ? UserService.getUserById(req.payload.id) : null
]).then(function (results) {
let jumps = results[0];