UNPKG

openhim-core

Version:

The OpenHIM core application that provides logging and routing of http requests

510 lines (454 loc) 19.6 kB
should = require "should" sinon = require "sinon" http = require "http" moment = require "moment" alerts = require "../../lib/alerts" testUtils = require "../testUtils" config = require "../../lib/config/config" config.alerts = config.get('alerts') Channel = require("../../lib/model/channels").Channel User = require("../../lib/model/users").User ContactGroup = require("../../lib/model/contactGroups").ContactGroup Event = require("../../lib/model/events").Event Alert = require("../../lib/model/alerts").Alert testUser1 = new User firstname: 'User' surname: 'One' email: 'one@openhim.org' passwordAlgorithm: 'sha512' passwordHash: '3cc90918-7044-4e55-b61d-92ae73cb261e' passwordSalt: '22a61686-66f6-483c-a524-185aac251fb0' testUser2 = new User firstname: 'User' surname: 'Two' email: 'two@openhim.org' msisdn: '27721234567' passwordAlgorithm: 'sha512' passwordHash: '3cc90918-7044-4e55-b61d-92ae73cb261e' passwordSalt: '22a61686-66f6-483c-a524-185aac251fb0' testGroup1 = new ContactGroup _id: "aaa908908bbb98cc1d0809ee" group: 'group1' users: [ { user: 'one@openhim.org' method: 'email' } { user: 'two@openhim.org' method: 'email' maxAlerts: '1 per day' } ] testGroup2 = new ContactGroup _id: "bbb908908ccc98cc1d0888aa" group: 'group2' users: [ { user: 'one@openhim.org', method: 'email' } ] testFailureRate = 50 testChannel = new Channel name: 'test' urlPattern: '/test' allow: '*' alerts: [ { condition: 'status' status: "404" groups: ['aaa908908bbb98cc1d0809ee'] } { condition: 'status' status: '5xx' groups: ['bbb908908ccc98cc1d0888aa'] users: [ { user: 'two@openhim.org', method: 'sms' } ] failureRate: testFailureRate } ] disabledChannel = new Channel name: 'disabled' urlPattern: '/disabled' allow: '*' alerts: [ { condition: 'status' status: "404" groups: ['aaa908908bbb98cc1d0809ee'] } ] status: 'disabled' autoRetryChannel = new Channel name: 'autoretry' urlPattern: '/autoretry' allow: '*' autoRetryEnabled: true autoRetryPeriodMinutes: 1 autoRetryMaxAttempts: 3 alerts: [ { condition: 'auto-retry-max-attempted' groups: ['aaa908908bbb98cc1d0809ee'] } ] testTransactions = [ # 0 new Event transactionID: 'aaa908908bbb98cc1daaaaa0' route: 'primary' event: 'end' status: 404 # 1 new Event transactionID: 'aaa908908bbb98cc1daaaaa1' route: 'route' event: 'end' status: 404 # 2 new Event transactionID: 'aaa908908bbb98cc1daaaaa2' route: 'primary' event: 'end' status: 400 # 3 new Event transactionID: 'aaa908908bbb98cc1daaaaa3' route: 'primary' event: 'end' status: 500 # 4 new Event transactionID: 'aaa908908bbb98cc1daaaaa4' route: 'primary' event: 'end' status: 500 # 5 new Event transactionID: 'aaa908908bbb98cc1daaaaa5' route: 'primary' event: 'end' status: 500 # 6 new Event transactionID: 'aaa908908bbb98cc1daaaaa6' route: 'primary' event: 'end' status: 404 # 7 new Event transactionID: 'aaa908908bbb98cc1daaaaa7' route: 'primary' event: 'end' status: 404 # 8 new Event transactionID: 'aaa908908bbb98cc1daaaaa8' route: 'primary' event: 'end' status: 500 autoRetryAttempt: 2 # 9 new Event transactionID: 'aaa908908bbb98cc1daaaaa9' route: 'primary' event: 'end' status: 500 autoRetryAttempt: 3 # 10 - channel event for 9 new Event transactionID: 'aaa908908bbb98cc1daaaaa9' route: 'channel' event: 'end' status: 500 autoRetryAttempt: 3 # 11 new Event transactionID: 'aaa908908bbb98cc1daaaaa9' route: 'primary' event: 'end' status: 200 autoRetryAttempt: 3 ] dateFrom = new Date() dateFrom.setHours 0, 0, 0, 0 describe "Transaction Alerts", -> before (done) -> Event.ensureIndexes -> Alert.ensureIndexes -> testUser1.save -> testUser2.save -> testGroup1.save -> testGroup2.save -> testChannel.save -> disabledChannel.save -> autoRetryChannel.save -> for testTransaction in testTransactions testTransaction.channelID = testChannel._id testTransactions[6].channelID = "000000000000000000000000" # a channel id that doesn't exist testTransactions[7].channelID = disabledChannel._id testTransactions[8].channelID = autoRetryChannel._id testTransactions[9].channelID = autoRetryChannel._id testTransactions[10].channelID = autoRetryChannel._id testTransactions[11].channelID = autoRetryChannel._id done() after (done) -> User.remove {}, -> ContactGroup.remove {}, -> Channel.remove {}, -> done() afterEach (done) -> Alert.remove {}, -> Event.remove {}, -> for testTransaction in testTransactions testTransaction.isNew = true delete testTransaction._id done() describe "config", -> it "default config should contain alerting config fields", (done) -> config.alerts.should.exist config.alerts.enableAlerts.should.exist config.alerts.pollPeriodMinutes.should.exist config.alerts.himInstance.should.exist config.alerts.consoleURL.should.exist done() describe ".findTransactionsMatchingStatus", -> it "should return transactions that match an exact status", (done) -> testTransactions[0].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "404" }, dateFrom, (err, results) -> results.length.should.be.exactly 1 results[0]._id.equals(testTransactions[0]._id).should.be.true() done() it "should return transactions that have a matching status in a route response", (done) -> testTransactions[1].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "404" }, dateFrom, (err, results) -> results.length.should.be.exactly 1 results[0]._id.equals(testTransactions[1]._id).should.be.true() done() it "should only return transactions for the requested channel", (done) -> # should return transaction 0 but not 6 testTransactions[0].save (err) -> return done err if err testTransactions[6].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "404" }, dateFrom, (err, results) -> results.length.should.be.exactly 1 results[0]._id.equals(testTransactions[0]._id).should.be.true() done() it "should not return transactions that occur before dateFrom", (done) -> testTransactions[0].save (err) -> return done err if err newFrom = moment().add(1, 'days').toDate() alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "404" }, newFrom, (err, results) -> results.length.should.be.exactly 0 done() it "should return all matching transactions for a fuzzy status search for the specified channel", (done) -> # should return transactions 0, 1 and 2 but not 3 or 6 testTransactions[0].save (err) -> return done err if err testTransactions[1].save (err) -> return done err if err testTransactions[2].save (err) -> return done err if err testTransactions[3].save (err) -> return done err if err testTransactions[6].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "4xx" }, dateFrom, (err, results) -> results.length.should.be.exactly 3 resultIDs = results.map (result) -> result._id resultIDs.should.containEql testTransactions[0]._id resultIDs.should.containEql testTransactions[1]._id resultIDs.should.containEql testTransactions[2]._id resultIDs.should.not.containEql testTransactions[6]._id done() it "should not return any transactions when their count is below the failure rate", (done) -> testTransactions[0].save (err) -> return done err if err testTransactions[1].save (err) -> return done err if err testTransactions[3].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "500", failureRate: testFailureRate }, dateFrom, (err, results) -> # only one 500 transaction, but failureRate is 50% results.length.should.be.exactly 0 done() it "should return transactions when their count is equal to the failure rate", (done) -> testTransactions[0].save (err) -> return done err if err testTransactions[1].save (err) -> return done err if err testTransactions[3].save (err) -> return done err if err testTransactions[4].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "500", failureRate: testFailureRate }, dateFrom, (err, results) -> results.length.should.be.exactly 2 resultIDs = results.map (result) -> result._id resultIDs.should.containEql testTransactions[3]._id resultIDs.should.containEql testTransactions[4]._id done() it "should return transactions when their count is above the failure rate", (done) -> testTransactions[0].save (err) -> return done err if err testTransactions[1].save (err) -> return done err if err testTransactions[3].save (err) -> return done err if err testTransactions[4].save (err) -> return done err if err testTransactions[5].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "500", failureRate: testFailureRate }, dateFrom, (err, results) -> results.length.should.be.exactly 3 resultIDs = results.map (result) -> result._id resultIDs.should.containEql testTransactions[3]._id resultIDs.should.containEql testTransactions[4]._id resultIDs.should.containEql testTransactions[5]._id done() it "should not return any transactions when the count is equal/above the failure rate, but an alert has already been sent", (done) -> alert = new Alert user: 'one@openhim.org' method: 'email' channelID: testChannel._id condition: 'status' status: '500' alertStatus: 'Completed' alert.save (err) -> testTransactions[0].save (err) -> return done err if err testTransactions[1].save (err) -> return done err if err testTransactions[3].save (err) -> return done err if err testTransactions[4].save (err) -> return done err if err alerts.findTransactionsMatchingStatus testChannel, { condition: 'status', status: "500", failureRate: testFailureRate }, dateFrom, (err, results) -> results.length.should.be.exactly 0 done() describe ".findTransactionsMaxRetried", -> it "should not return transactions have not reached max retries", (done) -> testTransactions[8].save (err) -> return done err if err alerts.findTransactionsMaxRetried autoRetryChannel, autoRetryChannel.alerts[0], dateFrom, (err, results) -> results.length.should.be.exactly 0 done() it "should return transactions have reached max retries", (done) -> testTransactions[9].save (err) -> return done err if err alerts.findTransactionsMaxRetried autoRetryChannel, autoRetryChannel.alerts[0], dateFrom, (err, results) -> results.length.should.be.exactly 1 results[0]._id.equals(testTransactions[9]._id).should.be.true() done() it "should not return successful transactions that have reached max retries", (done) -> testTransactions[11].save (err) -> return done err if err alerts.findTransactionsMaxRetried autoRetryChannel, autoRetryChannel.alerts[0], dateFrom, (err, results) -> results.length.should.be.exactly 0 done() it "should not return duplicate transaction IDs where multiple events exist for the same transaction", (done) -> testTransactions[9].save (err) -> return done err if err testTransactions[10].save (err) -> return done err if err alerts.findTransactionsMaxRetried autoRetryChannel, autoRetryChannel.alerts[0], dateFrom, (err, results) -> results.length.should.be.exactly 1 results[0].transactionID.equals(testTransactions[9].transactionID).should.be.true() done() describe ".alertingTask", -> buildJobStub = (date) -> jobStub = {} jobStub.attrs = {} if date jobStub.attrs.data = {} jobStub.attrs.data.lastAlertDate = date return jobStub mockContactHandler = (spy, err=null) -> (method, contactAddress, title, messagePlain, messageHTML, callback) -> spy method, contactAddress, title, messagePlain, messageHTML callback err it "should not contact users if there no matching transactions", (done) -> contactSpy = sinon.spy() alerts.alertingTask buildJobStub(null), mockContactHandler(contactSpy), -> contactSpy.called.should.be.false done() it "should set the last run date as a job attribute", (done) -> jobStub = buildJobStub null contactSpy = sinon.spy() alerts.alertingTask jobStub, mockContactHandler(contactSpy), -> jobStub.attrs.data.should.exist jobStub.attrs.data.lastAlertDate.should.exist jobStub.attrs.data.lastAlertDate.should.be.instanceof(Date) done() it "should contact users when there are matching transactions", (done) -> contactSpy = sinon.spy() testTransactions[0].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), -> contactSpy.calledTwice.should.be.true() contactSpy.withArgs('email', 'one@openhim.org', 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() contactSpy.withArgs('email', 'two@openhim.org', 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() done() it "should store an alert log item in mongo for each alert generated", (done) -> contactSpy = sinon.spy() testTransactions[0].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), -> contactSpy.called.should.be.true() Alert.find {}, (err, results) -> return done err if err results.length.should.be.exactly 2 resultUsers = results.map (result) -> result.user resultUsers.should.containEql testUser1.email resultUsers.should.containEql testUser2.email done() it "should contact users using their specified method", (done) -> contactSpy = sinon.spy() testTransactions[3].save (err) -> return done err if err testTransactions[4].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), -> contactSpy.calledTwice.should.be.true() contactSpy.withArgs('email', testUser1.email, 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() contactSpy.withArgs('sms', testUser2.msisdn, 'OpenHIM Alert', sinon.match.string, null).calledOnce.should.be.true() done() it "should not send alerts to users with a maxAlerts restriction if they've already received an alert for the same day", (done) -> contactSpy = sinon.spy() testTransactions[0].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), -> contactSpy.calledTwice.should.be.true() secondSpy = sinon.spy() alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(secondSpy), -> secondSpy.calledOnce.should.be.true() secondSpy.withArgs('email', testUser1.email, 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() done() it "should send alerts to users if an alert for the same day was already attempted but it failed", (done) -> contactSpy = sinon.spy() testTransactions[0].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy, "Test Failure"), -> contactSpy.calledTwice.should.be.true() secondSpy = sinon.spy() alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(secondSpy), -> secondSpy.calledTwice.should.be.true() secondSpy.withArgs('email', 'one@openhim.org', 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() secondSpy.withArgs('email', 'two@openhim.org', 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() done() it "should not generate alerts for disabled channels", (done) -> contactSpy = sinon.spy() testTransactions[0].save (err) -> return done err if err testTransactions[7].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), -> contactSpy.called.should.be.true() Alert.find {}, (err, results) -> return done err if err results.length.should.be.exactly 2 resultUsers = results.map (result) -> result.user resultUsers.should.containEql testUser1.email resultUsers.should.containEql testUser2.email resultChannels = results.map (result) -> result.channelID resultChannels.should.containEql testChannel._id.toHexString() resultChannels.should.not.containEql disabledChannel._id.toHexString() done() it "should contact users when there are matching max auto retried transactions", (done) -> contactSpy = sinon.spy() testTransactions[9].save (err) -> return done err if err alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), -> contactSpy.calledTwice.should.be.true() contactSpy.withArgs('email', 'one@openhim.org', 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() contactSpy.withArgs('email', 'two@openhim.org', 'OpenHIM Alert', sinon.match.string, sinon.match.string).calledOnce.should.be.true() done()