routes/test.js

/**
 * @module routes/admin
 * @author jesse reichler <mouser@donationcoder.com>
 * @copyright 6/4/19
 * @description
 * Provides admin test routes
 */

"use strict";


// modules
const express = require("express");
const util = require("util");


// requirement service locator
const jrequire = require("../helpers/jrequire");

// helpers
const JrContext = require("../helpers/jrcontext");
const JrResult = require("../helpers/jrresult");
const jrdebug = require("../helpers/jrdebug");
const jrhMisc = require("../helpers/jrh_misc");
const jrhRateLimiter = require("../helpers/jrh_ratelimiter");

// controllers
const adminAid = jrequire("adminaid");
const arserver = jrequire("arserver");

// constants
const appdef = jrequire("appdef");









//---------------------------------------------------------------------------
function setupRouter(urlPath) {
	// create express router
	const router = express.Router();

	// setup routes
	router.get("/", routerGetIndex);
	router.get("/makeappsrooms", routerGetMakeappsrooms);
	router.post("/makeappsrooms", routerPostMakeappsrooms);

	router.get("/emergencyalert", routerGetTestEmergencyAlerts);
	router.post("/emergencyalert", routerPostTestEmergencyAlerts);

	router.get("/trigger_crash", routerGetTriggerCrash);
	router.post("/trigger_crash", routerPostTriggerCrash);

	router.get("/shutdown", routerGetShutdown);
	router.post("/shutdown", routerPostShutdown);

	router.get("/ratelimit", routerGetRateLimit);
	router.post("/ratelimit", routerPostRateLimit);

	router.get("/recentloginforce", routerGetRecentLoginForce);

	// return router
	return router;
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------
// router functions


async function routerGetIndex(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	if (!await arserver.aclRequireLoggedInSitePermissionRenderErrorPageOrRedirect(jrContext, appdef.DefAclActionAdminister)) {
		// all done
		return;
	}

	res.render("test/index", {
		jrResult: jrContext.mergeSessionMessages(),
	});
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
async function routerGetMakeappsrooms(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	await arserver.confirmUrlPost(jrContext, appdef.DefAclActionAdminister, "Generate some test Apps and Rooms", "This operation will bulk create a bunch of apps and rooms.  Note it will fail if run twice, due to clashing shortcodes.");
}


async function routerPostMakeappsrooms(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);

	if (!await arserver.aclRequireLoggedInSitePermissionRenderErrorPageOrRedirect(jrContext, appdef.DefAclActionAdminister)) {
		// all done
		return;
	}
	// check required csrf token
	if (!arserver.testCsrfRedirectToOriginalUrl(jrContext)) {
		return;
	}

	// get logged in user (note we've already checked they are logged in with permission)
	const user = await arserver.lookupLoggedInUser(jrContext);

	// do it using adminaid
	const addCountApps = 5;
	const addCountRooms = 3;
	const addCountRoomDatas = 3;
	await adminAid.addTestAppsAndRooms(jrContext, user, addCountApps, addCountRooms, addCountRoomDatas);
	jrContext.addToThisSession();
	//
	if (!jrContext.isError()) {
		// return them to admin testing page
		res.redirect("/test");
	} else {
		res.redirect("/test/makeappsrooms");
	}
}
//---------------------------------------------------------------------------





//---------------------------------------------------------------------------
async function routerGetTestEmergencyAlerts(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	await arserver.confirmUrlPost(jrContext, appdef.DefAclActionAdminister, "Test emergency alert functionality", "This function will send out some emergency alerts and test that rate limiting works for them.");
}


async function routerPostTestEmergencyAlerts(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	if (!await arserver.aclRequireLoggedInSitePermissionRenderErrorPageOrRedirect(jrContext, appdef.DefAclActionAdminister)) {
		// all done
		return;
	}
	// check required csrf token
	if (!arserver.testCsrfRedirectToOriginalUrl(jrContext)) {
		return;
	}

	// send emergency alerts
	const subject = "Test of emergency alert system";
	const message = "This is a test of the emergency alert system.\nThis will send out an email to a list of email address configured to receive emergency alerts.";
	const extraData = {};
	const flagAlsoSendToSecondaries = true;
	const numToSend = 1;
	let numSent = 0;
	for (let i = 0; i < numToSend; ++i) {
		extraData.info = util.format("Message %d of %d", i + 1, numToSend);
		numSent += await arserver.emergencyAlert(jrContext, "test", subject, message, req, extraData, flagAlsoSendToSecondaries, false);
	}

	// push session
	jrContext.pushSuccess(util.format("Testing sent a total of %d email messages while triggering %d emergency alerts.", numSent, numToSend));
	jrContext.addToThisSession();

	// return them to page
	res.redirect("/test/emergencyalert");
}
//---------------------------------------------------------------------------





//---------------------------------------------------------------------------
async function routerGetTriggerCrash(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	await arserver.confirmUrlPost(jrContext, appdef.DefAclActionAdminister, "Test fatal uncaught nodejs crash/exception", "This function will deliberately throw an uncaught nodejs exception to test how the system deals with it; it will likely exit nodejs, but hopefully log+email an error message and trace.");
}

async function routerPostTriggerCrash(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	// trigger a crash to check handling
	if (!await arserver.aclRequireLoggedInSitePermissionRenderErrorPageOrRedirect(jrContext, appdef.DefAclActionAdminister)) {
		// all done
		return;
	}
	// check required csrf token
	if (!arserver.testCsrfRedirectToOriginalUrl(jrContext)) {
		return;
	}

	throw new Error("PURPOSEFUL_TEST_CRASH_EXCEPTION");
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
async function routerGetShutdown(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	await arserver.confirmUrlPost(jrContext, appdef.DefAclActionAdminister, "Shutdown application server", "This will shut down the application server and do a clean exit.");
}

async function routerPostShutdown(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	// trigger a crash to check handling
	if (!await arserver.aclRequireLoggedInSitePermissionRenderErrorPageOrRedirect(jrContext, appdef.DefAclActionAdminister)) {
		// all done
		return;
	}
	// check required csrf token
	if (!arserver.testCsrfRedirectToOriginalUrl(jrContext)) {
		return;
	}

	// render simple message
	jrContext.res.status(200).send("Initiating shutdown.");

	// tell server to shut down after some short delay to allow it to send response and flush session data, etc.
	setTimeout(async () => {
		await arserver.shutDown();
	}, 250);
}
//---------------------------------------------------------------------------




//---------------------------------------------------------------------------
async function routerGetRateLimit(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	await arserver.confirmUrlPost(jrContext, appdef.DefAclActionAdminister, "Test rate limiter", "This will generate a number of events which should trigger the test rate limiter to kick in.  An operation will loop many times, with some iterations being blocked by the rate limiter.");
}

async function routerPostRateLimit(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);
	// test rate limiter

	// require admin permission, etc.
	if (!await arserver.aclRequireLoggedInSitePermissionRenderErrorPageOrRedirect(jrContext, appdef.DefAclActionAdminister)) {
		// all done
		return;
	}
	// check required csrf token
	if (!arserver.testCsrfRedirectToOriginalUrl(jrContext)) {
		return;
	}

	const rateLimiter = arserver.getRateLimiterTest();
	jrContext.pushSuccess("rateLimiterTest info:" + jrhRateLimiter.getRateLimiterInfo(rateLimiter));

	// ATTN: with rateLimiterKey == "" it means that we share a single rate limter for all emergencyAlerts
	const rateLimiterKey = "";

	let message = "";
	const numToTest = 36;
	const sleepPerTest = 100;
	for (let i = 0; i < numToTest; ++i) {
		message = "Rate limiter test " + i.toString() + " at " + jrhMisc.getPreciseNowString();
		jrContext.pushSuccess(message);
		try {
			// consume a point of action
			await rateLimiter.consume(rateLimiterKey, 1);
			// no excpetion
			jrContext.pushSuccess("Ok, rate limiter did not trigger.");
		} catch (rateLimiterRes) {
			// rate limiter triggered
			if (rateLimiterRes.isFirstInDuration) {
				message = "Rate limiter kicks in.\n";
				jrContext.pushSuccess(message);
			}
			// add message about rate limiter blocking
			const blockTime = rateLimiterRes.msBeforeNext / 1000.0;
			message = "Rate limiter blocking for " + blockTime.toString() + " seconds.";
			jrContext.pushSuccess(message);
		}
		// sleep a bit
		await jrhMisc.usleep(sleepPerTest);
	}

	// push reslt message to session
	jrContext.addToThisSession();

	// return them to page
	res.redirect("/test/ratelimit");
}
//---------------------------------------------------------------------------





//---------------------------------------------------------------------------
async function routerGetRecentLoginForce(req, res, next) {
	const jrContext = JrContext.makeNew(req, res, next);

	if (!await arserver.requireRecentLoggedIn(jrContext, 60000)) {
		// errror and redirect will have happened
		return;
	}

	// ok all good
	jrContext.res.render("generic/infopage", {
		jrResult: jrContext.mergeSessionMessages(),
		headline: "Recent Log In Force Test",
		message: "All good, you logged in recently.",
	});
}
//---------------------------------------------------------------------------




module.exports = {
	setupRouter,
};