From be030bb83af827eef69465f72992165393906ad4 Mon Sep 17 00:00:00 2001 From: Siddharth VP Date: Thu, 22 Oct 2020 20:11:22 +0530 Subject: [PATCH] Enable emergency shutoff support --- src/bot.js | 64 +++++++++++++++++++++++++++++++++++++++++++ tests/shutoff.test.js | 62 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 tests/shutoff.test.js diff --git a/src/bot.js b/src/bot.js index 2e667ad..a24cf3f 100644 --- a/src/bot.js +++ b/src/bot.js @@ -139,6 +139,18 @@ class mwn { // shutoffPage: null, // shutoffRegex: /^\s*$/, + /** + * Bot emergency shutoff options + * @type {{condition: RegExp | Function, page: string, + * intervalDuration: number, onShutoff: Function}} + */ + shutoff: { + intervalDuration: 10000, + page: null, + condition: /^\s*$/, + onShutoff: function () {} + }, + // default parameters included in every API request defaultParams: { format: 'json', @@ -198,6 +210,15 @@ class mwn { responseType: 'json' }, mwn.requestDefaults); + /** + * Emergency shutoff config + * @type {{hook: ReturnType, state: boolean}} + */ + this.shutoff = { + state: false, + hook: null + }; + /** * Title class associated with the bot instance */ @@ -409,6 +430,14 @@ class mwn { * @returns {Promise} */ async request(params, customRequestOptions = {}) { + + if (this.shutoff.state) { + return this.rejectWithError({ + code: 'bot-shutoff', + info: `Bot was shut off (check ${this.options.shutoff.page})` + }); + } + params = merge(this.options.defaultParams, params); const getOrPost = function (data) { @@ -865,6 +894,41 @@ class mwn { }); } + /** + * Enable bot emergency shutoff + * @param {Object} [shutoffOptions] + * @config {string} [page] + * @config {number} [intervalDuration] + * @config {RegExp | Function} [condition] + * @config {Function} [onShutoff] + */ + enableEmergencyShutoff(shutoffOptions) { + Object.assign(this.options.shutoff, shutoffOptions); + + this.shutoff.hook = setInterval(async () => { + let text = await new this.page(this.options.shutoff.page).text(); + let cond = this.options.shutoff.condition; + if ( + (cond instanceof RegExp && !cond.test(text)) || + (cond instanceof Function && !cond(text)) + ) { + this.shutoff.state = true; + this.disableEmergencyShutoff(); + // user callback executed last, so that an error thrown by + // it doesn't prevent the the above from being run + this.options.shutoff.onShutoff(text); + } + }, this.options.shutoff.intervalDuration); + } + + /** + * Disable emergency shutoff detection. + * Use this only if it was ever enabled. + */ + disableEmergencyShutoff() { + clearInterval(this.shutoff.hook); + } + /***************** HELPER FUNCTIONS ******************/ /** diff --git a/tests/shutoff.test.js b/tests/shutoff.test.js new file mode 100644 index 0000000..3018632 --- /dev/null +++ b/tests/shutoff.test.js @@ -0,0 +1,62 @@ +'use strict'; + +const { bot, expect, loginBefore, logoutAfter} = require('./test_base'); + + +describe('bot emergency shutoff', async function() { + this.timeout(10000); + + before('logs in and gets token & namespaceInfo', loginBefore); + after('logs out', logoutAfter); + + it('set shutoff=true on non-matching regex', async function () { + bot.enableEmergencyShutoff({ + page: 'SD0001test', + intervalDuration: 1000, + condition: /^\s*$/ + }); + await bot.save('SD0001test', 'shutoff', 'Testing bot shutoff (mwn)'); + await bot.sleep(1500); // wait till we can be sure that shutoff check is queried + expect(bot.shutoff.state).to.equal(true); + bot.disableEmergencyShutoff(); + }); + + it('set shutoff=true on condition function returns falsy value', async function () { + bot.shutoff.state = false; // restore to default state + + bot.enableEmergencyShutoff({ + page: 'SD0001test', + intervalDuration: 1000, + condition: function () { + return false; // function returns false means bot would shut down + } + }); + await bot.sleep(1500); // wait till we can be sure that shutoff check is queried + expect(bot.shutoff.state).to.equal(true); + bot.disableEmergencyShutoff(); + }); + + it ('sets shutoff=true even if shutoff options are not configured in function', async function () { + bot.shutoff.state = false; // restore to default state + + bot.options.shutoff.page = 'SD0001test'; + bot.options.shutoff.intervalDuration = 1000; + bot.options.shutoff.condition = /^\s*$/; + bot.enableEmergencyShutoff(); + await bot.save('SD0001test', 'shutoff', 'Testing bot shutoff (mwn)'); + await bot.sleep(1500); // wait till we can be sure that shutoff check is queried + expect(bot.shutoff.state).to.equal(true); + bot.disableEmergencyShutoff(); + }); + + it('refuses to do API requests when shut off', function () { + bot.shutoff.state = true; + return bot.request({action: 'query', titles: 'testtitle'}) + .then(() => { + expect(true).to.equal(false); // should never execute + }, (err) => { + expect(err.code).to.equal('bot-shutoff'); + }); + }); + +});