Files
Zos/Skills/@be/node_modules/syslog-streams2/test.js

674 lines
26 KiB
JavaScript

'use strict';
var SyslogStream = require('./index'),
format = require('util').format,
os = require('os');
require('should');
var TEST = {
NAME: 'Test',
MSG_ID: 'FOOMSG',
PEN: 12343,
FACILITY: 'LOCAL3',
HOSTNAME: '127.0.1.1'
};
var syslogRegex = new RegExp(format(
'^<\\d+>\\d [\\d\\-.T:Z]+ %s %s \\d+ %s \\S',
TEST.HOSTNAME,
TEST.NAME,
TEST.MSG_ID
));
var ISOStringRegex = /"[\d\-.T:Z]+"$/;
var BUNYAN = {
FATAL: 60,
ERROR: 50,
WARN: 40,
INFO: 30,
DEBUG: 20,
TRACE: 10
};
var SYSLOG = {
VERSION: 1,
NILVALUE: '-',
LEVEL: {
EMERG: 0,
ALERT: 1,
CRIT: 2,
ERR: 3,
WARNING: 4,
NOTICE: 5,
INFO: 6,
DEBUG: 7
},
FACILITY: {
KERN: 0,
USER: 1,
MAIL: 2,
DAEMON: 3,
AUTH: 4,
SYSLOG: 5,
LPR: 6,
NEWS: 7,
UUCP: 8,
CLOCK: 9,
AUTHPRIV: 10,
FTP: 11,
NTP: 12,
LOG_AUDIT: 13,
LOG_ALERT: 14,
CRON: 15,
LOCAL0: 16,
LOCAL1: 17,
LOCAL2: 18,
LOCAL3: 19,
LOCAL4: 20,
LOCAL5: 21,
LOCAL6: 22,
LOCAL7: 23
}
};
describe('SyslogStream', function () {
var syslog, stream;
// cheating - relying on emit's synchronous behavior
function getMsg(r, log) {
var msg;
if (!log) { log = syslog; }
log.once('data', function (chunk) {
msg = chunk.toString();
});
log.write(r);
return msg.replace(/\r?\n$/, '');
}
function getHeader(r, log) {
var msg = getMsg(r, log);
return msg.split(' ').slice(0, 7);
}
beforeEach(function () {
syslog = new SyslogStream({
name: TEST.NAME,
msgId: TEST.MSG_ID,
PEN: TEST.PEN,
facility: TEST.FACILITY,
hostname: TEST.HOSTNAME
});
});
afterEach(function (done) {
syslog.end(done);
});
describe('constructor', function () {
it('provides default values', function () {
var log = new SyslogStream();
['type', 'facility', 'host', 'appName', 'msgID', 'pid']
.forEach(function (type) {
log.glossy[type].should.be.ok;
});
log.decodeBuffers.should.equal(false);
log.decodeJSON.should.equal(false);
log.defaultSeverity.should.equal('notice');
});
describe('appName', function () {
var _title = process.title,
_argv = process.argv;
beforeEach(function () {
delete process.title;
delete process.argv;
});
afterEach(function () {
process.title = _title;
process.argv = _argv;
});
it('should respect the specified value', function () {
var log = new SyslogStream({ appName: TEST.NAME });
getHeader('bar', log)[3].should.equal(TEST.NAME);
var log = new SyslogStream({ name: TEST.NAME });
getHeader('bar', log)[3].should.equal(TEST.NAME);
});
it('should fall back on process.title', function () {
process.title = 'process.title';
var log = new SyslogStream();
getHeader('bar', log)[3].should.equal('process.title');
});
it('should fall back on process.argv[0]', function () {
process.argv = ['process.argv'];
var log = new SyslogStream();
getHeader('bar', log)[3].should.equal('process.argv');
});
it('should fall back on NILVALUE', function () {
var log = new SyslogStream();
getHeader('bar', log)[3].should.equal(SYSLOG.NILVALUE);
});
});
describe('hostname', function () {
var _os_hostname = os.hostname;
beforeEach(function () {
os.hostname = function () { };
});
afterEach(function () {
os.hostname = _os_hostname;
});
it('should respect the specified hostname', function () {
var log = new SyslogStream({ host: TEST.HOSTNAME });
getHeader('bar', log)[2].should.equal(TEST.HOSTNAME);
var log = new SyslogStream({ hostname: TEST.HOSTNAME });
getHeader('bar', log)[2].should.equal(TEST.HOSTNAME);
});
it('should fall back on os.hostname()', function () {
os.hostname = function () { return 'os.hostname'; }
var log = new SyslogStream();
getHeader('bar', log)[2].should.equal('os.hostname');
});
it('should fall back on NILVALUE', function () {
var log = new SyslogStream();
getHeader('bar', log)[2].should.equal(SYSLOG.NILVALUE);
});
});
describe('pid', function () {
var _pid = process.pid;
beforeEach(function () {
delete process.pid;
});
afterEach(function () {
process.pid = _pid
});
it('should respect the specified pid', function () {
var log = new SyslogStream({ pid: 1 });
getHeader('bar', log)[4].should.equal('1');
});
it('should fall back on process.pid', function () {
process.pid = 2;
var log = new SyslogStream();
getHeader('bar', log)[4].should.equal('2');
});
it('should fall back on NILVALUE', function () {
var log = new SyslogStream();
getHeader('bar', log)[4].should.equal(SYSLOG.NILVALUE);
});
});
describe('msgID', function () {
it('should respect the specified hostname', function () {
var log = new SyslogStream({ msgID: TEST.MSG_ID });
getHeader('bar', log)[5].should.equal(TEST.MSG_ID);
var log = new SyslogStream({ msgId: TEST.MSG_ID });
getHeader('bar', log)[5].should.equal(TEST.MSG_ID);
});
it('should fall back on NILVALUE', function () {
var log = new SyslogStream();
getHeader('bar', log)[5].should.equal(SYSLOG.NILVALUE);
});
});
it('should respect the decodeBuffers option', function () {
var log = new SyslogStream({
decodeBuffers: true
});
getMsg(new Buffer('foo'), log).should.match(/foo$/);
var log = new SyslogStream({
decodeBuffers: false
});
try {
getMsg(new Buffer('foo'), log).should.match(/\[102,111,111\]$/);
} catch (e) {
getMsg(new Buffer('foo'), log).should.match(/\{"type":"Buffer","data":\[102,111,111\]\}$/);
}
});
it('should respect the decodeJSON option', function () {
var log = new SyslogStream({
decodeJSON: true
});
getMsg('{"msg":"foo"}', log).should.match(/foo$/);
var log = new SyslogStream({
decodeJSON: false
});
getMsg('{"msg":"foo"}', log).should.match(/\{"msg":"foo"\}$/);
});
it('should allow decodeBuffers and decodeJSON to work together', function () {
var log = new SyslogStream({
decodeBuffers: true,
decodeJSON: true
});
getMsg(new Buffer('{"msg":"foo"}'), log).should.match(/foo$/);
});
it('should respect the useStructuredData option', function () {
var log = new SyslogStream({ useStructuredData: true, PEN: 1 });
getMsg({ msg: 'foo', data: { bar: 'baz' } }, log).should.match(/\[data@1 bar="baz"\] foo$/);
var log = new SyslogStream({ useStructuredData: true });
getMsg({ msg: 'foo' }, log).should.match(/- foo$/);
var log = new SyslogStream({ useStructuredData: false, PEN: 1 });
getMsg({ msg: 'foo', data: { bar: 'baz' } }, log).should.match(/- foo \{"data":\{"bar":"baz"\}\}$/);
var log = new SyslogStream({ useStructuredData: false});
getMsg({ msg: 'foo' }, log).should.match(/- foo$/);
});
});
describe('message', function () {
it('should accept plain text strings', function () {
getMsg('foo').should.match(syslogRegex);
});
it('should return JSON encoded messages for arrays', function () {
getMsg([null, 'foo']).should.match(/\[null,"foo"\]$/);
});
it('should return an ISO Date string for Date objects', function () {
getMsg(new Date()).should.match(ISOStringRegex);
});
it('should interpret other primitives as strings', function () {
getMsg(true).should.match(/true$/);
getMsg(123).should.match(/123$/);
});
it('should use the \'msg\' field of a record as the message if it exists', function () {
getMsg({
msg: 'msgKey'
}).should.match(/msgKey$/);
});
});
describe('header', function () {
function priority(level, facility) {
level = syslog.convertBunyanLevel(level);
return new RegExp(format('^<%d>1$', facility * 8 + level));
}
function header(rec, token) {
if (arguments.length === 1) {
return getHeader({ msg: 'foo' })[rec];
}
rec.msg = rec.msg || 'foo';
return getHeader(rec)[token];
}
describe('priority', function () {
var DEF_FACILITY = SYSLOG.FACILITY[TEST.FACILITY];
it('should default the level to BUNYAN.INFO', function () {
header(0).should.match(priority(BUNYAN.INFO, DEF_FACILITY));
});
it('should default the facility to local0', function () {
var log = new SyslogStream();
getHeader('foo', log)[0].should.match(priority(BUNYAN.INFO, SYSLOG.FACILITY.LOCAL0));
});
it('should reflect explicitly specified levels', function () {
header({ level: 'fatal' }, 0).should.match(priority(BUNYAN.FATAL, DEF_FACILITY));
});
});
describe('time', function () {
it('should default the timestamp to the current time', function () {
var now = new Date();
var ts = new Date(header(1));
Math.abs(now-ts).should.be.within(0, 10);
});
it('should use the provided timestamp if given', function () {
var then = new Date();
then.setFullYear(then.getFullYear() - 1);
header({ time: then }, 1).should.equal(then.toISOString());
});
it('should use the NILVALUE if given false for the timestamp', function () {
header({ time: 'foo' }, 1).should.equal(SYSLOG.NILVALUE);
});
});
it('should supply hostname', function () {
header(2).should.equal(TEST.HOSTNAME);
});
it('should supply appName', function () {
header(3).should.equal(TEST.NAME);
});
it('should supply procId', function () {
header(4).should.eql(String(process.pid));
});
it('should supply msgId', function () {
header(5).should.equal(TEST.MSG_ID);
});
it('should supply structuredData as NILVALUE when none is given', function () {
header(6).should.equal(SYSLOG.NILVALUE);
});
});
describe('structured data', function () {
function SD(r, log) {
r.msg = 'foo';
var msg = getMsg(r, log);
var matched = msg.split(' ').slice(6).join(' ').replace(/\\\\/, '').match(/(\[(\\\]|[^\]])+\])+/);
return matched ? matched[0] : '';
};
describe('standard SDIDs', function () {
describe('timeQuality', function () {
it('should validate with no arguments', function () {
SD({
timeQuality: { }
}).should.equal('[timeQuality]');
});
it('should accept tzKnown', function () {
SD({
timeQuality: { tzKnown: 1 }
}).should.equal('[timeQuality tzKnown="1"]');
});
it('should error if tzKnown is invalid', function () {
[null, 1.2, 3, -1, Infinity, { }].forEach(function (val) {
SD({ timeQuality: { tzKnown: val } }).should.equal('');
});
});
it('should accept isSynced', function () {
SD({
timeQuality: { isSynced: 0 }
}).should.equal('[timeQuality isSynced="0"]');
});
it('should error if isSynced is invalid', function () {
[null, 1.2, 3, -1, Infinity, { }].forEach(function (val) {
SD({ timeQuality: { isSynced: val} }).should.equal('');
});
});
it('should accept syncAccuracy', function () {
// Joi counts isSynced being undefined as being defined as 0,
// so must specify 'isSynced' here even though it's not required by the RFC
SD({
timeQuality: { isSynced: 1, syncAccuracy: 123 }
}).should.equal('[timeQuality isSynced="1" syncAccuracy="123"]');
});
it('should error if syncAccuracy is supplied when isSynced is 0', function () {
var rec = { timeQuality: { isSynced: 0, syncAccuracy: 123 } };
SD(rec).should.equal('');
rec.msg = 'foo';
getMsg(rec).should.match(/syncAccuracy is not allowed/);
});
});
describe('origin', function () {
it('should validate with no arguments', function () {
SD({ origin: { } }).should.equal('[origin]');
});
it('should accept a single ip', function () {
SD({ origin: { ip: '127.0.0.1' } }).should.equal('[origin ip="127.0.0.1"]');
});
it('should accept a single hostname', function () {
SD({ origin: { ip: 'foo' } }).should.equal('[origin ip="foo"]');
SD({ origin: { ip: 'foo.bar' } }).should.equal('[origin ip="foo.bar"]');
});
it('should error on an invalid parameter for \'ip\'', function () {
var rec = { origin: { ip: '.' } };
SD(rec).should.equal('');
rec.msg = 'foo';
getMsg(rec).should.match(/ip must be a valid hostname/);
});
it('should accept an array', function () {
SD({ origin: { ip: ['127.0.0.1', '127.0.0.2'] } }).should.equal('[origin ip="127.0.0.1" ip="127.0.0.2"]');
});
it('should accept an enterpriseId', function () {
SD({ origin: { enterpriseId: '1234' } }).should.equal('[origin enterpriseId="1234"]');
});
it('should accept a software name', function () {
SD({ origin: { software: 'poop' } }).should.equal('[origin software="poop"]');
});
it('should accept a software version', function () {
SD({ origin: { swVersion: '4242' } }).should.equal('[origin swVersion="4242"]');
});
});
describe('meta', function () {
it('should validate with no arguments', function () {
SD({ meta: { } }).should.equal('[meta]');
});
// probably could/should implement this into the code
it('should accept a sequence id', function () {
SD({ meta: { sequenceId: 1 } }).should.equal('[meta sequenceId="1"]');
});
it('should accept system uptime', function () {
SD({ meta: { sysUpTime: 1234 } }).should.equal('[meta sysUpTime="1234"]');
});
it('should accept a language', function () {
SD({ meta: { language: 'en-us' } }).should.equal('[meta language="en-us"]');
});
it('should error on an invalid BCP_47 language tag', function () {
['en_US', 1234, 'jabberwocky', null].forEach(function(val) {
SD({ meta: { language: val } }).should.equal('');
});
});
});
it('should accept everything', function () {
SD({
timeQuality: {
tzKnown: 1,
isSynced: 1,
syncAccuracy: 123
},
origin: {
ip: ['127.0.0.1', 'foo.bar'],
enterpriseId: '3434.34355',
software: 'keke',
swVersion: '1.2.3'
},
meta: {
sequenceId: 55,
sysUpTime: 21355,
language: 'fr'
}
}).should.equal(
'[timeQuality tzKnown="1" isSynced="1" syncAccuracy="123"]'+
'[origin ip="127.0.0.1" ip="foo.bar" enterpriseId="3434.34355" software="keke" swVersion="1.2.3"]'+
'[meta sequenceId="55" sysUpTime="21355" language="fr"]'
);
});
});
describe('custom SDIDs', function () {
var PEN = TEST.PEN;
it('should not produce custom structured data if PEN is invalid', function () {
var log = new SyslogStream({
PEN: 'foo'
});
SD({ foo: { bar: 123 } }, log).should.equal('');
});
it('should format any extra keys as structured data; SDID should contain the PEN', function () {
SD({ foo: { bar: 123 } }).should.equal('[foo@'+PEN+' bar="123"]');
});
it('should not create any structured data if there is no PEN', function () {
var _useSD = syslog.useStructuredData;
syslog.useStructuredData = false;
SD({ foo: { bar: 123 } }).should.equal('');
syslog.useStructuredData = _useSD;
});
it('should not format keys that are not maps', function () {
['bar', new Date(), true, /./].forEach(function (val) {
var rec = { foo: val };
SD(rec).should.equal('');
rec.foo.should.equal(val);
rec.should.not.have.property('SD_VALIDATION_ERROR');
});
});
it('should not format keys that are illegal SDID values', function () {
var rec = { '@': { invalid: 'true' } };
SD(rec).should.equal('');
rec['@'].invalid.should.equal('true');
rec.should.not.have.property('SD_VALIDATION_ERROR');
});
it('should accept arrays', function () {
SD({ foo: { bar: [ 1, 2, 3 ] } }).should.equal('[foo@'+PEN+' bar="1" bar="2" bar="3"]');
});
});
});
describe('write', function () {
it('should provide any data not converted to structured data as JSON', function () {
getMsg({
msg: 'hai',
'@': 'foo'
}).should.match(/{"@":"foo"}$/);
});
it('should not be destructive to the passed object', function () {
var rec = { level: 'warn', msg: 'foo' };
syslog.write(rec);
rec.should.eql({
level: 'warn',
msg: 'foo'
});
});
it('should convert a buffer to a string', function () {
var _decodeBuffers = syslog.decodeBuffers;
syslog.decodeBuffers = true;
getMsg(new Buffer('foo')).should.match(/foo$/);
syslog.decodeBuffers = _decodeBuffers;
});
it('should decode JSON if possible', function () {
var _decodeJSON = syslog.decodeJSON;
syslog.decodeJSON = true;
getMsg('{"msg":"foo"}').should.match(/foo$/);
syslog.decodeJSON = _decodeJSON;
});
});
describe('formatObject', function () {
it('should flag circular references', function () {
var obj = { };
obj.foo = obj;
getMsg(obj).should.match(/\{"foo":"\[Circular\]"\}$/);
});
});
describe('convertBunyanLevel', function () {
it('should return the correct syslog mapping for the given bunyan level', function () {
[ [ BUNYAN.FATAL, SYSLOG.LEVEL.EMERG ],
[ BUNYAN.ERROR, SYSLOG.LEVEL.ERR ],
[ BUNYAN.WARN, SYSLOG.LEVEL.WARNING ],
[ BUNYAN.INFO, SYSLOG.LEVEL.NOTICE ],
[ BUNYAN.DEBUG, SYSLOG.LEVEL.INFO ],
[ BUNYAN.TRACE, SYSLOG.LEVEL.DEBUG ] ]
.forEach(function (pair) {
syslog.convertBunyanLevel(pair[0]).should.equal(pair[1]);
});
});
it('should return a valid syslog mapping for other log values not mapped directly to bunyan log names', function () {
[ [ 99, SYSLOG.LEVEL.EMERG ],
[ 53, SYSLOG.LEVEL.ERR ],
[ 42, SYSLOG.LEVEL.WARNING ],
[ 31, SYSLOG.LEVEL.NOTICE ],
[ 28, SYSLOG.LEVEL.INFO ],
[ 11, SYSLOG.LEVEL.DEBUG ],
[ 7, SYSLOG.LEVEL.DEBUG ],
[ 'foo', SYSLOG.LEVEL.NOTICE ] ]
.forEach(function (pair) {
syslog.convertBunyanLevel(pair[0]).should.equal(pair[1]);
});
});
it('should accept (case-insensitive) strings', function () {
[ [ 'Fatal', SYSLOG.LEVEL.EMERG ],
[ 'Error', SYSLOG.LEVEL.ERR ],
[ 'Warn', SYSLOG.LEVEL.WARNING ],
[ 'infO', SYSLOG.LEVEL.NOTICE ],
[ 'debuG', SYSLOG.LEVEL.INFO ],
[ 'tracE', SYSLOG.LEVEL.DEBUG ] ]
.forEach(function (pair) {
syslog.convertBunyanLevel(pair[0]).should.equal(pair[1]);
});
});
it('should return the syslog notice level for invalid values', function () {
[null, new Date(), 'foo', [ ]]
.forEach(function (val) {
syslog.convertBunyanLevel(val).should.equal(SYSLOG.LEVEL.NOTICE);
});
});
});
describe('bunyan record', function () {
it('should output a message', function () {
getMsg({
msg: 'foo'
}).should.match(/foo$/);
});
it('should process structured data', function () {
getMsg({
msg: 'hai',
'@': 'foo'
}).should.match(/{"@":"foo"}$/);
getMsg({
msg: 'foo',
timeQuality: {
tzKnown: 1,
isSynced: 1,
syncAccuracy: 123
},
origin: {
ip: ['127.0.0.1', 'foo.bar'],
enterpriseId: '3434.34355',
software: 'keke',
swVersion: '1.2.3'
},
meta: {
sequenceId: 55,
sysUpTime: 21355,
language: 'fr'
}
}).should.containEql(
'[timeQuality tzKnown="1" isSynced="1" syncAccuracy="123"]'+
'[origin ip="127.0.0.1" ip="foo.bar" enterpriseId="3434.34355" software="keke" swVersion="1.2.3"]'+
'[meta sequenceId="55" sysUpTime="21355" language="fr"]'
);
});
it('should fall back on JSON with invalid data', function () {
getMsg({
v: 'bar',
msg: 'foo'
}).should.containEql('{"v":"bar","msg":"foo"}');
});
});
describe('glossy record', function () {
it('should output a message', function () {
getMsg({
message: 'foo'
}).should.match(/foo$/);
});
it('should process structured data', function () {
getMsg({
message: 'hai',
structuredData: {
'@': 'foo'
}
}).should.match(/{"@":"foo"}$/);
getMsg({
message: 'foo',
structuredData: {
timeQuality: {
tzKnown: 1,
isSynced: 1,
syncAccuracy: 123
},
origin: {
ip: ['127.0.0.1', 'foo.bar'],
enterpriseId: '3434.34355',
software: 'keke',
swVersion: '1.2.3'
},
meta: {
sequenceId: 55,
sysUpTime: 21355,
language: 'fr'
}
}
}).should.containEql(
'[timeQuality tzKnown="1" isSynced="1" syncAccuracy="123"]'+
'[origin ip="127.0.0.1" ip="foo.bar" enterpriseId="3434.34355" software="keke" swVersion="1.2.3"]'+
'[meta sequenceId="55" sysUpTime="21355" language="fr"]'
);
});
it('should fall back on JSON with invalid data', function () {
getMsg({
appName: null,
message: 'foo'
}).should.containEql('{"appName":null,"message":"foo"}');
});
});
});