diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f11725..8df2d27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,10 @@ jobs: name: Installing dependencies command: npm install + - run: + name: Installing dependencies + command: npm install --save-dev + - run: name: installing additional dependencies command: npm install sqlite3 @@ -41,17 +45,9 @@ jobs: - run: name: Creating config file - command: echo {} >> config.json + command: echo {api:{}} >> config.json # run tests! - run: - name: Testing ./lib/music - command: node ./testscripts/musicTest.js - - - run: - name: Testing ./lib/cmd - command: node ./testscripts/cmdTest.js - - - run: - name: Testing ./lib/guilding - command: node ./testscripts/guildingTest.js \ No newline at end of file + name: Unit testing + command: npm test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7472e1c..00b2077 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .log .idea +.nyc_output data package-lock.json node_modules diff --git a/lib/utils.js b/lib/utils.js index db5f33b..b230a07 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -36,7 +36,7 @@ exports.objectDeepFind = function (object, attributePath) { let current = object, paths = attributePath.split('.'); for (let path of paths) { - if (current[path]) + if (current[path] !== undefined && current[path] !== null) current = current[path]; else return undefined; @@ -100,8 +100,8 @@ exports.YouTube = class { * @returns {boolean} */ static isValidUrl(url) { - return /https?:\/\/www.youtube.com\/(watch\?v=|playlist\?list=)/g.test(url) || - /https?:\/\/youtu.be\//g.test(url); + return /https?:\/\/(www\.)?youtube\.com\/(watch\?v=|playlist\?list=)/g.test(url) || + /https?:\/\/youtu\.be\//g.test(url); } /** @@ -110,8 +110,8 @@ exports.YouTube = class { * @returns {boolean} */ static isValidEntityUrl(url) { - return /https?:\/\/www.youtube.com\/(watch\?v=.+?|playlist\?list=.+?)/g.test(url) || - /https?:\/\/youtu.be\/.+?/g.test(url); + return /https?:\/\/(www\.)?youtube\.com\/(watch\?v=.+?|playlist\?list=.+?)/g.test(url) || + /https?:\/\/youtu\.be\/.+?/g.test(url); } /** @@ -120,7 +120,7 @@ exports.YouTube = class { * @returns {boolean} */ static isValidPlaylistUrl(url) { - return /https?:\/\/www.youtube.com\/playlist\?list=.+?/g.test(url); + return /https?:\/\/(www\.)?youtube\.com\/playlist\?list=.+?/g.test(url); } /** @@ -129,15 +129,17 @@ exports.YouTube = class { * @returns {boolean} */ static isValidVideoUrl(url) { - return /https?:\/\/www.youtube.com\/watch\?v=.+?/g.test(url) || /https?:\/\/youtu.be\/.+?/g.test(url); + return /https?:\/\/(www\.)?youtube\.com\/watch\?v=.+?/g.test(url) || /https?:\/\/youtu\.be\/.+?/g.test(url); } /** * Returns the id for a youtube video stripped from the url * @param url - * @returns {RegExpMatchArray} + * @returns {String} */ static getPlaylistIdFromUrl(url) { + if (!exports.YouTube.isValidPlaylistUrl(url)) + return null; let matches = url.match(/(?<=\?list=)[\w\-]+/); if (matches) return matches[0]; @@ -148,13 +150,21 @@ exports.YouTube = class { /** * Returns the id for a youtube video stripped from the url * @param url + * @return {String} */ static getVideoIdFromUrl(url) { - let matches = url.match(/(?<=\?v=)[\w\-]+/); - if (matches) - return matches[0]; - else + if (!exports.YouTube.isValidVideoUrl(url)) return null; + let matches1 = url.match(/(?<=\?v=)[\w\-]+/); + if (matches1) + return matches1[0]; + else { + let matches2 = url.match(/(?<=youtu\.be\/)[\w\-]+/); + if (matches2) + return matches2[0]; + else + return null; + } } /** @@ -177,9 +187,8 @@ exports.YouTube = class { exports.ConfigVerifyer = class { /** - * @param confFile {String} the file that needs to be verified + * @param confObj * @param required {Array} the attributes that are required for the bot to work - * @param optional {Array} the attributes that are optional (and may provide extra features) */ constructor(confObj, required) { this.config = confObj; @@ -192,12 +201,12 @@ exports.ConfigVerifyer = class { verifyConfig(logger) { let missing = []; for (let reqAttr of this.requiredAttributes) { - if (!exports.objectDeepFind(this.config, reqAttr)) + if (exports.objectDeepFind(this.config, reqAttr) === undefined) missing.push(reqAttr); } this.missingAttributes = missing; this.logMissing(logger); - return this.missingAttributes.length > 0; + return this.missingAttributes.length === 0; } /** @@ -206,10 +215,7 @@ exports.ConfigVerifyer = class { */ logMissing(logger) { if (this.missingAttributes.length > 0) { - logger.error("Missing required Attributes"); - for (let misAttr of this.missingAttributes) { - logger.warn(`Missing Attribute ${misAttr} in config.json`); - } + logger.error(`Missing required Attributes ${this.missingAttributes.join(', ')}`); } } }; \ No newline at end of file diff --git a/package.json b/package.json index f6b6d9a..898f8b9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "discordbot", "version": "1.0.0", "scripts": { - "start": "node bot.js" + "start": "node bot.js", + "test": "mocha", + "test-unit": "NODE_ENV=test mocha '/**/*.spec.js'" }, "dependencies": { "args-parser": "1.1.0", @@ -15,5 +17,12 @@ "winston-daily-rotate-file": "3.6.0", "youtube-playlist-info": "1.1.2", "ytdl-core": "0.29.1" + }, + "devDependencies": { + "assert": "^1.4.1", + "chai": "^4.2.0", + "mocha": "^5.2.0", + "nyc": "^13.1.0", + "sinon": "^7.2.3" } } diff --git a/testscripts/cmdTest.js b/test/cmdTest.js similarity index 100% rename from testscripts/cmdTest.js rename to test/cmdTest.js diff --git a/testscripts/guildingTest.js b/test/guildingTest.js similarity index 100% rename from testscripts/guildingTest.js rename to test/guildingTest.js diff --git a/testscripts/mockobjects.js b/test/mockobjects.js similarity index 94% rename from testscripts/mockobjects.js rename to test/mockobjects.js index 86e7ed0..0316b74 100644 --- a/testscripts/mockobjects.js +++ b/test/mockobjects.js @@ -1,7 +1,8 @@ exports.mockLogger = { - error: msg => raise(msg), + error: msg => { + throw new Error(msg); + }, warn: msg => console.error("warn: ", msg), - warning: msg => console.error("warn: ", msg), info: msg => console.log("info: ", msg), verbose: msg => console.log("verbose: ", msg), debug: msg => console.log("debug: ", msg) @@ -48,4 +49,4 @@ exports.mockVoicechannel = { exports.mockChannel = { send: (msg) => console.log('Send: ', msg) -} \ No newline at end of file +}; \ No newline at end of file diff --git a/test/test-setup.spec.js b/test/test-setup.spec.js new file mode 100644 index 0000000..f7c976a --- /dev/null +++ b/test/test-setup.spec.js @@ -0,0 +1,10 @@ +const sinon = require('sinon'), + chai = require('chai'); + +beforeEach(() => { + this.sandbox = sinon.createSandbox(); +}); + +afterEach(() => { + this.sandbox.restore(); +}); \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..76127c5 --- /dev/null +++ b/test/test.js @@ -0,0 +1,190 @@ +const mockobjects = require('./mockobjects.js'), + sinon = require('sinon'); +let Discord = require("discord.js"), + assert = require('assert'), + config = require('../config.json'); + +describe('lib/utils', function() { + const utils = require('../lib/utils.js'); + + describe('#YouTube', function() { + + it('returns if an url is valid', function(done) { + assert(utils.YouTube.isValidUrl('https://www.youtube.com/watch?v=VID-ID')); + assert(utils.YouTube.isValidUrl('https://youtube.com/playlist?list=PL-ID')); + assert(utils.YouTube.isValidUrl('https://youtube.com/watch?v=')); + assert(utils.YouTube.isValidUrl('https://www.youtube.com/playlist?list=')); + assert(utils.YouTube.isValidUrl('https://youtu.be/VIDID')); + assert(utils.YouTube.isValidUrl('https://youtu.be/')); + assert(utils.YouTube.isValidUrl('http://youtube.com/watch?v=VID-ID')); + assert(utils.YouTube.isValidUrl('http://youtube.com/playlist?list=PL-ID')); + assert(utils.YouTube.isValidUrl('http://youtube.com/watch?v=')); + assert(utils.YouTube.isValidUrl('http://youtube.com/playlist?list=')); + assert(!utils.YouTube.isValidUrl('https://github.com')); + assert(!utils.YouTube.isValidUrl('notevenanurl')); + done(); + }); + + it('returns if an url is a valid entity url', function(done) { + assert(utils.YouTube.isValidEntityUrl('https://youtube.com/watch?v=VID-ID')); + assert(utils.YouTube.isValidEntityUrl('https://youtube.com/playlist?list=PL-ID')); + assert(utils.YouTube.isValidEntityUrl('https://youtu.be/VIDID')); + assert(utils.YouTube.isValidEntityUrl('http://www.youtube.com/watch?v=VID-ID')); + assert(utils.YouTube.isValidEntityUrl('http://youtube.com/playlist?list=PL-ID')); + assert(!utils.YouTube.isValidEntityUrl('https://youtube.com/watch?v=')); + assert(!utils.YouTube.isValidEntityUrl('https://youtube.com/playlist?list=')); + assert(!utils.YouTube.isValidEntityUrl('https://youtu.be/')); + assert(!utils.YouTube.isValidEntityUrl('https://github.com')); + assert(!utils.YouTube.isValidEntityUrl('notevenanurl')); + done(); + }); + + it('returns if an url is a valid playlist url', function(done) { + assert(!utils.YouTube.isValidPlaylistUrl('https://youtube.com/watch?v=VID-ID')); + assert(utils.YouTube.isValidPlaylistUrl('https://youtube.com/playlist?list=PL-ID')); + assert(!utils.YouTube.isValidPlaylistUrl('https://youtu.be/VIDID')); + assert(!utils.YouTube.isValidPlaylistUrl('http://www.youtube.com/watch?v=VID-ID')); + assert(utils.YouTube.isValidPlaylistUrl('http://youtube.com/playlist?list=PL-ID')); + assert(!utils.YouTube.isValidPlaylistUrl('http://youtube.com/playlist?list=')); + assert(!utils.YouTube.isValidPlaylistUrl('https://github.com')); + assert(!utils.YouTube.isValidPlaylistUrl('notevenanurl')); + done(); + }); + + it('returns if an url is a valid video url', function(done) { + assert(utils.YouTube.isValidVideoUrl('https://youtube.com/watch?v=VID-ID')); + assert(!utils.YouTube.isValidVideoUrl('https://youtube.com/playlist?list=PL-ID')); + assert(utils.YouTube.isValidVideoUrl('https://youtu.be/VIDID')); + assert(utils.YouTube.isValidVideoUrl('http://www.youtube.com/watch?v=VID-ID')); + assert(!utils.YouTube.isValidVideoUrl('http://youtube.com/playlist?list=PL-ID')); + assert(!utils.YouTube.isValidVideoUrl('https://youtube.com/watch?v=')); + assert(!utils.YouTube.isValidVideoUrl('https://youtu.be/')); + assert(!utils.YouTube.isValidVideoUrl('https://github.com')); + assert(!utils.YouTube.isValidVideoUrl('notevenanurl')); + done(); + }); + + it('returns the id for a playlist url', function(done) { + let getPlId = utils.YouTube.getPlaylistIdFromUrl; + assert('PL-ID' === getPlId('https://youtube.com/playlist?list=PL-ID')); + assert('PL-ID' === getPlId('http://youtube.com/playlist?list=PL-ID')); + assert('PL-ID' === getPlId('https://www.youtube.com/playlist?list=PL-ID')); + assert('PL-ID' === getPlId('https://www.youtube.com/playlist?list=PL-ID')); + assert(null === getPlId('https://www.youtube.com/playlist?list=')); + done(); + }); + + it('returns the id for a video url', function(done) { + let getVidId = utils.YouTube.getVideoIdFromUrl; + assert('VID-ID' === getVidId('https://youtube.com/watch?v=VID-ID')); + assert('VID-ID' === getVidId('http://youtube.com/watch?v=VID-ID')); + assert('VID-ID' === getVidId('https://www.youtube.com/watch?v=VID-ID')); + assert('VID-ID' === getVidId('https://youtu.be/VID-ID')); + assert(null === getVidId('https://www.faketube.com/watch?v=VID-ID')); + assert(null === getVidId('tu.be/VID-ID')); + assert(null === getVidId('https://youtube.com/watch?v=')); + assert(null === getVidId('https://youtu.be/')); + done(); + }); + + it('returns the video url for an id', function(done) { + let getVid4Id = utils.YouTube.getVideoUrlFromId; + assert('https://www.youtube.com/watch?v=VID-ID', getVid4Id('VID-ID')); + assert('https://www.youtube.com/watch?v=12345567885432', getVid4Id('12345567885432')); + done(); + }); + + it('returns the thumbnail url for a video url', function(done) { + let getVid4Id = utils.YouTube.getVideoUrlFromId; + let getTh4Id = utils.YouTube.getVideoThumbnailUrlFromUrl; + assert('https://i3.ytimg.com/vi/VIDID/maxresdefault.jpg', getTh4Id(getVid4Id('VIDID'))); + assert('https://i3.ytimg.com/vi/1234/maxresdefault.jpg', getTh4Id(getVid4Id('1234'))); + done(); + }) + }); + + describe('#ConfigVerifyer', function() { + it('verifies correct configs', function(done) { + const testObj = { + 'key1': { + 'key2': 'value2', + 'key3': 'value3' + }, + 'key4': [], + 'key5': false, + 'key6': 'a longer string', + 'key7': { + 'key8': [{ + 'key9': 'okay...' + }] + } + }; + let confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key3']); + assert(confVer.verifyConfig(mockobjects.mockLogger)); + confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key2', 'key7.key8.0.key9']); + assert(confVer.verifyConfig(mockobjects.mockLogger)); + confVer = new utils.ConfigVerifyer(testObj, ['key4', 'key1.key2', 'key5', 'key7']); + assert(confVer.verifyConfig(mockobjects.mockLogger)); + done(); + }); + + it('rejects invalid configs', function(done) { + const testObj = { + }; + let modifiedMockLogger = mockobjects.mockLogger; + modifiedMockLogger.error = (msg) => {}; + let confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key3']); + assert(!confVer.verifyConfig(mockobjects.mockLogger)); + confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key2', 'key7.key8.0.key9']); + assert(!confVer.verifyConfig(mockobjects.mockLogger)); + done(); + }) + }); +}); + +// TODO: Repair and activate later +describe('The dj class', function *() { + const music = require('../lib/music'); + let ytdl = require("ytdl-core"); + let yttl = require('get-youtube-title'); + let ypi = require('youtube-playlist-info'); + + let ytdlMock = sinon.mock(ytdl); + let yttlMock = sinon.mock(yttl); + let ypiMock = sinon.mock(ypi); + + it('connects to a VoiceChannel', function () { + let dj = new music.DJ(mockobjects.mockVoicechannel); + dj.connect(); + + console.log(dj.connected); + + assert(dj.connected); + }); + + it('listens on Repeat', function() { + let dj = new music.DJ(mockobjects.mockVoicechannel); + dj.current = {'url': '', 'title': ''}; + dj.listenOnRepeat = true; + + assert(dj.repeat); + assert(dj.queue.length > 0); + }); + + it('plays Files', function () { + + let dj = new music.DJ(mockobjects.mockVoicechannel); + dj.connect(); + dj.playFile(); + + assert(dj.playing); + }); + + it('plays YouTube urls', function () { + + let dj = new music.DJ(mockobjects.mockVoicechannel); + dj.playYouTube('http://www.youtube.com/watch?v=abc'); + + assert(dj.playing); + }); +}); \ No newline at end of file diff --git a/testscripts/musicTest.js b/testscripts/musicTest.js deleted file mode 100644 index 1c90aab..0000000 --- a/testscripts/musicTest.js +++ /dev/null @@ -1,33 +0,0 @@ -const music = require('../lib/music.js'), - mockobjects = require('./mockobjects.js'); - -function main() { - let dj = new music.DJ(mockobjects.mockVoicechannel) - music.setLogger(mockobjects.mockLogger); - dj.connect().then(() => { - console.log('connected', dj.connected); - dj.playFile('test'); - dj.playYouTube('https://www.youtube.com/watch?v=TEST'); - dj.setVolume(1); - dj.pause(); - dj.resume(); - dj.skip(); - dj.stop(); - dj.shuffle(); - console.log('dj.playlist: ', dj.playlist); - console.log('dj.song: ', dj.song); - dj.clear(); - process.exit(0); - }); -} - -// Executing the main function -if (typeof require !== 'undefined' && require.main === module) { - process.on('unhandledRejection', (reason, p) => { - console.error('Unhandled Rejection at: Promise', p, 'reason:', reason); - throw Error('Promise rejection'); - }); - - setTimeout(() => process.exit(1), 60000); - main(); -} \ No newline at end of file