diff --git a/spec/Health.spec.js b/spec/Health.spec.js new file mode 100644 index 00000000000..ec7f0474f08 --- /dev/null +++ b/spec/Health.spec.js @@ -0,0 +1,131 @@ +"use strict"; +/* Tests for Health reporting */ + +import Health from '../src/Health'; + +describe('Parse server health', () => { + it('should have expected default health report', (done) => { + expect(Health.getReport()).toEqual({ + status: 'ok' + }); + + done(); + }); + + it('should return expected complex report', (done) => { + Health.set('callback', function() { + return 'fancy_status'; + }); + + done(); + }); + + it('should return ok for get default status', (done) => { + const status = Health.get('status'); + expect(status).toBe('ok'); + + done(); + }); + + it('should set and get plain data', (done) => { + const string = 'foo-boo!'; + Health.set('critical_status', string); + expect(Health.get('critical_status')).toBe(string); + expect(Health.getRaw('critical_status')).toBe(string); + + const int = 42; + Health.set('critical_status', int); + expect(Health.get('critical_status')).toBe(int); + expect(Health.getRaw('critical_status')).toBe(int); + + done(); + }); + + it('should set and get data from a callback', (done) => { + Health.set('realtime_status', function() { + return 'super_good'; + }); + + // test plain get + expect(Health.get('realtime_status')).toBe('super_good'); + + // test raw get + expect(Health.getRaw('realtime_status') instanceof Function).toBe(true); + + done(); + }); + + it('should empty report with clear all', (done) => { + Health.reset(); + let all = Health.getAll(); + expect(Object.keys(all).length > 0).toBe(true); + + Health.clearAll(); + all = Health.getAll(); + expect(Object.keys(all).length).toBe(0); + + done(); + }); + + it('should go back to default on reset', (done) => { + Health.clearAll(); + const all = Health.getAll(); + expect(Object.keys(all).length).toBe(0); + + Health.reset(); + expect(Health.get('status')).toBe('ok'); + + done(); + }); + + it('should add and override with setAll', (done) => { + Health.setAll({ + status: 'new_status', + running: 1, + db: (() => { + return 'good'; + }) + }); + + expect(Health.get('status')).toBe('new_status'); + expect(Health.get('running')).toBe(1); + expect(Health.get('db')).toBe('good'); + + // reset + Health.clearAll(); + Health.set('status', 'ok'); + + done(); + }); + + it('should remove data on clear', (done) => { + const key = 'unneeded'; + + Health.set(key, 1); + expect(Health.get(key)).toBe(1); + + Health.clear(key); + expect(Health.get(key)).toBe(undefined); + + done(); + }); + + it('should remove callback on clear', (done) => { + const key = 'callback'; + + Health.set(key, function() { + return 'in_place'; + }); + expect(Health.get(key)).toBe('in_place'); + + Health.clear(key); + expect(Health.get(key)).toBe(undefined); + + done(); + }); + + it('should return null on unknown get', (done) => { + expect(Health.get('not_a_known_item')).toBe(undefined); + done(); + }) +}); diff --git a/spec/index.spec.js b/spec/index.spec.js index c0fa872dd69..6f06cfaad29 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -265,6 +265,7 @@ describe('server', () => { url: 'http://localhost:8378/1/health', }, (error, response) => { expect(response.statusCode).toBe(200); + console.dir(response.body); done(); }); }); diff --git a/src/Health.js b/src/Health.js new file mode 100644 index 00000000000..e62a8335784 --- /dev/null +++ b/src/Health.js @@ -0,0 +1,125 @@ +// +// Holds configuration used for reporting server health statistics +// Additional callbacks to get information for reporting can be registered here +// + +const Health = { + + /** + * Sets up our report with default data + */ + setup() { + // reset to get started + this.reset(); + }, + + /** + * Returns the default report data + * + * @returns {{status: string}} + */ + getDefaultReportData() { + return { + status: 'ok' + }; + }, + + /** + * Gets the raw value for a given key + * + * @param {string }key Key to get value of + */ + getRaw(key) { + return this.reportData[key]; + }, + + /** + * Gets a particular value in our health report + * + * @param {string} key Key to get value of + */ + get(key) { + const value = this.getRaw(key); + if(!(value instanceof Function)) { + // return data normally + return value; + } else { + // return function result + return value(); + } + }, + + /** + * Gets all data, replacing functions with their call results + * + * @returns {{}} + */ + getAll() { + const all = {}; + for(const key in this.reportData) { + all[key] = this.get(key); + } + return all; + }, + + /** + * Sets a key/value pair of our health report + * The + * + * @param {string} key Key to set report data on + * @param {string|int|Object|Function} value Report data to set, this can also be a callback + */ + set(key, value) { + this.reportData[key] = value; + }, + + /** + * Sets all the given data on the current report, overriding any pre-existing data + * + * @param {Object} data + */ + setAll(data) { + for(const key in data) { + this.reportData[key] = data[key]; + } + }, + + /** + * Clears a given key for this report + * + * @param key + */ + clear(key) { + delete this.reportData[key]; + }, + + /** + * Clears all report data + */ + clearAll() { + this.reportData = {}; + }, + + /** + * Prepares and returns the current health report + * + * @returns {{status: string}|*} + */ + getReport() { + // return our prepared report + return this.getAll(); + }, + + /** + * Resets the current report data to it's defaults + */ + reset() { + this.reportData = this.getDefaultReportData(); + } + +}; + +// reset to populate report data +Health.setup(); + +module.exports = Health; diff --git a/src/ParseServer.js b/src/ParseServer.js index f24d8ec154a..03d477e5658 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -34,6 +34,7 @@ import { SessionsRouter } from './Routers/SessionsRouter'; import { UsersRouter } from './Routers/UsersRouter'; import { PurgeRouter } from './Routers/PurgeRouter'; import { AudiencesRouter } from './Routers/AudiencesRouter'; +import Health from './Health'; import { ParseServerRESTController } from './ParseServerRESTController'; import * as controllers from './Controllers'; @@ -138,9 +139,7 @@ class ParseServer { })); api.use('/health', (function(req, res) { - res.json({ - status: 'ok' - }); + res.json(Health.getReport()); })); api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressRouter());