diff --git a/lib/cronitor.js b/lib/cronitor.js index 86dd7db..0e88933 100644 --- a/lib/cronitor.js +++ b/lib/cronitor.js @@ -72,45 +72,42 @@ function Cronitor(apiKey, config = {}) { }; - this.applyConfig = async function(rollback = false) { - if (!this.config) throw new Errors.ConfigError('Must call cronitor.readConfig({path: \'path/to/config\'}) before calling applyConfig().'); - - // try: - // monitors = Monitor.put(monitors=config, rollback=rollback, format=YAML) - // job_count = len(monitors.get('jobs', [])) - // check_count = len(monitors.get('checks', [])) - // heartbeat_count = len(monitors.get('heartbeats', [])) - // total_count = sum([job_count, check_count, heartbeat_count]) - // logger.info('{} monitor{} {}'.format(total_count, 's' if total_count != 1 else '', 'validated.' if rollback else 'synced.',)) - // return True - // except (yaml.YAMLError, ConfigValidationError, APIValidationError, APIError, AuthenticationError) as e: - // logger.error(e) - // return False + this.applyConfig = async function({ path = this.path, rollback = false } = {}) { + if (!path) throw new Errors.ConfigError('Must include a path to config file e.g. cronitor.applyConfig({path: \'./cronitor.yaml\'})'); + + try { + config = await this.readConfig({ path, output: true}); + } catch (err) { + console.error('Error reading config:', err); + return false + } + try { - console.log('CONFIG', this.config) - await Monitor.put(this.config, {rollback, format: Monitor.requestType.YAML}); + await Monitor.put(config, {rollback, format: Monitor.requestType.YAML}); + console.log(`Cronitor config ${rollback ? 'validated' : 'applied'} successfully.`) return true } catch (err) { - console.log(`Error applying config: ${err}`); + console.error(`Error applying config: ${err}`); return false } }; - this.validateConfig = async () => { - return this.applyConfig(true); + this.validateConfig = async ({ path = this.path} = {}) => { + return this.applyConfig({ path, rollback: true }); }; this.readConfig = async function({path = null, output = false}={}) { - path = path || this.path; - if (!path) throw new Errors.ConfigError('Must include a path to config file e.g. cronitor.readConfig({path: \'./cronitor.yaml\'})'); if (!this.path) this.path = path; try { - this.config = await fs.readFile(path, 'utf8'); + let configFile = await fs.readFile(path, 'utf8') + this.config = yaml.load(configFile); if (output) return this.config; + return true } catch (err) { - throw new Errors.ConfigError("Error reading Cronitor config file: ", err); + console.error('Error reading Cronitor config file:', err); + return false } }; @@ -150,6 +147,7 @@ function Cronitor(apiKey, config = {}) { job.start(); }; } else { + console.log(lib.CronJob) throw new Errors.ConfigError(`Unsupported library ${lib.name}`); } diff --git a/lib/monitor.js b/lib/monitor.js index 5cc9eb3..4e2d88a 100644 --- a/lib/monitor.js +++ b/lib/monitor.js @@ -19,37 +19,43 @@ class Monitor { }; } - static async put(data, {rollback = false, format = Monitor.requestType.JSON} = {}) { - // if (typeof data == 'object') { - // if (!Array.isArray(data)) { - // data = [data]; - // } - // } else { - // throw new Errors.MonitorNotCreated('Invalid monitor data.'); - // } - - console.log("PUT ARGS", data, rollback, type) + static async put(data, {rollback = false, format = Monitor.requestType.JSON} = {}) { - let payload if (format === Monitor.requestType.YAML) { - payload = yaml.dump({...data, rollback}); - console.log("THE PAYLOAD", payload) - this._api.axios.defaults.headers['Content-Type'] = 'application/yaml'; - } else { - payload = { monitors: data, rollback } + return this.putYaml(yaml.dump({...data, rollback})) + + } + + // if a user passed a single monitor object, wrap it in an array + if (!Array.isArray(data)) { + data = [data]; } - + try { - const resp = await this._api.axios.put(this._api.monitorUrl(), payload); + const resp = await this._api.axios.put(this._api.monitorUrl(), {monitors: data, rollback}); const monitors = resp.data.monitors.map((_m) => { const m = new Monitor(_m.key); m.data = _m; return m; }); - return monitors.length > 1 ? monitors : monitors[0]; + return monitors.length > 1 ? monitors : monitors[0]; + } catch (err) { + throw new Errors.MonitorNotCreated(err.message); + } + } + + static async putYaml(payload) { + try { + const resp = await this._api.axios.put( + this._api.monitorUrl(), + payload, + { headers: {'Content-Type': 'application/yaml'} } + ); + return yaml.load(resp.data); } catch (err) { throw new Errors.MonitorNotCreated(err.message); } + } constructor(key) { diff --git a/test/test.js b/test/test.js index 43e28d6..d376d0d 100644 --- a/test/test.js +++ b/test/test.js @@ -36,26 +36,44 @@ describe('Config Parser', () => { context('validateConfig', () => { afterEach(() => { sinon.restore(); + cronitor.path = null; }); it('should call Monitor.put with a YAML payload and rollback: true', async () => { const stub = sinon.stub(cronitor.Monitor, 'put'); - await cronitor.readConfig({path: './test/cronitor.yaml'}); - await cronitor.validateConfig(); - expect(stub).to.be.calledWith(sinon.match.string, { rollback: true, format: Monitor.requestType.YAML}); + await cronitor.validateConfig({path: './test/cronitor.yaml'}); + expect(stub).to.be.calledWith(sinon.match.object, { rollback: true, format: Monitor.requestType.YAML}); }); + + it('should raise an exception if no path is provided', async () => { + try { + await cronitor.validateConfig(); + expect.fail('Should have raised an error'); + } catch (err) { + expect(err.message).to.eq('Must include a path to config file e.g. cronitor.applyConfig({path: \'./cronitor.yaml\'})'); + } + }) }); context('applyConfig', () => { afterEach(async () => { sinon.restore(); + cronitor.path = null; }); it('should call Monitor.put with array of monitors and rollback: false', async () => { const stub = sinon.stub(cronitor.Monitor, 'put'); - await cronitor.readConfig({path: './test/cronitor.yaml'}); - await cronitor.applyConfig(); - expect(stub).to.be.calledWith(sinon.match.string, { rollback: false, format: Monitor.requestType.YAML}); + await cronitor.applyConfig({path: './test/cronitor.yaml'}); + expect(stub).to.be.calledWith(sinon.match.object, { rollback: false, format: Monitor.requestType.YAML}); }); + + it('should raise an exception if no path is provided', async () => { + try { + await cronitor.validateConfig(); + expect.fail('Should have raised an error'); + } catch (err) { + expect(err.message).to.eq('Must include a path to config file e.g. cronitor.applyConfig({path: \'./cronitor.yaml\'})'); + } + }) }); @@ -85,6 +103,16 @@ describe('Config Parser', () => { console.error('Failed to read the file:', err); } }); + + it('should allow a group to be specified', async () => { + const stub = sinon.stub(cronitor._api.axios, 'get'); + const dummyData = await fs.readFile('./test/cronitor.yaml', 'utf8'); + stub.resolves({data: dummyData}) + const resp = await cronitor.generateConfig({path: './cronitor-test.yaml', group: 'test-group'}); + + expect(stub).to.be.calledWith('https://cronitor.io/api/monitors.yaml?group=test-group'); + expect(resp).to.be.true; + }); }); }); @@ -275,28 +303,70 @@ describe('Event', () => { }); }); -// describe('test wrap cron', () => { -// it('should load the node-cron library and define the wrapper function', () => { -// cronitor.wraps(require('node-cron')); +describe.skip('test wrap cron', () => { + it('should load the node-cron library and define the wrapper function', () => { + cronitor.wraps(require('node-cron')); -// cronitor.schedule('everyMinuteJob', '* * * * *', () => { -// return new Promise(function(resolve) { -// setTimeout(() => { -// console.log('running node-cron every min'); -// resolve('i ran for 10 seconds'); -// }, 3000); -// }); -// }); + cronitor.schedule('everyMinuteJob', '* * * * *', () => { + console.log('running node-cron every min'); + }); -// }); + }); -// it('should load the NodeCron library and define the wrapper function', () => { -// cronitor.wraps(require('cron')); + it('should load the NodeCron library and define the wrapper function', () => { + cronitor.wraps(require('cron')); -// cronitor.schedule('everyMinuteJob', '* * * * *', () => { -// console.log('running cron every min'); -// return 'i ran for 10 seconds'; -// }); + cronitor.schedule('everyMinuteJob', '* * * * *', () => { + console.log('running cron every min'); + return 'i ran for 10 seconds'; + }); -// }); -// }); \ No newline at end of file + }); +}); + +describe.skip('functional test YAML API', () => { + const cronitor = require('../lib/cronitor')('ADD_YOUR_API_KEY') + + it('should read a config file and validate it', async () => { + const validated = await cronitor.validateConfig({path: './test/cronitor.yaml'}); + expect(validated).to.be.true; + }); + + it('should read a config file and apply it', async () => { + const applied = await cronitor.applyConfig({path: './test/cronitor.yaml'}); + expect(applied).to.be.true; + + // clean up if this runs against prod + const config = await cronitor.readConfig({path: './test/cronitor.yaml', output: true}); + keys = Object.keys(config).map((k) => Object.keys(config[k])).flat(); + keys.map(async (k) => { + const monitor = new cronitor.Monitor(k); + await monitor.delete() + }); + }); + + it('should create a monitor from an object', async () => { + const monitor = await Monitor.put({ + key: 'test-monitor', + name: 'Test Monitor', + type: 'job', + }) + expect(monitor).to.be.instanceOf(Monitor); + await monitor.delete(); + }); + + it('should create monitors from a list', async () => { + const monitors = await Monitor.put([{ + key: 'test-monitor', + name: 'Test Monitor', + type: 'job', + },{ + key: 'test-monitor-1', + name: 'Test Monitor1', + type: 'job', + }]) + expect(monitors).to.be.instanceOf(Array); + expect(monitors.length).to.eq(2); + monitors.forEach(async (m) => await m.delete()); + }); +}) \ No newline at end of file