mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
tests: Add tests for recoverCron
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { cron } from '../../../../../website/server/libs/api-v3/cron';
|
import Bluebird from 'bluebird';
|
||||||
|
import { recoverCron, cron } from '../../../../../website/server/libs/api-v3/cron';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
import * as Tasks from '../../../../../website/server/models/task';
|
import * as Tasks from '../../../../../website/server/models/task';
|
||||||
import { clone } from 'lodash';
|
import { clone } from 'lodash';
|
||||||
@@ -562,3 +563,68 @@ describe('cron', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('recoverCron', () => {
|
||||||
|
let locals, status, execStub;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
execStub = sandbox.stub();
|
||||||
|
sandbox.stub(User, 'findOne').returns({ exec: execStub });
|
||||||
|
|
||||||
|
status = { times: 0 };
|
||||||
|
locals = {
|
||||||
|
user: new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username',
|
||||||
|
lowerCaseUsername: 'username',
|
||||||
|
email: 'email@email.email',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if user cannot be found', async (done) => {
|
||||||
|
execStub.returns(Bluebird.resolve(null));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await recoverCron(status, locals);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.eql(`User ${locals.user._id} not found while recovering.`);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increases status.times count and reruns up to 3 times', async (done) => {
|
||||||
|
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||||
|
execStub.onCall(3).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'}));
|
||||||
|
|
||||||
|
await recoverCron(status, locals);
|
||||||
|
|
||||||
|
expect(status.times).to.eql(3);
|
||||||
|
expect(locals.user).to.eql({_cronSignature: 'NOT_RUNNING'});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if recoverCron runs 4 times', async (done) => {
|
||||||
|
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await recoverCron(status, locals);
|
||||||
|
} catch (err) {
|
||||||
|
expect(status.times).to.eql(4);
|
||||||
|
expect(err.message).to.eql(`Impossible to recover from cron for user ${locals.user._id}.`);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import {
|
|||||||
generateTodo,
|
generateTodo,
|
||||||
generateDaily,
|
generateDaily,
|
||||||
} from '../../../../helpers/api-unit.helper';
|
} from '../../../../helpers/api-unit.helper';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron';
|
import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
import { model as Group } from '../../../../../website/server/models/group';
|
import { model as Group } from '../../../../../website/server/models/group';
|
||||||
import * as Tasks from '../../../../../website/server/models/task';
|
import * as Tasks from '../../../../../website/server/models/task';
|
||||||
import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService';
|
import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService';
|
||||||
|
import * as cronLib from '../../../../../website/server/libs/api-v3/cron';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
describe('cron middleware', () => {
|
describe('cron middleware', () => {
|
||||||
@@ -45,6 +47,10 @@ describe('cron middleware', () => {
|
|||||||
.catch(done);
|
.catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('calls next when user is not attached', (done) => {
|
it('calls next when user is not attached', (done) => {
|
||||||
res.locals.user = null;
|
res.locals.user = null;
|
||||||
cronMiddleware(req, res, (err) => done(err));
|
cronMiddleware(req, res, (err) => done(err));
|
||||||
@@ -191,4 +197,28 @@ describe('cron middleware', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('recovers from failed cron and does not error when user is already cronning', async (done) => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({days: 2});
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
let updatedUser = cloneDeep(user);
|
||||||
|
updatedUser.nMatched = 0;
|
||||||
|
|
||||||
|
sandbox.spy(cronLib, 'recoverCron');
|
||||||
|
|
||||||
|
sandbox.stub(User, 'update')
|
||||||
|
.withArgs({ _id: user._id, _cronSignature: 'NOT_RUNNING' })
|
||||||
|
.returns({
|
||||||
|
exec () {
|
||||||
|
return Promise.resolve(updatedUser);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cronMiddleware(req, res, () => {
|
||||||
|
expect(cronLib.recoverCron).to.be.calledOnce;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import Bluebird from 'bluebird';
|
||||||
|
import { model as User } from '../../models/user';
|
||||||
import common from '../../../../common/';
|
import common from '../../../../common/';
|
||||||
import { preenUserHistory } from '../../libs/api-v3/preening';
|
import { preenUserHistory } from '../../libs/api-v3/preening';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -79,6 +81,29 @@ function performSleepTasks (user, tasksByType, now) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function recoverCron (status, locals) {
|
||||||
|
let {user} = locals;
|
||||||
|
|
||||||
|
await Bluebird.delay(300);
|
||||||
|
|
||||||
|
let reloadedUser = await User.findOne({_id: user._id}).exec();
|
||||||
|
|
||||||
|
if (!reloadedUser) {
|
||||||
|
throw new Error(`User ${user._id} not found while recovering.`);
|
||||||
|
} else if (reloadedUser._cronSignature !== 'NOT_RUNNING') {
|
||||||
|
status.times++;
|
||||||
|
|
||||||
|
if (status.times < 4) {
|
||||||
|
await recoverCron(status, locals);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Impossible to recover from cron for user ${user._id}.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
locals.user = reloadedUser;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Perform various beginning-of-day reset actions.
|
// Perform various beginning-of-day reset actions.
|
||||||
export function cron (options = {}) {
|
export function cron (options = {}) {
|
||||||
let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options;
|
let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options;
|
||||||
|
|||||||
@@ -5,33 +5,11 @@ import * as Tasks from '../../models/task';
|
|||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
import { model as Group } from '../../models/group';
|
import { model as Group } from '../../models/group';
|
||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
import { cron } from '../../libs/api-v3/cron';
|
import { recoverCron, cron } from '../../libs/api-v3/cron';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const daysSince = common.daysSince;
|
const daysSince = common.daysSince;
|
||||||
|
|
||||||
async function recoverCron (status, req, res) {
|
|
||||||
let user = res.locals.user;
|
|
||||||
|
|
||||||
await Bluebird.delay(300);
|
|
||||||
let reloadedUser = await User.findOne({_id: user._id}).exec();
|
|
||||||
|
|
||||||
if (!reloadedUser) {
|
|
||||||
throw new Error(`User ${user._id} not found while recovering.`);
|
|
||||||
} else if (reloadedUser._cronSignature !== 'NOT_RUNNING') {
|
|
||||||
status.times++;
|
|
||||||
|
|
||||||
if (status.times < 4) {
|
|
||||||
await recoverCron(status, req, res);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Impossible to recover from cron for user ${user._id}.`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.locals.user = reloadedUser;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function cronAsync (req, res) {
|
async function cronAsync (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
if (!user) return null; // User might not be available when authentication is not mandatory
|
if (!user) return null; // User might not be available when authentication is not mandatory
|
||||||
@@ -210,7 +188,7 @@ async function cronAsync (req, res) {
|
|||||||
times: 0,
|
times: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
recoverCron(recoveryStatus, req, res);
|
recoverCron(recoveryStatus, res.locals);
|
||||||
} else {
|
} else {
|
||||||
// For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running
|
// For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running
|
||||||
// at the next request
|
// at the next request
|
||||||
|
|||||||
Reference in New Issue
Block a user