aws-cloudwatch-forwarder
Version:
Forwards the Stdin to AWS Cloudwatch
430 lines (370 loc) • 15.5 kB
JavaScript
var rfr = require("rfr");
var async = require("async")
var awsForwarderService = rfr("lib/awsForwarderService")
describe("awsForwarderService", function() {
var initConfig = {
aws: {
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream"
}
};
var service = null;
var cloudwatchLogsStub = null;
beforeEach(() => {
cloudwatchLogsStub = {
describeLogGroups: () => {},
createLogGroup: () => {},
describeLogStreams: () => {},
createLogStream: () => {},
putLogEvents: () => {}
}
service = awsForwarderService(cloudwatchLogsStub);
})
function describeLogGroupsWillReturn(data) {
spyOn(cloudwatchLogsStub, "describeLogGroups").and.callFake((info, callback) => {
callback(null, data);
});
}
function createLogGroupsWillSucceed(){
spyOn(cloudwatchLogsStub, "createLogGroup").and.callFake((info, callback) => {
callback(null);
});
}
function describeLogStreamsWillReturn(data) {
spyOn(cloudwatchLogsStub, "describeLogStreams").and.callFake((info, callback) => {
callback(null, data);
});
}
function createLogStreamWillSucceed(){
spyOn(cloudwatchLogsStub, "createLogStream").and.callFake((info, callback) => {
callback(null);
});
}
function putLogEventsWillSucceed(sequenceTokenGenerator){
if (!sequenceTokenGenerator){
sequenceTokenGenerator = () => null
}
spyOn(cloudwatchLogsStub, "putLogEvents").and.callFake((info, callback) => {
var data = {
nextSequenceToken: sequenceTokenGenerator()
}
callback(null, data)
});
}
describe("init", () => {
it ("requires a log group name", () => {
expect(() => service.init()).toThrow(new Error("a logGroupName is required"))
})
it ("requires a log stream name", () => {
var config = {
aws: {
logGroupName: "aaa"
}
}
expect(() => service.init(config)).toThrow(new Error("a logStreamName is required"))
})
it ("determines if the log group exists", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
createLogStreamWillSucceed()
service.init(initConfig).then(() => {
expect(cloudwatchLogsStub.describeLogGroups).toHaveBeenCalledWith({
logGroupNamePrefix: "the-log-groupname"
}, jasmine.any(Function))
done()
})
})
it ("fails when log group could not be described", done => {
spyOn(cloudwatchLogsStub, "describeLogGroups").and.callFake((info, callback) => {
callback("something went wrong")
});
service.init(initConfig).then(null, (err) => {
expect(err).toBe("something went wrong")
done()
})
})
it ("creates a log group if it doesn't already exists", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
createLogStreamWillSucceed()
service.init(initConfig).then(() => {
expect(cloudwatchLogsStub.createLogGroup).toHaveBeenCalledWith({
logGroupName: "the-log-groupname"
}, jasmine.any(Function))
done()
})
})
it ("fails when the log group creation fails", done => {
describeLogGroupsWillReturn({});
spyOn(cloudwatchLogsStub, "createLogGroup").and.callFake((info, callback) => {
callback("something went wrong")
});
service.init(initConfig).then(null, (err) => {
expect(err).toBe("something went wrong");
done();
})
})
it ("does not create the log group if it already exists", done => {
describeLogGroupsWillReturn({
logGroups: [
{logGroupName: "aaaa"},
{logGroupName: "the-log-groupname"}
]
});
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
createLogStreamWillSucceed()
service.init(initConfig).then(() => {
expect(cloudwatchLogsStub.createLogGroup).not.toHaveBeenCalled()
done()
})
})
it ("reads what log streams exist", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
createLogStreamWillSucceed()
service.init(initConfig).then(() => {
expect(cloudwatchLogsStub.describeLogStreams).toHaveBeenCalledWith({
logGroupName: "the-log-groupname",
logStreamNamePrefix: "the-log-stream"
}, jasmine.any(Function))
done()
})
})
it ("fails when the log streams could not be read", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
spyOn(cloudwatchLogsStub, "describeLogStreams").and.callFake((info, callback) => {
callback("something went wrong")
})
service.init(initConfig).then(null, (err) => {
expect(err).toBe("something went wrong");
done();
})
})
it ("creates the log stream if it doesn't exist", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
createLogStreamWillSucceed()
service.init(initConfig).then(() => {
expect(cloudwatchLogsStub.createLogStream).toHaveBeenCalledWith({
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream"
}, jasmine.any(Function))
done()
})
})
it ("fails when the log stream could not be created", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
spyOn(cloudwatchLogsStub, "createLogStream").and.callFake((info, callback) => {
callback("something went wrong")
})
service.init(initConfig).then(null, (err) => {
expect(err).toBe("something went wrong");
done();
})
})
it ("does not create the log stream if it already exist", done => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({
logStreams: [
{logStreamName: "pepitin"},
{logStreamName: "the-log-stream"}
]
})
createLogStreamWillSucceed()
service.init(initConfig).then(() => {
expect(cloudwatchLogsStub.createLogStream).not.toHaveBeenCalled()
done()
})
})
})
describe("send", () => {
function generateMessages(count){
var result = []
for (var i = 0; i < count; i++){
result.push({
timestamp: i,
message: `line ${i + 1}`
})
}
return result
}
describe("on new streams", () => {
beforeEach(() => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({})
createLogStreamWillSucceed()
service.init(initConfig)
})
it ("does not transmit anything when there are no messages", done => {
putLogEventsWillSucceed()
service
.send(generateMessages(0))
.then(() => {
expect(cloudwatchLogsStub.putLogEvents).not.toHaveBeenCalled()
done()
})
})
it ("does not transmit empty messages", done => {
putLogEventsWillSucceed()
var allMessages = generateMessages(10)
allMessages[0].message = null
allMessages[3].message = ""
allMessages[6].message = undefined
service
.send(allMessages)
.then(() => {
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 1, message: "line 2"},
{timestamp: 2, message: "line 3"},
{timestamp: 4, message: "line 5"},
{timestamp: 5, message: "line 6"},
{timestamp: 7, message: "line 8"},
{timestamp: 8, message: "line 9"},
{timestamp: 9, message: "line 10"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream"
}, jasmine.any(Function))
done()
})
})
it ("sends the correct information", done => {
putLogEventsWillSucceed()
service
.send(generateMessages(2))
.then(() => {
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 0, message: "line 1"},
{timestamp: 1, message: "line 2"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream"
}, jasmine.any(Function))
done()
})
})
it ("escapes terminal colors", done => {
putLogEventsWillSucceed()
var seededLines = generateMessages(2)
var excludedSequences = Array
.from(new Array(20), (x,i) => i + 30)
.map(x => `[${x}m`)
.join("")
seededLines[0].message += excludedSequences
seededLines[1].message = "[36" + seededLines[1].message
service
.send(seededLines)
.then(() => {
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 0, message: "line 1"},
{timestamp: 1, message: "[36line 2"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream"
}, jasmine.any(Function))
done()
})
})
it ("fails when log transmission failed", done => {
spyOn(cloudwatchLogsStub, "putLogEvents").and.callFake((info, callback) => {
callback("something went wrong");
});
service
.send(generateMessages(2))
.then(null, (err) => {
expect(err).toBe("something went wrong")
done()
})
})
it ("sends the correct sequence token in subsequent responses", done => {
var nextSequenceToken = 1000
putLogEventsWillSucceed(() => {
nextSequenceToken += 1
return nextSequenceToken
})
function sendOneMessage(callback){
service.send(generateMessages(1))
.then(() => callback(null), callback)
}
async.series([
sendOneMessage,
sendOneMessage,
sendOneMessage
], err => {
if (err){
jasmine.getEnv().fail("sending messages failed")
done()
return
}
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 0, message: "line 1"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream"
}, jasmine.any(Function))
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 0, message: "line 1"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream",
sequenceToken: 1001
}, jasmine.any(Function))
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 0, message: "line 1"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream",
sequenceToken: 1002
}, jasmine.any(Function))
done()
})
})
})
describe("on existing streams", () => {
beforeEach(() => {
describeLogGroupsWillReturn({})
createLogGroupsWillSucceed()
describeLogStreamsWillReturn({
logStreams: [
{logStreamName: "pepitin"},
{logStreamName: "the-log-stream", uploadSequenceToken: 2000}
]
})
service.init(initConfig)
})
it ("sends the correct information", done => {
putLogEventsWillSucceed()
service
.send(generateMessages(2))
.then(() => {
expect(cloudwatchLogsStub.putLogEvents).toHaveBeenCalledWith({
logEvents: [
{timestamp: 0, message: "line 1"},
{timestamp: 1, message: "line 2"}
],
logGroupName: "the-log-groupname",
logStreamName: "the-log-stream",
sequenceToken: 2000
}, jasmine.any(Function))
done()
})
})
})
})
})