From fb56f7df202446686ed1276e1580b6882ddbc654 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 28 Feb 2024 17:54:42 +0100 Subject: [PATCH] Fix various tests --- .../unit/middlewares/ensureTimeTravelMode.js | 38 ++++++ .../debug/GET-debug-timeTravelTime.test.js | 44 +++++++ .../debug/POST-debug_jumpTime.test.js | 75 ++++++++++++ test/common/ops/buy/buyQuestGems.js | 3 + test/common/ops/buy/purchase.js | 15 ++- test/common/ops/unlock.js | 94 +++++++++------ website/client/src/components/appFooter.vue | 21 ++-- website/common/script/content/index.js | 7 +- website/common/script/index.js | 5 + website/common/script/ops/buy/buyArmoire.js | 2 +- website/server/controllers/api-v3/debug.js | 113 ++++++++++-------- website/server/libs/worldState.js | 44 +++---- .../middlewares/ensureTimeTravelMode.js | 12 ++ 13 files changed, 349 insertions(+), 124 deletions(-) create mode 100644 test/api/unit/middlewares/ensureTimeTravelMode.js create mode 100644 test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_jumpTime.test.js create mode 100644 website/server/middlewares/ensureTimeTravelMode.js diff --git a/test/api/unit/middlewares/ensureTimeTravelMode.js b/test/api/unit/middlewares/ensureTimeTravelMode.js new file mode 100644 index 0000000000..4c24838d24 --- /dev/null +++ b/test/api/unit/middlewares/ensureTimeTravelMode.js @@ -0,0 +1,38 @@ +/* eslint-disable global-require */ +import nconf from 'nconf'; +import { + generateRes, + generateReq, + generateNext, +} from '../../../helpers/api-unit.helper'; +import ensureDevelpmentMode from '../../../../website/server/middlewares/ensureDevelpmentMode'; +import { NotFound } from '../../../../website/server/libs/errors'; + +describe('timetravelMode middleware', () => { + let res; let req; let + next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + it('returns not found when not in time travel mode', () => { + sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(true); + + ensureDevelpmentMode(req, res, next); + + const calledWith = next.getCall(0).args; + expect(calledWith[0] instanceof NotFound).to.equal(true); + }); + + it('passes when in time travel mode', () => { + sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(false); + + ensureDevelpmentMode(req, res, next); + + expect(next).to.be.calledOnce; + expect(next.args[0]).to.be.empty; + }); +}); diff --git a/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js b/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js new file mode 100644 index 0000000000..49f7464a58 --- /dev/null +++ b/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js @@ -0,0 +1,44 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /debug/time-travel-time', () => { + let user; + before(async () => { + user = await generateUser({ permissions: { fullAccess: true } }); + }); + + after(() => { + nconf.set('ENABLE_TIME_TRAVEL', false); + }); + + it('returns the shifted time', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const result = await user.get('/debug/time-travel-time'); + expect(result.time).to.exist; + await user.post('/debug/jump-time', { disable: true }); + }); + + it('returns error when the user is not an admin', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const regularUser = await generateUser(); + await expect(regularUser.get('/debug/time-travel-time')) + .eventually.be.rejected.and.to.deep.equal({ + code: 400, + error: 'BadRequest', + message: 'You do not have permission to time travel.', + }); + }); + + it('returns error when not in time travel mode', async () => { + nconf.set('ENABLE_TIME_TRAVEL', false); + + await expect(user.get('/debug/time-travel-time')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_jumpTime.test.js b/test/api/v3/integration/debug/POST-debug_jumpTime.test.js new file mode 100644 index 0000000000..ccfb86cc39 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_jumpTime.test.js @@ -0,0 +1,75 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /debug/jump-time', () => { + let user; + let today; + before(async () => { + user = await generateUser({ permissions: { fullAccess: true } }); + today = new Date(); + }); + + after(async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + await user.post('/debug/jump-time', { disable: true }); + nconf.set('ENABLE_TIME_TRAVEL', false); + }); + + it('Jumps forward', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time); + expect(resultDate.getDate()).to.eql(today.getDate()); + expect(resultDate.getMonth()).to.eql(today.getMonth()); + expect(resultDate.getFullYear()).to.eql(today.getFullYear()); + const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 1 })).time); + expect(newResultDate.getDate()).to.eql(today.getDate() + 1); + expect(newResultDate.getMonth()).to.eql(today.getMonth()); + expect(newResultDate.getFullYear()).to.eql(today.getFullYear()); + }); + + it('jumps back', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time); + expect(resultDate.getDate()).to.eql(today.getDate()); + expect(resultDate.getMonth()).to.eql(today.getMonth()); + expect(resultDate.getFullYear()).to.eql(today.getFullYear()); + const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: -1 })).time); + expect(newResultDate.getDate()).to.eql(today.getDate() - 1); + expect(newResultDate.getMonth()).to.eql(today.getMonth()); + expect(newResultDate.getFullYear()).to.eql(today.getFullYear()); + }); + + it('can jump a lot', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time); + expect(resultDate.getDate()).to.eql(today.getDate()); + expect(resultDate.getMonth()).to.eql(today.getMonth()); + expect(resultDate.getFullYear()).to.eql(today.getFullYear()); + const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time); + expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1); + }); + + it('returns error when the user is not an admin', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const regularUser = await generateUser(); + await expect(regularUser.post('/debug/jump-time', { offsetDays: 1 })) + .eventually.be.rejected.and.to.deep.equal({ + code: 400, + error: 'BadRequest', + message: 'You do not have permission to time travel.', + }); + }); + + it('returns error when not in time travel mode', async () => { + nconf.set('ENABLE_TIME_TRAVEL', false); + + await expect(user.post('/debug/jump-time', { offsetDays: 1 })) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/common/ops/buy/buyQuestGems.js b/test/common/ops/buy/buyQuestGems.js index bb5346b420..94d1bc5697 100644 --- a/test/common/ops/buy/buyQuestGems.js +++ b/test/common/ops/buy/buyQuestGems.js @@ -10,6 +10,7 @@ import { BuyQuestWithGemOperation } from '../../../../website/common/script/ops/ describe('shared.ops.buyQuestGems', () => { let user; + let clock; const goldPoints = 40; const analytics = { track () {} }; @@ -26,11 +27,13 @@ describe('shared.ops.buyQuestGems', () => { beforeEach(() => { sinon.stub(analytics, 'track'); sinon.spy(pinnedGearUtils, 'removeItemByPath'); + clock = sinon.useFakeTimers(new Date('2024-01-16')); }); afterEach(() => { analytics.track.restore(); pinnedGearUtils.removeItemByPath.restore(); + clock.restore(); }); context('single purchase', () => { diff --git a/test/common/ops/buy/purchase.js b/test/common/ops/buy/purchase.js index 8f70523d1c..ef10b7c182 100644 --- a/test/common/ops/buy/purchase.js +++ b/test/common/ops/buy/purchase.js @@ -15,6 +15,7 @@ import { describe('shared.ops.purchase', () => { const SEASONAL_FOOD = moment().isBefore('2021-11-02T20:00-04:00') ? 'Candy_Base' : 'Meat'; let user; + let clock; const goldPoints = 40; const analytics = { track () {} }; @@ -25,11 +26,13 @@ describe('shared.ops.purchase', () => { beforeEach(() => { sinon.stub(analytics, 'track'); sinon.spy(pinnedGearUtils, 'removeItemByPath'); + clock = sandbox.useFakeTimers(new Date('2024-01-10')); }); afterEach(() => { analytics.track.restore(); pinnedGearUtils.removeItemByPath.restore(); + clock.restore(); }); context('failure conditions', () => { @@ -62,7 +65,7 @@ describe('shared.ops.purchase', () => { } }); - it('returns error when unknown item is requested', async () => { + it.only('returns error when unknown item is requested', async () => { try { await purchase(user, { params: { type: 'gear', key: 'randomKey' } }); } catch (err) { @@ -82,7 +85,7 @@ describe('shared.ops.purchase', () => { it('returns error when user does not have enough gems to buy an item', async () => { try { - await purchase(user, { params: { type: 'gear', key: 'headAccessory_special_wolfEars' } }); + await purchase(user, { params: { type: 'gear', key: 'shield_special_winter2019Healer' } }); } catch (err) { expect(err).to.be.an.instanceof(NotAuthorized); expect(err.message).to.equal(i18n.t('notEnoughGems')); @@ -150,7 +153,7 @@ describe('shared.ops.purchase', () => { user.pinnedItems.push({ type: 'eggs', key: 'Wolf' }); user.pinnedItems.push({ type: 'hatchingPotions', key: 'Base' }); user.pinnedItems.push({ type: 'food', key: SEASONAL_FOOD }); - user.pinnedItems.push({ type: 'gear', key: 'headAccessory_special_tigerEars' }); + user.pinnedItems.push({ type: 'gear', key: 'shield_special_winter2019Healer' }); user.pinnedItems.push({ type: 'bundles', key: 'featheredFriends' }); }); @@ -187,7 +190,7 @@ describe('shared.ops.purchase', () => { it('purchases gear', async () => { const type = 'gear'; - const key = 'headAccessory_special_tigerEars'; + const key = 'shield_special_winter2019Healer'; await purchase(user, { params: { type, key } }); @@ -197,7 +200,8 @@ describe('shared.ops.purchase', () => { it('purchases quest bundles', async () => { const startingBalance = user.balance; - const clock = sandbox.useFakeTimers(moment('2024-03-20').valueOf()); + clock.restore(); + clock = sandbox.useFakeTimers(moment('2022-03-10').valueOf()); const type = 'bundles'; const key = 'cuddleBuddies'; const price = 1.75; @@ -216,7 +220,6 @@ describe('shared.ops.purchase', () => { expect(user.balance).to.equal(startingBalance - price); expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true); - clock.restore(); }); }); diff --git a/test/common/ops/unlock.js b/test/common/ops/unlock.js index f08608e615..6c589fe167 100644 --- a/test/common/ops/unlock.js +++ b/test/common/ops/unlock.js @@ -2,14 +2,17 @@ import get from 'lodash/get'; import unlock from '../../../website/common/script/ops/unlock'; import i18n from '../../../website/common/script/i18n'; import { generateUser } from '../../helpers/common.helper'; -import { NotAuthorized, BadRequest } from '../../../website/common/script/libs/errors'; +import { + NotAuthorized, + BadRequest, +} from '../../../website/common/script/libs/errors'; describe('shared.ops.unlock', () => { let user; + let clock; const unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie'; const unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'; const backgroundUnlockPath = 'background.giant_florals'; - const backgroundSetUnlockPath = 'background.archery_range,background.giant_florals,background.rainbows_end'; const hairUnlockPath = 'hair.color.rainbow,hair.color.yellow,hair.color.green,hair.color.purple,hair.color.blue,hair.color.TRUred'; const facialHairUnlockPath = 'hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3'; const usersStartingGems = 50 / 4; @@ -17,6 +20,11 @@ describe('shared.ops.unlock', () => { beforeEach(() => { user = generateUser(); user.balance = usersStartingGems; + clock = sandbox.useFakeTimers(new Date('2024-04-10')); + }); + + afterEach(() => { + clock.restore(); }); it('returns an error when path is not provided', async () => { @@ -31,7 +39,9 @@ describe('shared.ops.unlock', () => { it('does not unlock lost gear', async () => { user.items.gear.owned.headAccessory_special_bearEars = false; - await unlock(user, { query: { path: 'items.gear.owned.headAccessory_special_bearEars' } }); + await unlock(user, { + query: { path: 'items.gear.owned.headAccessory_special_bearEars' }, + }); expect(user.balance).to.equal(usersStartingGems); }); @@ -95,7 +105,9 @@ describe('shared.ops.unlock', () => { it('returns an error if gear is not from the animal set', async () => { try { - await unlock(user, { query: { path: 'items.gear.owned.back_mystery_202004' } }); + await unlock(user, { + query: { path: 'items.gear.owned.back_mystery_202004' }, + }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); expect(err.message).to.equal(i18n.t('invalidUnlockSet')); @@ -163,7 +175,9 @@ describe('shared.ops.unlock', () => { await unlock(user, { query: { path: backgroundUnlockPath } }); const afterBalance = user.balance; - const response = await unlock(user, { query: { path: backgroundUnlockPath } }); + const response = await unlock(user, { + query: { path: backgroundUnlockPath }, + }); expect(user.balance).to.equal(afterBalance); // do not bill twice expect(response.message).to.not.exist; @@ -176,7 +190,9 @@ describe('shared.ops.unlock', () => { await unlock(user, { query: { path: backgroundUnlockPath } }); // unlock const afterBalance = user.balance; await unlock(user, { query: { path: backgroundUnlockPath } }); // equip - const response = await unlock(user, { query: { path: backgroundUnlockPath } }); + const response = await unlock(user, { + query: { path: backgroundUnlockPath }, + }); expect(user.balance).to.equal(afterBalance); // do not bill twice expect(response.message).to.not.exist; @@ -192,8 +208,9 @@ describe('shared.ops.unlock', () => { individualPaths.forEach(path => { expect(get(user.purchased, path)).to.be.true; }); - expect(Object.keys(user.purchased.shirt).length) - .to.equal(initialShirts + individualPaths.length); + expect(Object.keys(user.purchased.shirt).length).to.equal( + initialShirts + individualPaths.length, + ); expect(user.balance).to.equal(usersStartingGems - 1.25); }); @@ -208,8 +225,9 @@ describe('shared.ops.unlock', () => { individualPaths.forEach(path => { expect(get(user.purchased, path)).to.be.true; }); - expect(Object.keys(user.purchased.hair.color).length) - .to.equal(initialHairColors + individualPaths.length); + expect(Object.keys(user.purchased.hair.color).length).to.equal( + initialHairColors + individualPaths.length, + ); expect(user.balance).to.equal(usersStartingGems - 1.25); }); @@ -219,21 +237,28 @@ describe('shared.ops.unlock', () => { const initialMustache = Object.keys(user.purchased.hair.mustache).length; const initialBeard = Object.keys(user.purchased.hair.mustache).length; - const [, message] = await unlock(user, { query: { path: facialHairUnlockPath } }); + const [, message] = await unlock(user, { + query: { path: facialHairUnlockPath }, + }); expect(message).to.equal(i18n.t('unlocked')); const individualPaths = facialHairUnlockPath.split(','); individualPaths.forEach(path => { expect(get(user.purchased, path)).to.be.true; }); - expect(Object.keys(user.purchased.hair.mustache).length + Object.keys(user.purchased.hair.beard).length) // eslint-disable-line max-len + expect( + Object.keys(user.purchased.hair.mustache).length + + Object.keys(user.purchased.hair.beard).length, + ) // eslint-disable-line max-len .to.equal(initialMustache + initialBeard + individualPaths.length); expect(user.balance).to.equal(usersStartingGems - 1.25); }); it('unlocks a full set of gear', async () => { const initialGear = Object.keys(user.items.gear.owned).length; - const [, message] = await unlock(user, { query: { path: unlockGearSetPath } }); + const [, message] = await unlock(user, { + query: { path: unlockGearSetPath }, + }); expect(message).to.equal(i18n.t('unlocked')); @@ -241,32 +266,21 @@ describe('shared.ops.unlock', () => { individualPaths.forEach(path => { expect(get(user, path)).to.be.true; }); - expect(Object.keys(user.items.gear.owned).length) - .to.equal(initialGear + individualPaths.length); + expect(Object.keys(user.items.gear.owned).length).to.equal( + initialGear + individualPaths.length, + ); expect(user.balance).to.equal(usersStartingGems - 1.25); }); - it('unlocks a full set of backgrounds', async () => { - const initialBackgrounds = Object.keys(user.purchased.background).length; - const [, message] = await unlock(user, { query: { path: backgroundSetUnlockPath } }); - - expect(message).to.equal(i18n.t('unlocked')); - const individualPaths = backgroundSetUnlockPath.split(','); - individualPaths.forEach(path => { - expect(get(user.purchased, path)).to.be.true; - }); - expect(Object.keys(user.purchased.background).length) - .to.equal(initialBackgrounds + individualPaths.length); - expect(user.balance).to.equal(usersStartingGems - 3.75); - }); - it('unlocks an item (appearance)', async () => { const path = unlockPath.split(',')[0]; const initialShirts = Object.keys(user.purchased.shirt).length; const [, message] = await unlock(user, { query: { path } }); expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.shirt).length).to.equal(initialShirts + 1); + expect(Object.keys(user.purchased.shirt).length).to.equal( + initialShirts + 1, + ); expect(get(user.purchased, path)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 0.5); }); @@ -279,7 +293,9 @@ describe('shared.ops.unlock', () => { const [, message] = await unlock(user, { query: { path } }); expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.hair.color).length).to.equal(initialColorHair + 1); + expect(Object.keys(user.purchased.hair.color).length).to.equal( + initialColorHair + 1, + ); expect(get(user.purchased, path)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 0.5); }); @@ -295,8 +311,12 @@ describe('shared.ops.unlock', () => { expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.hair.mustache).length).to.equal(initialMustache + 1); - expect(Object.keys(user.purchased.hair.beard).length).to.equal(initialBeard); + expect(Object.keys(user.purchased.hair.mustache).length).to.equal( + initialMustache + 1, + ); + expect(Object.keys(user.purchased.hair.beard).length).to.equal( + initialBeard, + ); expect(get(user.purchased, path)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 0.5); @@ -315,10 +335,14 @@ describe('shared.ops.unlock', () => { it('unlocks an item (background)', async () => { const initialBackgrounds = Object.keys(user.purchased.background).length; - const [, message] = await unlock(user, { query: { path: backgroundUnlockPath } }); + const [, message] = await unlock(user, { + query: { path: backgroundUnlockPath }, + }); expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.background).length).to.equal(initialBackgrounds + 1); + expect(Object.keys(user.purchased.background).length).to.equal( + initialBackgrounds + 1, + ); expect(get(user.purchased, backgroundUnlockPath)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 1.75); }); diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index c652857fde..a3e52140ea 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -292,28 +292,35 @@
+ :key="lastTimeJump" + > -1 Day + @click="jumpTime(-1)" + >-1 Day -7 Days + @click="jumpTime(-7)" + >-7 Days -30 Days + @click="jumpTime(-30)" + >-30 Days
Time Traveling! It is {{ new Date().toLocaleDateString() }}
+1 Day + @click="jumpTime(1)" + >+1 Day +7 Days + @click="jumpTime(7)" + >+7 Days +30 Days + @click="jumpTime(30)" + >+30 Days
`, diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js index 5c23f32f6d..2a37ca7692 100644 --- a/website/server/controllers/api-v3/debug.js +++ b/website/server/controllers/api-v3/debug.js @@ -1,8 +1,9 @@ import _ from 'lodash'; -import nconf from 'nconf'; +import sinon from 'sinon'; import moment from 'moment'; import { authWithHeaders } from '../../middlewares/auth'; import ensureDevelpmentMode from '../../middlewares/ensureDevelpmentMode'; +import ensureTimeTravelMode from '../../middlewares/ensureTimeTravelMode'; import { BadRequest } from '../../libs/errors'; import common from '../../../common'; @@ -204,56 +205,68 @@ api.questProgress = { }; let clock; -if (nconf.get('ENABLE_TIME_TRAVEL')) { - (async () => { - const sinon = await import('sinon'); - const time = new Date(); - clock = sinon.useFakeTimers({ - now: time, - shouldAdvanceTime: true, - }); - })(); - api.timeTravelTime = { - method: 'GET', - url: '/debug/time-travel-time', - middlewares: [authWithHeaders()], - async handler (req, res) { - const { user } = res.locals; - - if (!user.permissions.fullAccess) { - throw new BadRequest('You do not have permission to time travel.'); - } - - res.respond(200, { - time: new Date(), - }); - }, - } - - api.timeTravelAdjust = { - method: 'POST', - url: '/debug/jump-time', - middlewares: [authWithHeaders()], - async handler (req, res) { - const { user } = res.locals; - - if (!user.permissions.fullAccess) { - throw new BadRequest('You do not have permission to time travel.'); - } - - const { offsetDays } = req.body; - if (offsetDays > 0) { - clock.jump(offsetDays * 24 * 60 * 60 * 1000) - } else { - clock.setSystemTime(moment().add(offsetDays, 'days').toDate()); - } - - res.respond(200, { - time: new Date(), - }); - }, - } +function fakeClock () { + if (clock) clock.restore(); + const time = new Date(); + clock = sinon.useFakeTimers({ + now: time, + shouldAdvanceTime: true, + }); } +api.timeTravelTime = { + method: 'GET', + url: '/debug/time-travel-time', + middlewares: [ensureTimeTravelMode, authWithHeaders()], + async handler (req, res) { + if (clock === undefined) { + fakeClock(); + } + + const { user } = res.locals; + + if (!user.permissions.fullAccess) { + throw new BadRequest('You do not have permission to time travel.'); + } + + res.respond(200, { + time: new Date(), + }); + }, +}; + +api.timeTravelAdjust = { + method: 'POST', + url: '/debug/jump-time', + middlewares: [ensureTimeTravelMode, authWithHeaders()], + async handler (req, res) { + const { user } = res.locals; + + if (!user.permissions.fullAccess) { + throw new BadRequest('You do not have permission to time travel.'); + } + + const { offsetDays, reset, disable } = req.body; + if (reset) { + fakeClock(); + } else if (disable) { + clock.restore(); + clock = undefined; + } else if (clock !== undefined) { + try { + clock.setSystemTime(moment().add(offsetDays, 'days').toDate()); + } catch (e) { + throw new BadRequest('Error adjusting time'); + } + } else { + throw new BadRequest('Invalid command'); + } + + res.respond(200, { + time: new Date(), + }); + }, +}; + export default api; diff --git a/website/server/libs/worldState.js b/website/server/libs/worldState.js index 36b4ff8d0f..d675370acc 100644 --- a/website/server/libs/worldState.js +++ b/website/server/libs/worldState.js @@ -4,12 +4,10 @@ import { // eslint-disable-line import/no-cycle model as Group, TAVERN_ID as tavernId, } from '../models/group'; -import { REPEATING_EVENTS } from '../../common/script/content/constants'; -import { getCurrentGalaEvent } from '../../common/script/content/constants/schedule'; +import common from '../../common'; export async function getWorldBoss () { - const tavern = await Group - .findById(tavernId) + const tavern = await Group.findById(tavernId) .select('quest.progress quest.key quest.active quest.extra') .exec(); if (tavern && tavern.quest && tavern.quest.active) { @@ -20,42 +18,46 @@ export async function getWorldBoss () { export function getCurrentEvent () { const now = moment(); - const currEvtKey = Object.keys(REPEATING_EVENTS).find(evtKey => { - const event = REPEATING_EVENTS[evtKey]; - const startDate = event.start.replace('1970', now.year()); - const endDate = event.end.replace('1970', now.year()); + const currEvtKey = Object.keys(common.content.repeatingEvents).find( + evtKey => { + const event = common.content.repeatingEventsS[evtKey]; + const startDate = event.start.replace('1970', now.year()); + const endDate = event.end.replace('1970', now.year()); - return now.isBetween(startDate, endDate); - }); + return now.isBetween(startDate, endDate); + }, + ); if (!currEvtKey) { - return getCurrentGalaEvent() + return common.schedule.getCurrentGalaEvent(); } return { event: currEvtKey, - ...REPEATING_EVENTS[currEvtKey], + ...common.content.repeatingEvents[currEvtKey], }; } export function getCurrentEventList () { const now = moment(); - const currentEventKeys = filter(Object.keys(REPEATING_EVENTS), eventKey => { - const eventData = REPEATING_EVENTS[eventKey]; - const startDate = eventData.start.replace('1970', now.year()); - const endDate = eventData.end.replace('1970', now.year()); + const currentEventKeys = filter( + Object.keys(common.content.repeatingEvents), + eventKey => { + const eventData = common.content.repeatingEvents[eventKey]; + const startDate = eventData.start.replace('1970', now.year()); + const endDate = eventData.end.replace('1970', now.year()); - return now.isBetween(startDate, endDate); - }); + return now.isBetween(startDate, endDate); + }, + ); const currentEventList = []; currentEventKeys.forEach(key => { currentEventList.push({ event: key, - ...REPEATING_EVENTS[key], + ...common.content.repeatingEvents[key], }); }); - - currentEventList.push(getCurrentGalaEvent()); + currentEventList.push(common.schedule.getCurrentGalaEvent()); return currentEventList; } diff --git a/website/server/middlewares/ensureTimeTravelMode.js b/website/server/middlewares/ensureTimeTravelMode.js new file mode 100644 index 0000000000..24427c5112 --- /dev/null +++ b/website/server/middlewares/ensureTimeTravelMode.js @@ -0,0 +1,12 @@ +import nconf from 'nconf'; +import { + NotFound, +} from '../libs/errors'; + +export default function ensureTimeTravelMode (req, res, next) { + if (nconf.get('ENABLE_TIME_TRAVEL')) { + next(); + } else { + next(new NotFound()); + } +}