Cleanup Repo

This commit is contained in:
rainbownapkin 2022-06-26 01:07:41 +00:00
parent 56e15894c0
commit 0cabedbac6
22 changed files with 0 additions and 2241 deletions

View file

@ -1,91 +0,0 @@
const assert = require('assert');
const Camo = require('../lib/camo');
const CamoConfig = require('../lib/configuration/camoconfig').CamoConfig;
describe('Camo', () => {
const config = new CamoConfig({
camo: {
server: 'http://localhost:8081',
key: '9LKC7708ZHOVRCTLOLE3G2YJ0U1T8F96',
'whitelisted-domains': ['def.xyz', 'tii.kzz.qqq'],
encoding: 'hex'
}
});
describe('#camoify', () => {
it('constructs a camo url', () => {
const result = Camo.camoify(config, 'http://abc.xyz/image.jpeg');
assert.strictEqual(result, 'http://localhost:8081/a9c295dd7d8dcbc8247dec97ac5d9b4ee8baeb31/687474703a2f2f6162632e78797a2f696d6167652e6a706567');
});
it('constructs a camo url using url encoding', () => {
const config = new CamoConfig({
camo: {
server: 'http://localhost:8081',
key: '9LKC7708ZHOVRCTLOLE3G2YJ0U1T8F96',
'whitelisted-domains': ['def.xyz'],
encoding: 'url'
}
});
const result = Camo.camoify(config, 'http://abc.xyz/image.jpeg');
assert.strictEqual(result, 'http://localhost:8081/a9c295dd7d8dcbc8247dec97ac5d9b4ee8baeb31?url=http%3A%2F%2Fabc.xyz%2Fimage.jpeg');
});
it('bypasses camo for whitelisted domains', () => {
const result = Camo.camoify(config, 'http://def.xyz/image.jpeg');
assert.strictEqual(result, 'https://def.xyz/image.jpeg');
});
it('bypasses camo for whitelisted domains subdomains', () => {
const result = Camo.camoify(config, 'http://abc.def.xyz/image.jpeg');
assert.strictEqual(result, 'https://abc.def.xyz/image.jpeg');
});
it('does not bypass camo for a non-subdomain match', () => {
const result = Camo.camoify(config, 'http://abcdef.xyz/image.jpeg');
assert.strictEqual(result, 'http://localhost:8081/19f53f65e8081a064cff54fbd665e8bb08612aa6/687474703a2f2f6162636465662e78797a2f696d6167652e6a706567');
});
it('does not bypass camo when no whitelist is configured', () => {
const config = new CamoConfig({
camo: {
server: 'http://localhost:8081',
key: '9LKC7708ZHOVRCTLOLE3G2YJ0U1T8F96',
encoding: 'hex'
}
});
const result = Camo.camoify(config, 'http://abcdef.xyz/image.jpeg');
assert.strictEqual(result, 'http://localhost:8081/19f53f65e8081a064cff54fbd665e8bb08612aa6/687474703a2f2f6162636465662e78797a2f696d6167652e6a706567');
});
});
describe('#transformImgTags', () => {
it('transforms an img tag with a src', () => {
const attribs = {
src: 'http://abc.xyz/image.jpeg',
'class': 'some-image'
};
const expectedAttribs = {
src: 'http://localhost:8081/a9c295dd7d8dcbc8247dec97ac5d9b4ee8baeb31/687474703a2f2f6162632e78797a2f696d6167652e6a706567',
'class': 'some-image'
};
const result = Camo.transformImgTags(config, 'img', attribs);
assert.deepStrictEqual(result, { tagName: 'img', attribs: expectedAttribs });
});
it('skips img tags with no src', () => {
const attribs = { 'class': 'some-image' };
const result = Camo.transformImgTags(config, 'img', attribs);
assert.deepStrictEqual(result, { tagName: 'img', attribs: attribs });
});
it('fails gracefully', () => {
const attribs = { src: 'http://abc.xyz/image.jpeg' };
const config = new CamoConfig({ camo: { enabled: true }});
config.getKey = () => { throw new Error('something happened'); };
const result = Camo.transformImgTags(config, 'img', attribs);
assert.deepStrictEqual(result, { tagName: 'img', attribs: attribs });
});
});
});

View file

@ -1,53 +0,0 @@
const PlaylistModule = require('../../lib/channel/playlist');
const assert = require('assert');
const Config = require('../../lib/config');
describe('PlaylistModule', () => {
describe('#handleClean', () => {
let fakeChannel = {
uniqueName: 'testChannel',
logger: {
log() {
}
},
broadcastToRoom() {
},
broadcastAll() {
},
modules: {
permissions: {
canDeleteVideo() {
return true;
}
}
}
};
let fakeUser = {
getName() {
return 'testUser';
},
socket: {
emit() {
}
}
};
let playlistModule = new PlaylistModule(fakeChannel);
it('rejects invalid regexes', () => {
let sentError = false;
fakeUser.socket.emit = (event, payload) => {
assert.strictEqual(event, 'errorMsg');
assert.deepStrictEqual(payload, {
msg: "Invalid target: -i * -m"
});
sentError = true;
};
playlistModule.handleClean(fakeUser, "/clean -i * -m", {});
assert(sentError, 'Expected error due to invalid regex');
});
});
});

View file

@ -1,221 +0,0 @@
const PollModule = require('../../lib/channel/poll');
const assert = require('assert');
const Config = require('../../lib/config');
describe('PollModule', () => {
describe('#validatePollInput', () => {
let pollModule = new PollModule({ uniqueName: 'testChannel', modules: {} });
it('accepts valid input', () => {
let title = '';
for (let i = 0; i < 20; i++) {
title += 'x';
}
pollModule.validatePollInput(title, ['ab', 'cd']);
});
it('rejects non-string titles', () => {
assert.throws(() => {
pollModule.validatePollInput(null, []);
}, /title/);
});
it('rejects invalidly long titles', () => {
let title = '';
for (let i = 0; i < 256; i++) {
title += 'x';
}
assert.throws(() => {
pollModule.validatePollInput(title, []);
}, /title/);
});
it('rejects non-array option parameter', () => {
assert.throws(() => {
pollModule.validatePollInput('poll', 1234);
}, /options/);
});
it('rejects too many options', () => {
const limit = Config.get('poll.max-options');
Config.set('poll.max-options', 2);
try {
assert.throws(() => {
pollModule.validatePollInput('poll', ['1', '2', '3', '4']);
}, /maximum of 2 options/);
} finally {
Config.set('poll.max-options', limit);
}
});
it('rejects non-string options', () => {
assert.throws(() => {
pollModule.validatePollInput('poll', [null]);
}, /options must be strings/);
});
it('rejects invalidly long options', () => {
let option = '';
for (let i = 0; i < 256; i++) {
option += 'x';
}
assert.throws(() => {
pollModule.validatePollInput('poll', [option]);
}, /options must be 1-255 characters/);
});
});
describe('#handleNewPoll', () => {
let fakeChannel = {
uniqueName: 'testChannel',
logger: {
log() {
}
},
broadcastToRoom() {
},
broadcastAll() {
},
modules: {
permissions: {
canControlPoll() {
return true;
}
}
}
};
let fakeUser = {
getName() {
return 'testUser';
},
socket: {
emit() {
}
}
};
let pollModule;
beforeEach(() => {
pollModule = new PollModule(fakeChannel);
});
it('creates a valid poll', () => {
let sentNewPoll = false;
let sentClosePoll = false;
fakeChannel.broadcastToRoom = (event, data, room) => {
if (room === 'testChannel:viewHidden' && event === 'newPoll') {
sentNewPoll = true;
}
};
fakeChannel.broadcastAll = (event) => {
if (event === 'closePoll') {
sentClosePoll = true;
}
};
pollModule.handleNewPoll(fakeUser, {
title: 'test poll',
opts: [
'option 1',
'option 2'
],
obscured: false
}, (ackResult) => {
assert(!ackResult.error, `Unexpected error: ${ackResult.error}`);
});
assert(!sentClosePoll, 'Unexpected broadcast of closePoll event');
assert(sentNewPoll, 'Expected broadcast of newPoll event');
});
it('closes an existing poll when a new one is created', () => {
let sentNewPoll = 0;
let sentClosePoll = 0;
let sentUpdatePoll = 0;
fakeChannel.broadcastToRoom = (event, data, room) => {
if (room === 'testChannel:viewHidden' && event === 'newPoll') {
sentNewPoll++;
}
};
fakeChannel.broadcastAll = (event, data) => {
if (event === 'closePoll') {
sentClosePoll++;
} else if (event === 'updatePoll') {
sentUpdatePoll++;
assert.deepStrictEqual(data.counts, [0, 0]);
}
};
pollModule.handleNewPoll(fakeUser, {
title: 'test poll',
opts: [
'option 1',
'option 2'
],
obscured: true
}, (ackResult) => {
assert(!ackResult.error, `Unexpected error: ${ackResult.error}`);
});
pollModule.handleNewPoll(fakeUser, {
title: 'poll 2',
opts: [
'option 3',
'option 4'
],
obscured: false
}, (ackResult) => {
assert(!ackResult.error, `Unexpected error: ${ackResult.error}`);
});
assert.strictEqual(sentClosePoll, 1, 'Expected 1 broadcast of closePoll event');
assert.strictEqual(sentUpdatePoll, 1, 'Expected 1 broadcast of updatePoll event');
assert.strictEqual(sentNewPoll, 2, 'Expected 2 broadcasts of newPoll event');
});
it('rejects an invalid poll', () => {
fakeChannel.broadcastToRoom = (event, data, room) => {
assert(false, 'Expected no events to be sent');
};
fakeChannel.broadcastAll = (event) => {
assert(false, 'Expected no events to be sent');
};
const options = [];
for (let i = 0; i < 200; i++) {
options.push('option ' + i);
}
pollModule.handleNewPoll(fakeUser, {
title: 'test poll',
opts: options,
obscured: false
}, (ackResult) => {
assert.equal(ackResult.error.message, 'Polls are limited to a maximum of 50 options.');
});
});
it('handles a rejection with no ack provided by socket.io', () => {
fakeChannel.broadcastToRoom = (event, data, room) => {
assert(false, 'Expected no events to be sent');
};
fakeChannel.broadcastAll = (event) => {
assert(false, 'Expected no events to be sent');
};
let sentErrorMsg = false;
fakeUser.socket.emit = (event, data) => {
if (event === 'errorMsg') {
sentErrorMsg = true;
}
};
const options = [];
for (let i = 0; i < 200; i++) {
options.push('option ' + i);
}
pollModule.handleNewPoll(fakeUser, {
title: 'test poll',
opts: options,
obscured: false
});
assert(sentErrorMsg, 'Expected to send errorMsg since ack was missing');
});
})
});

View file

@ -1,138 +0,0 @@
const VoteskipModule = require('../../lib/channel/voteskip');
const assert = require('assert');
const Flags = require('../../lib/flags');
describe('VoteskipModule', () => {
let fakeUser;
let fakeChannel;
let voteskipModule;
beforeEach(() => {
fakeUser = {
socket: {
emit() {
}
},
is() {
return false
}
};
fakeChannel = {
logger: {
log() {
}
},
modules: {
permissions: {
canSeeVoteskipResults() {
return true;
},
canVoteskip() {
return true;
}
},
options: {
get(key) {
if (key === 'voteskip_ratio') {
return 0.5;
} else if (key === 'allow_voteskip') {
return true;
}
}
},
playlist: {
_playNext() {
},
meta: {
count: 1
}
}
},
users: [fakeUser],
broadcastAll() {
}
};
voteskipModule = new VoteskipModule(fakeChannel);
});
describe('#update', () => {
it('resets the vote before changing to the next video', () => {
let reset = false, playNext = false;
fakeChannel.modules.playlist._playNext = () => {
if (!reset) {
assert(false, 'Expected voteskip reset prior to playlist._playNext');
}
playNext = true;
};
fakeUser.socket.emit = (event, data) => {
if (event === 'voteskip') {
assert.deepEqual(data, { count: 0, need: 0 });
reset = true;
}
};
voteskipModule.poll = {
toUpdateFrame() {
return { counts: [1] };
}
};
voteskipModule.update();
assert.equal(voteskipModule.poll, false, 'Expected voteskip poll to be reset to false');
assert(reset, 'Expected voteskip to be reset');
assert(playNext, 'Expected playlist to be advanced');
});
it('broadcasts a message', () => {
let sentMessage = false;
fakeChannel.broadcastAll = (frame, data) => {
assert.strictEqual(frame, 'chatMsg');
assert(/voteskip passed/i.test(data.msg), 'Expected voteskip passed message')
sentMessage = true;
};
voteskipModule.poll = {
toUpdateFrame() {
return { counts: [1] };
}
};
voteskipModule.update();
assert(sentMessage, 'Expected voteskip passed message');
});
});
describe('#calcUsercounts', () => {
it('calculates correctly', () => {
fakeChannel.users = [
// 1 with permission and not AFK
{ is(f) { return false; }, _has_permission: true },
// 1 without permission and not AFK
{ is(f) { return false; }, _has_permission: false },
// 1 afk with permission
{ is(f) { return f === Flags.U_AFK; }, _has_permission: true },
// 1 afk without permission
{ is(f) { return f === Flags.U_AFK; }, _has_permission: false }
]
fakeChannel.modules.permissions.canVoteskip = u => u._has_permission;
const {
total,
eligible,
afk,
noPermission
} = voteskipModule.calcUsercounts();
assert.strictEqual(total, 4, 'mismatch: total');
assert.strictEqual(eligible, 1, 'mismatch: eligible');
// Permission is checked before AFK; if user is AFK and also does
// not have permission, they should be counted in noPermission
// but not afk
assert.strictEqual(afk, 1, 'mismatch: afk');
assert.strictEqual(noPermission, 2, 'mismatch: noPermission');
});
});
});

View file

@ -1,56 +0,0 @@
const assert = require('assert');
const CamoConfig = require('../../lib/configuration/camoconfig').CamoConfig;
describe('CamoConfig', () => {
describe('#constructor', () => {
it('strips trailing slashes from the server', () => {
const config = new CamoConfig({
camo: {
server: 'http://abc.xyz/'
}
});
assert.strictEqual(config.getServer(), 'http://abc.xyz');
});
it('defaults to enabled=false', () => {
assert.strictEqual(new CamoConfig().isEnabled(), false);
});
it('validates that encoding must be either url or hex', () => {
assert.throws(() => {
new CamoConfig({
camo: {
encoding: 'asdjfnasdf'
}
});
}, /must be either 'url' or 'hex'/);
});
});
describe('#getWhitelistedDomains', () => {
it('defaults to an empty array', () => {
assert.deepStrictEqual(new CamoConfig().getWhitelistedDomains(), []);
});
});
describe('#getEncoding', () => {
it('defaults to url', () => {
assert.deepStrictEqual(new CamoConfig().getEncoding(), 'url');
});
});
describe('#getWhitelistedDomainsRegexp', () => {
it('generates a regex based on the whitelisted domains', () => {
const config = new CamoConfig({
camo: {
server: 'localhost:8081',
'whitelisted-domains': ['abc.xyz', 'tii.kzz.qqq']
}
});
const re = config.getWhitelistedDomainsRegexp();
assert.deepStrictEqual(re, /\.abc\.xyz$|\.tii\.kzz\.qqq$/i);
});
});
});

View file

@ -1,10 +0,0 @@
const assert = require('assert');
const PrometheusConfig = require('../../lib/configuration/prometheusconfig').PrometheusConfig;
describe('PrometheusConfig', () => {
describe('#constructor', () => {
it('defaults to enabled=false', () => {
assert.strictEqual(new PrometheusConfig().isEnabled(), false);
});
});
});

View file

@ -1,50 +0,0 @@
const assert = require('assert');
const { createTransport } = require('nodemailer');
const { EmailController } = require('../../lib/controller/email');
const { EmailConfig } = require('../../lib/configuration/emailconfig');
describe('EmailController', () => {
describe('sendPasswordReset', () => {
it('sends a password reset email', () => {
const mailer = createTransport({
jsonTransport: true
});
const config = new EmailConfig({
'password-reset': {
from: 'Test <test@example.com>',
subject: 'Password Reset',
'html-template': 'Reset <a href="$url$">here</a> $user$',
'text-template': 'Text is better than HTML $user$ $url$'
}
});
const controller = new EmailController(mailer, config);
return controller.sendPasswordReset({
address: 'some-user@example.com',
username: 'SomeUser',
url: 'http://localhost/password-reset/blah'
}).then(info => {
const sentMessage = JSON.parse(info.message);
assert.strictEqual(sentMessage.subject, 'Password Reset');
assert.deepStrictEqual(
sentMessage.from,
{ name: 'Test', address: 'test@example.com' }
);
assert.deepStrictEqual(
sentMessage.to,
[{ name: 'SomeUser', address: 'some-user@example.com' }]
);
assert.strictEqual(
sentMessage.html,
'Reset <a href="http://localhost/password-reset/blah">here</a> SomeUser'
);
assert.strictEqual(
sentMessage.text,
'Text is better than HTML SomeUser http://localhost/password-reset/blah'
);
});
});
});
});

View file

@ -1,435 +0,0 @@
const assert = require('assert');
const { validate, convert, lookup } = require('../lib/custom-media');
const http = require('http');
describe('custom-media', () => {
let valid, invalid;
beforeEach(() => {
invalid = valid = {
title: 'Test Video',
duration: 10,
live: false,
thumbnail: 'https://example.com/thumb.jpg',
sources: [
{
url: 'https://example.com/video.mp4',
contentType: 'video/mp4',
quality: 1080,
bitrate: 5000
}
],
textTracks: [
{
url: 'https://example.com/subtitles.vtt',
contentType: 'text/vtt',
name: 'English Subtitles'
}
]
};
});
describe('#validate', () => {
it('accepts valid metadata', () => {
validate(valid);
});
it('accepts valid metadata with no optional params', () => {
delete valid.live;
delete valid.thumbnail;
delete valid.textTracks;
delete valid.sources[0].bitrate;
validate(valid);
});
it('rejects missing title', () => {
delete invalid.title;
assert.throws(() => validate(invalid), /title must be a string/);
});
it('rejects blank title', () => {
invalid.title = '';
assert.throws(() => validate(invalid), /title must not be blank/);
});
it('rejects non-numeric duration', () => {
invalid.duration = 'twenty four seconds';
assert.throws(() => validate(invalid), /duration must be a number/);
});
it('rejects non-finite duration', () => {
invalid.duration = NaN;
assert.throws(() => validate(invalid), /duration must be a non-negative finite number/);
});
it('rejects negative duration', () => {
invalid.duration = -1;
assert.throws(() => validate(invalid), /duration must be a non-negative finite number/);
});
it('rejects non-boolean live', () => {
invalid.live = 'false';
assert.throws(() => validate(invalid), /live must be a boolean/);
});
it('rejects non-string thumbnail', () => {
invalid.thumbnail = 1234;
assert.throws(() => validate(invalid), /thumbnail must be a string/);
});
it('rejects invalid thumbnail URL', () => {
invalid.thumbnail = 'http://example.com/thumb.jpg';
assert.throws(() => validate(invalid), /URL protocol must be HTTPS/);
});
it('rejects non-live DASH', () => {
invalid.live = false;
invalid.sources[0].contentType = 'application/dash+xml';
assert.throws(
() => validate(invalid),
/contentType "application\/dash\+xml" requires live: true/
);
});
});
describe('#validateSources', () => {
it('rejects non-array sources', () => {
invalid.sources = { a: 'b' };
assert.throws(() => validate(invalid), /sources must be a list/);
});
it('rejects empty source list', () => {
invalid.sources = [];
assert.throws(() => validate(invalid), /source list must be nonempty/);
});
it('rejects non-string source url', () => {
invalid.sources[0].url = 1234;
assert.throws(() => validate(invalid), /source URL must be a string/);
});
it('rejects invalid source URL', () => {
invalid.sources[0].url = 'http://example.com/thumb.jpg';
assert.throws(() => validate(invalid), /URL protocol must be HTTPS/);
});
it('rejects unacceptable source contentType', () => {
invalid.sources[0].contentType = 'rtmp/flv';
assert.throws(() => validate(invalid), /unacceptable source contentType/);
});
it('rejects unacceptable source quality', () => {
invalid.sources[0].quality = 144;
assert.throws(() => validate(invalid), /unacceptable source quality/);
});
it('rejects non-numeric source bitrate', () => {
invalid.sources[0].bitrate = '1000kbps'
assert.throws(() => validate(invalid), /source bitrate must be a number/);
});
it('rejects non-finite source bitrate', () => {
invalid.sources[0].bitrate = Infinity;
assert.throws(() => validate(invalid), /source bitrate must be a non-negative finite number/);
});
it('rejects negative source bitrate', () => {
invalid.sources[0].bitrate = -1000;
assert.throws(() => validate(invalid), /source bitrate must be a non-negative finite number/);
});
});
describe('#validateTextTracks', () => {
it('rejects non-array text track list', () => {
invalid.textTracks = { a: 'b' };
assert.throws(() => validate(invalid), /textTracks must be a list/);
});
it('rejects non-string track url', () => {
invalid.textTracks[0].url = 1234;
assert.throws(() => validate(invalid), /text track URL must be a string/);
});
it('rejects invalid track URL', () => {
invalid.textTracks[0].url = 'http://example.com/thumb.jpg';
assert.throws(() => validate(invalid), /URL protocol must be HTTPS/);
});
it('rejects unacceptable track contentType', () => {
invalid.textTracks[0].contentType = 'text/plain';
assert.throws(() => validate(invalid), /unacceptable text track contentType/);
});
it('rejects non-string track name', () => {
invalid.textTracks[0].name = 1234;
assert.throws(() => validate(invalid), /text track name must be a string/);
});
it('rejects blank track name', () => {
invalid.textTracks[0].name = '';
assert.throws(() => validate(invalid), /text track name must be nonempty/);
});
});
describe('#validateURL', () => {
it('rejects non-URLs', () => {
invalid.sources[0].url = 'not a url';
assert.throws(() => validate(invalid), /invalid URL/);
});
it('rejects non-https', () => {
invalid.sources[0].url = 'http://example.com/thumb.jpg';
assert.throws(() => validate(invalid), /URL protocol must be HTTPS/);
});
it('rejects IP addresses', () => {
invalid.sources[0].url = 'https://0.0.0.0/thumb.jpg';
assert.throws(() => validate(invalid), /URL hostname must be a domain name/);
});
});
describe('#convert', () => {
let expected;
let id = 'testing';
beforeEach(() => {
expected = {
id: 'testing',
title: 'Test Video',
seconds: 10,
duration: '00:10',
type: 'cm',
meta: {
direct: {
1080: [
{
link: 'https://example.com/video.mp4',
contentType: 'video/mp4',
quality: 1080
}
]
},
textTracks: [
{
url: 'https://example.com/subtitles.vtt',
contentType: 'text/vtt',
name: 'English Subtitles'
}
]
}
};
});
function cleanForComparison(actual) {
actual = actual.pack();
// Strip out extraneous undefineds
for (let key in actual.meta) {
if (actual.meta[key] === undefined) delete actual.meta[key];
}
return actual;
}
it('converts custom metadata to a CyTube Media object', () => {
const media = convert(id, valid);
const actual = cleanForComparison(media);
assert.deepStrictEqual(actual, expected);
});
it('sets duration to 0 if live = true', () => {
valid.live = true;
expected.duration = '00:00';
expected.seconds = 0;
const media = convert(id, valid);
const actual = cleanForComparison(media);
assert.deepStrictEqual(actual, expected);
});
});
describe('#lookup', () => {
let server;
let serveFunc;
beforeEach(() => {
serveFunc = function (req, res) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(valid, null, 2));
res.end();
};
server = http.createServer((req, res) => serveFunc(req, res));
server.listen(10111);
});
afterEach(done => {
server.close(() => done());
});
it('retrieves metadata', () => {
function cleanForComparison(actual) {
actual = actual.pack();
delete actual.id;
// Strip out extraneous undefineds
for (let key in actual.meta) {
if (actual.meta[key] === undefined) delete actual.meta[key];
}
return actual;
}
const expected = {
title: 'Test Video',
seconds: 10,
duration: '00:10',
type: 'cm',
meta: {
direct: {
1080: [
{
link: 'https://example.com/video.mp4',
contentType: 'video/mp4',
quality: 1080
}
]
},
textTracks: [
{
url: 'https://example.com/subtitles.vtt',
contentType: 'text/vtt',
name: 'English Subtitles'
}
]
}
};
return lookup('http://127.0.0.1:10111/').then(result => {
assert.deepStrictEqual(cleanForComparison(result), expected);
});
});
it('rejects the wrong content-type', () => {
serveFunc = (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write(JSON.stringify(valid, null, 2));
res.end();
};
return lookup('http://127.0.0.1:10111/').then(() => {
throw new Error('Expected failure due to wrong content-type');
}).catch(error => {
assert.strictEqual(
error.message,
'Expected content-type application/json, not text/plain'
);
});
});
it('rejects non-200 status codes', () => {
serveFunc = (req, res) => {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(valid, null, 2));
res.end();
};
return lookup('http://127.0.0.1:10111/').then(() => {
throw new Error('Expected failure due to 404');
}).catch(error => {
assert.strictEqual(
error.message,
'Expected HTTP 200 OK, not 404 Not Found'
);
});
});
it('rejects responses >100KB', () => {
serveFunc = (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(Buffer.alloc(200 * 1024));
res.end();
};
return lookup('http://127.0.0.1:10111/').then(() => {
throw new Error('Expected failure due to response size');
}).catch(error => {
assert.strictEqual(
error.message,
'Response size exceeds 100KB'
);
});
});
it('times out', () => {
serveFunc = (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(valid, null, 2));
setTimeout(() => res.end(), 100);
};
return lookup('http://127.0.0.1:10111/', { timeout: 1 }).then(() => {
throw new Error('Expected failure due to request timeout');
}).catch(error => {
assert.strictEqual(
error.message,
'Request timed out'
);
assert.strictEqual(error.code, 'ETIMEDOUT');
});
});
it('rejects URLs with non-http(s) protocols', () => {
return lookup('ftp://127.0.0.1:10111/').then(() => {
throw new Error('Expected failure due to unacceptable URL protocol');
}).catch(error => {
assert.strictEqual(
error.message,
'Unacceptable protocol "ftp:". Custom metadata must be retrieved'
+ ' by HTTP or HTTPS'
);
});
});
it('rejects invalid URLs', () => {
return lookup('not valid').then(() => {
throw new Error('Expected failure due to invalid URL');
}).catch(error => {
assert.strictEqual(
error.message,
'Invalid URL "not valid"'
);
});
});
});
});

View file

@ -1,47 +0,0 @@
const customembed = require('../lib/customembed');
const assert = require('assert');
const crypto = require("crypto");
function sha256(input) {
let hash = crypto.createHash('sha256');
hash.update(input);
return hash.digest('base64');
}
describe('customembed', () => {
describe('#filter', () => {
it('rejects <embed> inputs', () => {
const input = '<embed src="https://example.com/baz.swf" type="application/x-shockwave-flash"></embed>';
assert.throws(() => { customembed.filter(input) }, /must be an <iframe>/);
});
it('rejects <object> inputs', () => {
const input = '<object data="https://example.com/baz.swf" type="application/x-shockwave-flash"></object>';
assert.throws(() => { customembed.filter(input) }, /must be an <iframe>/);
});
it('rejects plain-HTTP <iframe> inputs', () => {
const input = '<iframe src="http://foo.bar/baz.swf"></iframe>';
assert.throws(() => { customembed.filter(input) }, /must be HTTPS/);
});
it('accepts a valid iframe', () => {
let input = '<iframe src="https://example.com/video.html"</iframe>';
const { id, title, seconds, duration, type, meta } = customembed.filter(input).pack();
const { embed } = meta;
assert.strictEqual(id, `cu:${sha256(input)}`);
assert.strictEqual(title, 'Custom Media');
assert.strictEqual(seconds, 0);
assert.strictEqual(duration, '--:--');
assert.strictEqual(type, 'cu');
assert.deepStrictEqual(
embed,
{
tag: 'iframe',
src: 'https://example.com/video.html'
}
);
});
});
});

View file

@ -1,108 +0,0 @@
const assert = require('assert');
const sinon = require('sinon');
const TestUtilDB = require('../testutil/db');
const AliasesDB = require('../../lib/db/aliases').AliasesDB;
describe('AliasesDB', () => {
let mockTx, mockDB, aliasesDB;
beforeEach(() => {
mockTx = new TestUtilDB.MockTx();
mockDB = new TestUtilDB.MockDB(mockTx);
aliasesDB = new AliasesDB(mockDB);
});
describe('#addAlias', () => {
it('adds a new alias', () => {
const ip = '1.2.3.4';
const name = 'foo';
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
sinon.stub(mockTx, 'where').withArgs({ ip, name }).returns(mockTx);
const del = sinon.stub(mockTx, 'del').resolves();
const insert = sinon.stub(mockTx, 'insert').resolves();
return aliasesDB.addAlias(ip, name).then(() => {
assert(del.called, 'Expected old alias to be purged');
assert(insert.called, 'Expected new alias to be inserted');
const record = insert.getCall(0).args[0];
assert.strictEqual(record.ip, ip);
assert.strictEqual(record.name, name);
assert(typeof record.time === 'number', 'Expected time field to be a number');
});
});
});
describe('#getAliasesByIP', () => {
it('retrieves aliases by full IP', () => {
const ip = '1.2.3.4';
const rows = [
{ ip, name: 'foo' },
{ ip, name: 'bar' }
];
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
sinon.stub(mockTx, 'where').withArgs({ ip }).returns(mockTx);
sinon.stub(mockTx, 'select').returns(mockTx);
sinon.stub(mockTx, 'distinct').withArgs('name').returns(mockTx);
sinon.stub(mockTx, 'orderBy').withArgs('time', 'desc').returns(mockTx);
sinon.stub(mockTx, 'limit').withArgs(5).resolves(rows);
return aliasesDB.getAliasesByIP(ip).then(names => {
assert.deepStrictEqual(names.sort(), ['bar', 'foo']);
});
});
it('retrieves aliases by IPv4 range', () => {
const ip = '1.2.3';
const rows = [
{ ip: ip + '.4', name: 'foo' },
{ ip: ip + '.5', name: 'bar' }
];
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
sinon.stub(mockTx, 'where').withArgs('ip', 'LIKE', `${ip}.%`).returns(mockTx);
sinon.stub(mockTx, 'select').returns(mockTx);
sinon.stub(mockTx, 'distinct').withArgs('name').returns(mockTx);
sinon.stub(mockTx, 'orderBy').withArgs('time', 'desc').returns(mockTx);
sinon.stub(mockTx, 'limit').withArgs(5).resolves(rows);
return aliasesDB.getAliasesByIP(ip).then(names => {
assert.deepStrictEqual(names.sort(), ['bar', 'foo']);
});
});
it('retrieves aliases by IPv6 range', () => {
const ip = '1:2:3';
const rows = [
{ ip: ip + '::4', name: 'foo' },
{ ip: ip + '::5', name: 'bar' }
];
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
const where = sinon.stub(mockTx, 'where')
.withArgs('ip', 'LIKE', `${ip}:%`).returns(mockTx);
sinon.stub(mockTx, 'select').returns(mockTx);
sinon.stub(mockTx, 'distinct').withArgs('name').returns(mockTx);
sinon.stub(mockTx, 'orderBy').withArgs('time', 'desc').returns(mockTx);
sinon.stub(mockTx, 'limit').withArgs(5).resolves(rows);
return aliasesDB.getAliasesByIP(ip).then(names => {
assert(where.called, 'Expected WHERE LIKE clause');
assert.deepStrictEqual(names.sort(), ['bar', 'foo']);
});
});
});
describe('#getIPsByName', () => {
it('retrieves IPs by name', () => {
const name = 'foo';
const rows = [
{ name, ip: '1.2.3.4' },
{ name, ip: '5.6.7.8' }
];
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
sinon.stub(mockTx, 'select').withArgs('ip').returns(mockTx);
sinon.stub(mockTx, 'where').withArgs({ name }).resolves(rows);
return aliasesDB.getIPsByName(name).then(ips => {
assert.deepStrictEqual(ips.sort(), ['1.2.3.4', '5.6.7.8']);
});
});
});
});

View file

@ -1,84 +0,0 @@
const assert = require('assert');
const sinon = require('sinon');
const TestUtilDB = require('../testutil/db');
const GlobalBanDB = require('../../lib/db/globalban').GlobalBanDB;
describe('GlobalBanDB', () => {
let mockTx, mockDB, globalBanDB;
beforeEach(() => {
mockTx = new TestUtilDB.MockTx();
mockDB = new TestUtilDB.MockDB(mockTx);
globalBanDB = new GlobalBanDB(mockDB);
});
describe('#listGlobalBans', () => {
it('lists bans from the database', () => {
const expected = [
{ ip: '1.2.3.4', reason: 'spam' },
{ ip: '5.6', reason: 'ham' }
];
sinon.stub(mockTx, 'table').withArgs('global_bans').returns(mockTx);
sinon.stub(mockTx, 'select').resolves(expected);
return globalBanDB.listGlobalBans().then(bans => {
assert.deepStrictEqual(bans, expected);
});
});
});
describe('#addGlobalIPBan', () => {
it('adds a new ban', () => {
const input = { ip: '1.2.3.4', reason: 'spam' };
sinon.stub(mockTx, 'table').withArgs('global_bans').returns(mockTx);
const insert = sinon.stub(mockTx, 'insert').withArgs(input).resolves();
return globalBanDB.addGlobalIPBan(input.ip, input.reason).then(() => {
assert(insert.called, 'Expected insert to be called');
});
});
it('updates the ban reason for an existing ban', () => {
const input = { ip: '1.2.3.4', reason: 'spam' };
sinon.stub(mockTx, 'table').withArgs('global_bans').returns(mockTx);
const err = new Error();
err.code = 'ER_DUP_ENTRY';
const insert = sinon.stub(mockTx, 'insert').withArgs(input).rejects(err);
const where = sinon.stub(mockTx, 'where').withArgs({ ip: input.ip }).returns(mockTx);
const update = sinon.stub(mockTx, 'update').withArgs({ reason: input.reason }).resolves();
return globalBanDB.addGlobalIPBan(input.ip, input.reason).then(() => {
assert(insert.called, 'Expected insert to be called');
assert(where.called, 'Expected where({ ip }) to be called');
assert(update.called, 'Expected update({ reason }) to be called');
});
});
it('doesn\'t call update for a non-ER_DUP_ENTRY error', () => {
const input = { ip: '1.2.3.4', reason: 'spam' };
sinon.stub(mockTx, 'table').withArgs('global_bans').returns(mockTx);
const err = new Error('explosions');
const insert = sinon.stub(mockTx, 'insert').withArgs(input).rejects(err);
return globalBanDB.addGlobalIPBan(input.ip, input.reason).then(() => {
assert(false, 'Expected an error');
}).catch(error => {
assert.strictEqual(error, err, 'Expected error to match');
assert(insert.called, 'Expected insert to be called');
});
});
});
describe('#removeGlobalIPBan', () => {
it('removes a global ban', () => {
const ip = '1.2.3.4';
sinon.stub(mockTx, 'table').withArgs('global_bans').returns(mockTx);
const where = sinon.stub(mockTx, 'where').returns(mockTx);
const del = sinon.stub(mockTx, 'del').resolves();
return globalBanDB.removeGlobalIPBan(ip).then(() => {
assert(where.called, 'Expected where({ ip }) to be called');
assert(del.called, 'Expected del to be called');
});
});
});
});

View file

@ -1,16 +0,0 @@
const assert = require('assert');
const ffmpeg = require('../lib/ffmpeg');
const Config = require('../lib/config');
describe('ffmpeg', () => {
describe('#query', () => {
it('rejects plain http links', done => {
Config.set('ffmpeg.enabled', true);
ffmpeg.query('http://foo.bar/baz.mp4', err => {
assert(/begins with 'https:/.test(err),
`Expected error due to plain HTTP but got "${err}"`);
done();
});
});
});
});

View file

@ -1,53 +0,0 @@
const assert = require('assert');
const sinon = require('sinon');
const GlobalBanDB = require('../../lib/db/globalban').GlobalBanDB;
const CachingGlobalBanlist = require('../../lib/io/globalban').CachingGlobalBanlist;
describe('CachingGlobalBanlist', () => {
let banlist = null;
let banDB = null;
beforeEach(() => {
banDB = new GlobalBanDB();
banlist = new CachingGlobalBanlist(banDB);
});
describe('refreshCache', () => {
it('caches bans', () => {
const bans = [{ ip: '1.1.1.1', reason: 'test' }];
sinon.stub(banDB, 'listGlobalBans').resolves(bans);
return banlist.refreshCache().then(() => {
assert(banlist.cache.has(bans[0].ip), 'Cache was not populated');
});
});
it('clears removed bans', () => {
banlist.cache.add('1.1.1.1');
sinon.stub(banDB, 'listGlobalBans').resolves([]);
return banlist.refreshCache().then(() => {
assert(!banlist.cache.has('1.1.1.1'), 'Cache was not updated');
});
});
it('fails open', () => {
sinon.stub(banDB, 'listGlobalBans').rejects(new Error('Broken'));
return banlist.refreshCache();
});
});
describe('isIPGlobalBanned', () => {
it('checks the full IP', () => {
banlist.cache.add('1.2.3.4');
assert(banlist.isIPGlobalBanned('1.2.3.4'), 'Expected IP to be banned');
});
it('checks the range IP', () => {
banlist.cache.add('1.2.3');
assert(banlist.isIPGlobalBanned('1.2.3.4'), 'Expected IP to be banned');
});
it('checks the wrange IP', () => {
banlist.cache.add('1.2');
assert(banlist.isIPGlobalBanned('1.2.3.4'), 'Expected IP to be banned');
});
});
});

View file

@ -1,98 +0,0 @@
const assert = require('assert');
const IOServer = require('../../lib/io/ioserver').IOServer;
describe('IOServer', () => {
let server;
let socket;
beforeEach(() => {
server = new IOServer();
socket = {
context: {
ipAddress: '9.9.9.9'
},
handshake: {
address: '127.0.0.1',
headers: {
'x-forwarded-for': '1.2.3.4'
}
}
};
});
describe('#ipProxyMiddleware', () => {
it('proxies from a trusted address', done => {
server.ipProxyMiddleware(socket, error => {
assert(!error);
assert.strictEqual(socket.context.ipAddress, '1.2.3.4');
done();
});
});
it('does not proxy from a non-trusted address', done => {
socket.handshake.address = '5.6.7.8';
server.ipProxyMiddleware(socket, error => {
assert(!error);
assert.strictEqual(socket.context.ipAddress, '5.6.7.8');
done();
});
});
it('sets context.torConnection = true for Tor exits', () => {
// TODO
});
});
describe('#ipBanMiddleware', () => {
// TODO
});
describe('#ipThrottleMiddleware', () => {
it('throttles connections', done => {
let i = 0;
function callback(error) {
if (i < 5) {
assert(!error);
} else {
assert.strictEqual(error.message, 'Rate limit exceeded');
done();
}
}
function next() {
server.ipThrottleMiddleware(socket, error => {
callback(error);
if (++i < 6) next();
});
}
next();
});
});
describe('#cookieParsingMiddleware', () => {
it('parses cookies', done => {
socket.handshake.headers.cookie = 'flavor=chocolate%20chip';
server.cookieParsingMiddleware(socket, () => {
assert.strictEqual(socket.handshake.cookies.flavor, 'chocolate chip');
done();
});
});
it('defaults to empty objects if no cookies', done => {
server.cookieParsingMiddleware(socket, () => {
assert.deepStrictEqual(socket.handshake.cookies, {});
assert.deepStrictEqual(socket.handshake.signedCookies, {});
done();
});
});
});
describe('#ipSessionCookieMiddleware', () => {
// TODO
});
describe('#authUserMiddleware', () => {
// TODO
});
});

View file

@ -1,262 +0,0 @@
const assert = require('assert');
const { Poll } = require('../lib/poll');
describe('Poll', () => {
describe('constructor', () => {
it('constructs a poll', () => {
let poll = Poll.create(
'pollster',
'Which is better?',
[
'Coke',
'Pepsi'
]
/* default opts */
);
assert.strictEqual(poll.createdBy, 'pollster');
assert.strictEqual(poll.title, 'Which is better?');
assert.deepStrictEqual(poll.choices, ['Coke', 'Pepsi']);
assert.strictEqual(poll.hideVotes, false);
});
it('constructs a poll with hidden vote setting', () => {
let poll = Poll.create(
'pollster',
'Which is better?',
[
'Coke',
'Pepsi'
],
{ hideVotes: true }
);
assert.strictEqual(poll.hideVotes, true);
});
it('sanitizes title and choices', () => {
let poll = Poll.create(
'pollster',
'Which is better? <script></script>',
[
'<strong>Coke</strong>',
'Pepsi'
]
/* default opts */
);
assert.strictEqual(poll.title, 'Which is better? &lt;script&gt;&lt;/script&gt;');
assert.deepStrictEqual(poll.choices, ['&lt;strong&gt;Coke&lt;/strong&gt;', 'Pepsi']);
});
it('replaces URLs in title and choices', () => {
let poll = Poll.create(
'pollster',
'Which is better? https://example.com',
[
'Coke https://example.com',
'Pepsi'
]
/* default opts */
);
assert.strictEqual(
poll.title,
'Which is better? <a href="https://example.com" target="_blank" rel="noopener noreferer">https://example.com</a>'
);
assert.deepStrictEqual(
poll.choices,
[
'Coke <a href="https://example.com" target="_blank" rel="noopener noreferer">https://example.com</a>',
'Pepsi'
]
);
});
});
describe('#countVote', () => {
let poll;
beforeEach(() => {
poll = Poll.create(
'pollster',
'Which is better?',
[
'Coke',
'Pepsi'
]
/* default opts */
);
});
it('counts a new vote', () => {
assert.strictEqual(poll.countVote('userA', 0), true);
assert.strictEqual(poll.countVote('userB', 1), true);
assert.strictEqual(poll.countVote('userC', 0), true);
let { counts } = poll.toUpdateFrame();
assert.deepStrictEqual(counts, [2, 1]);
});
it('does not count a revote for the same choice', () => {
assert.strictEqual(poll.countVote('userA', 0), true);
assert.strictEqual(poll.countVote('userA', 0), false);
let { counts } = poll.toUpdateFrame();
assert.deepStrictEqual(counts, [1, 0]);
});
it('changes a vote to a different choice', () => {
assert.strictEqual(poll.countVote('userA', 0), true);
assert.strictEqual(poll.countVote('userA', 1), true);
let { counts } = poll.toUpdateFrame();
assert.deepStrictEqual(counts, [0, 1]);
});
it('ignores out of range votes', () => {
assert.strictEqual(poll.countVote('userA', 1000), false);
assert.strictEqual(poll.countVote('userA', -10), false);
let { counts } = poll.toUpdateFrame();
assert.deepStrictEqual(counts, [0, 0]);
});
});
describe('#uncountVote', () => {
let poll;
beforeEach(() => {
poll = Poll.create(
'pollster',
'Which is better?',
[
'Coke',
'Pepsi'
]
/* default opts */
);
});
it('uncounts an existing vote', () => {
assert.strictEqual(poll.countVote('userA', 0), true);
assert.strictEqual(poll.uncountVote('userA', 0), true);
let { counts } = poll.toUpdateFrame();
assert.deepStrictEqual(counts, [0, 0]);
});
it('does not uncount if there is no existing vote', () => {
assert.strictEqual(poll.uncountVote('userA', 0), false);
let { counts } = poll.toUpdateFrame();
assert.deepStrictEqual(counts, [0, 0]);
});
});
describe('#toUpdateFrame', () => {
let poll;
beforeEach(() => {
poll = Poll.create(
'pollster',
'Which is better?',
[
'Coke',
'Pepsi'
]
/* default opts */
);
poll.countVote('userA', 0);
poll.countVote('userB', 1);
poll.countVote('userC', 0);
});
it('generates an update frame', () => {
assert.deepStrictEqual(
poll.toUpdateFrame(),
{
title: 'Which is better?',
options: ['Coke', 'Pepsi'],
counts: [2, 1],
initiator: 'pollster',
timestamp: poll.createdAt.getTime()
}
);
});
it('hides votes when poll is hidden', () => {
poll.hideVotes = true;
assert.deepStrictEqual(
poll.toUpdateFrame(),
{
title: 'Which is better?',
options: ['Coke', 'Pepsi'],
counts: ['?', '?'],
initiator: 'pollster',
timestamp: poll.createdAt.getTime()
}
);
});
it('displays hidden votes when requested', () => {
poll.hideVotes = true;
assert.deepStrictEqual(
poll.toUpdateFrame(true),
{
title: 'Which is better?',
options: ['Coke', 'Pepsi'],
counts: ['2?', '1?'],
initiator: 'pollster',
timestamp: poll.createdAt.getTime()
}
);
});
});
describe('#toChannelData/fromChannelData', () => {
it('round trips a poll', () => {
let data = {
title: '&lt;strong&gt;ready?&lt;/strong&gt;',
initiator: 'aUser',
options: ['yes', 'no'],
counts: [0, 1],
votes:{
'1.2.3.4': null, // Previous poll code would set removed votes to null
'5.6.7.8': 1
},
obscured: false,
timestamp: 1483414981110
};
let poll = Poll.fromChannelData(data);
// New code does not store null votes
data.votes = { '5.6.7.8': 1 };
data.retainVotes = false;
assert.deepStrictEqual(poll.toChannelData(), data);
});
it('coerces a missing timestamp to the current time', () => {
let data = {
title: '&lt;strong&gt;ready?&lt;/strong&gt;',
initiator: 'aUser',
options: ['yes', 'no'],
counts: [0, 1],
votes:{
'1.2.3.4': null,
'5.6.7.8': 1
},
obscured: false
};
let now = Date.now();
let poll = Poll.fromChannelData(data);
const { timestamp } = poll.toChannelData();
if (typeof timestamp !== 'number' || isNaN(timestamp))
assert.fail(`Unexpected timestamp: ${timestamp}`);
if (Math.abs(timestamp - now) > 1000)
assert.fail(`Unexpected timestamp: ${timestamp}`);
});
});
});

View file

@ -1,65 +0,0 @@
const assert = require('assert');
const http = require('http');
const server = require('../lib/prometheus-server');
const PrometheusConfig = require('../lib/configuration/prometheusconfig').PrometheusConfig;
describe('prometheus-server', () => {
before(done => {
let inst = server.init(new PrometheusConfig({
prometheus: {
enabled: true,
port: 19820,
host: '127.0.0.1',
path: '/metrics'
}
}));
inst.once('listening', () => done());
});
function checkReq(options, done) {
const req = http.request({
method: options.method,
host: '127.0.0.1',
port: 19820,
path: options.path
}, res => {
assert.strictEqual(res.statusCode, options.expectedStatusCode);
assert.strictEqual(res.headers['content-type'], options.expectedContentType);
res.on('data', () => {});
res.on('end', () => done());
});
req.end();
}
it('rejects a non-GET request', done => {
checkReq({
method: 'POST',
path: '/metrics',
expectedStatusCode: 400,
expectedContentType: 'text/plain'
}, done);
});
it('rejects a request for the wrong path', done => {
checkReq({
method: 'GET',
path: '/qwerty',
expectedStatusCode: 400,
expectedContentType: 'text/plain'
}, done);
});
it('accepts a request for the configured path', done => {
checkReq({
method: 'GET',
path: '/metrics',
expectedStatusCode: 200,
expectedContentType: 'text/plain; version=0.0.4; charset=utf-8'
}, done);
});
after(() => {
server.shutdown();
});
});

View file

@ -1,52 +0,0 @@
const assert = require('assert');
const { RedisMessageBus } = require('../../lib/pubsub/redis');
const { EventEmitter } = require('events');
const sinon = require('sinon');
describe('RedisMessageBus', () => {
let pubClient, subClient, messageBus, publishSpy, subscribeSpy;
beforeEach(() => {
pubClient = { publish: () => {} };
subClient = new EventEmitter();
subClient.subscribe = () => {};
subscribeSpy = sinon.spy(subClient, 'subscribe');
publishSpy = sinon.spy(pubClient, 'publish');
messageBus = new RedisMessageBus(pubClient, subClient, 'test');
subClient.emit('ready');
});
describe('#onMessage', () => {
it('processes a valid message', done => {
messageBus.once('testEvent', payload => {
assert(subscribeSpy.withArgs('test').calledOnce);
assert.deepStrictEqual(payload, { foo: 'bar' });
done();
});
messageBus.onMessage('test', '{"event":"testEvent","payload":{"foo":"bar"}}');
});
it('processes a syntactically invalid message', done => {
messageBus.onMessage('test', 'not valid json lol');
done();
});
});
describe('#emit', () => {
it('emits messages', () => {
messageBus.emit('testEvent', { foo: 'bar' });
assert(publishSpy.withArgs('test', sinon.match(arg => {
arg = JSON.parse(arg);
return arg.event === 'testEvent' && arg.payload.foo === 'bar';
})).calledOnce);
});
});
});

View file

@ -1,34 +0,0 @@
const Promise = require('bluebird');
function MockDB(mockTx) {
this.mockTx = mockTx;
}
MockDB.prototype = {
runTransaction: function runTransaction(fn) {
return fn(this.mockTx);
}
};
function MockTx() {
}
[
'del',
'distinct',
'insert',
'limit',
'orderBy',
'select',
'table',
'update',
'where',
].forEach(method => {
MockTx.prototype[method] = function () {
return Promise.reject(new Error(`No stub defined for method "${method}"`));
};
});
exports.MockDB = MockDB;
exports.MockTx = MockTx;

View file

@ -1,18 +0,0 @@
const { hash } = require('../../lib/util/hash');
const assert = require('assert');
describe('hash', () => {
describe('#hash', () => {
const input = 'this is a test';
it('hashes input correctly', () => {
const sha256_hex = '2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c';
assert.strictEqual(hash('sha256', input, 'hex'), sha256_hex);
});
it('hashes input to base64', () => {
const sha256_base64 = 'Lpl1hUiXKo6IIq1H+hAX/3Lwbz/2oBaFH0XDmHMrxQw=';
assert.strictEqual(hash('sha256', input, 'base64'), sha256_base64);
});
});
});

View file

@ -1,66 +0,0 @@
const assert = require('assert');
const TokenBucket = require('../../lib/util/token-bucket').TokenBucket;
describe('TokenBucket', () => {
describe('#throttle', () => {
let bucket;
beforeEach(() => {
bucket = new TokenBucket(5, 5);
});
it('consumes capacity and then throttles', () => {
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(bucket.throttle(), 'should be empty now');
});
it('refills tokens', () => {
bucket.count = 0;
const oldRefill = bucket.lastRefill = Date.now() - 1000;
assert(!bucket.throttle(), 'should have refilled');
assert(bucket.lastRefill >= oldRefill + 1000, 'should have updated lastRefill');
});
it('refills at most {capacity} tokens', () => {
bucket.count = 0;
bucket.lastRefill = Date.now() - 10000;
bucket.throttle();
assert.strictEqual(bucket.count, 4);
});
it('does a partial refill', () => {
bucket.count = 0;
bucket.lastRefill = Date.now() - 400;
bucket.throttle();
assert.strictEqual(bucket.count, 1);
});
it('skips refilling if delta = 0', () => {
bucket.count = 0;
const oldRefill = bucket.lastRefill;
bucket.throttle();
assert.strictEqual(bucket.count, 0);
assert.strictEqual(bucket.lastRefill, oldRefill);
});
it('handles fractional refill rates', () => {
bucket = new TokenBucket(5, 0.1);
bucket.count = 0;
assert(bucket.throttle());
bucket.lastRefill = Date.now() - 10000;
assert(!bucket.throttle());
assert.strictEqual(bucket.count, 0);
});
it('handles infinite refill rate and capacity', () => {
bucket = new TokenBucket(Infinity, Infinity);
for (let i = 0; i < 100; i++) {
assert(!bucket.throttle(), 'should not throttle');
}
});
});
});

View file

@ -1,266 +0,0 @@
const assert = require('assert');
global.CyTube = {};
require('../../www/js/tabcomplete');
describe('CyTube.tabCompletionMethods', () => {
describe('"Longest unique match"', () => {
const testcases = [
{
input: 'and his name is j',
position: 17,
options: ['johncena', 'johnstamos', 'johto'],
output: {
text: 'and his name is joh',
newPosition: 19
},
description: 'completes the longest unique substring'
},
{
input: 'and his name is johnc',
position: 21,
options: ['johncena', 'johnstamos', 'johto'],
output: {
text: 'and his name is johncena ',
newPosition: 25
},
description: 'completes a unique match'
},
{
input: 'johnc',
position: 5,
options: ['johncena', 'johnstamos', 'johto'],
output: {
text: 'johncena ',
newPosition: 9
},
description: 'completes a unique match at the beginning of the string'
},
{
input: 'and his name is johnc',
position: 21,
options: ['asdf'],
output: {
text: 'and his name is johnc',
newPosition: 21
},
description: 'does not complete when there is no match'
},
{
input: 'and his name is ',
position: 16,
options: ['asdf'],
output: {
text: 'and his name is ',
newPosition: 16
},
description: 'does not complete when there is an empty prefix'
},
{
input: 'and his name is johnc',
position: 21,
options: [],
output: {
text: 'and his name is johnc',
newPosition: 21
},
description: 'does not complete when there are no options'
},
{
input: '',
position: 0,
options: ['abc', 'def', 'ghi'],
output: {
text: '',
newPosition: 0
},
description: 'does not complete when the input is empty'
}
];
testcases.forEach(test => {
it(test.description, () => {
assert.deepEqual(
CyTube.tabCompleteMethods['Longest unique match'](
test.input,
test.position,
test.options,
{}
),
test.output
);
});
});
});
describe('"Cycle options"', () => {
const testcases = [
{
input: 'hey c',
position: 5,
options: ['COBOL', 'Carlos', 'carl', 'john', 'joseph', ''],
outputs: [
{
text: 'hey carl ',
newPosition: 9
},
{
text: 'hey Carlos ',
newPosition: 11
},
{
text: 'hey COBOL ',
newPosition: 10
},
{
text: 'hey carl ',
newPosition: 9
}
],
description: 'cycles through options correctly'
},
{
input: 'c',
position: 1,
options: ['COBOL', 'Carlos', 'carl', 'john', 'joseph', ''],
outputs: [
{
text: 'carl ',
newPosition: 5
},
{
text: 'Carlos ',
newPosition: 7
},
{
text: 'COBOL ',
newPosition: 6
},
{
text: 'carl ',
newPosition: 5
}
],
description: 'cycles through options correctly at the beginning of the string'
},
{
input: 'hey ',
position: 5,
options: ['COBOL', 'Carlos', 'carl', 'john'],
outputs: [
{
text: 'hey ',
newPosition: 5
}
],
description: 'does not complete when there is an empty prefix'
},
{
input: 'hey c',
position: 6,
options: [],
outputs: [
{
text: 'hey c',
newPosition: 6
}
],
description: 'does not complete when there are no options'
},
{
input: '',
position: 0,
options: ['COBOL', 'Carlos', 'carl', 'john'],
outputs: [
{
text: '',
newPosition: 0
}
],
description: 'does not complete when the input is empty'
}
];
const complete = CyTube.tabCompleteMethods['Cycle options'];
testcases.forEach(test => {
it(test.description, () => {
var context = {};
var currentText = test.input;
var currentPosition = test.position;
for (var i = 0; i < test.outputs.length; i++) {
var output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(output, test.outputs[i]);
currentText = output.text;
currentPosition = output.newPosition;
}
});
});
it('updates the context when the input changes to reduce the # of matches', () => {
var test = testcases[0];
var context = {};
var currentText = test.input;
var currentPosition = test.position;
var output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(context, {
start: 4,
matches: ['carl', 'Carlos', 'COBOL'],
tabIndex: 0
});
currentText = output.text;
currentPosition = output.newPosition;
output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(context, {
start: 4,
matches: ['carl', 'Carlos', 'COBOL'],
tabIndex: 1
});
currentText = output.text.replace(context.matches[1], 'jo').trim();
currentPosition = 6;
output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(context, {
start: 4,
matches: ['john', 'joseph'],
tabIndex: 0
});
assert.deepEqual(output, {
text: 'hey john ',
newPosition: 9
});
});
it('clears the context when the input changes to a non-match', () => {
var test = testcases[0];
var context = {};
var currentText = test.input;
var currentPosition = test.position;
var output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(context, {
start: 4,
matches: ['carl', 'Carlos', 'COBOL'],
tabIndex: 0
});
currentText = output.text;
currentPosition = output.newPosition;
output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(context, {
start: 4,
matches: ['carl', 'Carlos', 'COBOL'],
tabIndex: 1
});
currentText = output.text.replace(context.matches[1], 'asdf').trim();
currentPosition = 8;
output = complete(currentText, currentPosition, test.options, context);
assert.deepEqual(context, {});
assert.deepEqual(output, {
text: 'hey asdf',
newPosition: 8
});
});
});
});

View file

@ -1,18 +0,0 @@
const assert = require('assert');
const XSS = require('../lib/xss');
describe('XSS', () => {
describe('sanitizeHTML', () => {
it('behaves consistently w.r.t. special chars used in emotes', () => {
const input = '`^~=| _-,;:!?/."()[]{}@$*\\&#%+á\t';
const expected = '`^~=| _-,;:!?/."()[]{}@$*\\&amp;#%+á\t';
assert.strictEqual(XSS.sanitizeHTML(input), expected);
});
it('disallows iframes', () => {
const input = '<iframe src="https://example.com"></iframe>';
const expected = '';
assert.strictEqual(XSS.sanitizeHTML(input), expected);
});
});
});