diff --git a/package.json b/package.json index 325ed674464fa30296eb35b0cdc11b673875047d..7828beb92098cf513a0e608407411ceeb0650527 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "description": "A bridge between Matrix and Discord", "main": "discordas.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "npm run-script build && mocha --opts test/mocha.opts build/test", "lint": "tslint --project ./tsconfig.json", - "coverage": "istanbul --include-all-sources cover -x /src/discordas.js _mocha -- test/ -R spec", - "build": "tsc", - "start": "tsc && node ./build/discordas.js -p 9005 -c config.yaml", + "coverage": "istanbul --include-all-sources cover -x build/src/discordas.js _mocha -- build/test/ -R spec", + "build": "rm -r build/* && tsc", + "start": "build && node ./build/discordas.js -p 9005 -c config.yaml", "getbotlink": "node ./tools/addbot.js" }, "repository": { @@ -34,17 +34,20 @@ "discord.js": "^11.0.0", "js-yaml": "^3.8.1", "marked": "^0.3.6", + "matrix-appservice-bridge": "^1.3.5", "mime": "^1.3.4", "npmlog": "^4.0.2", "tslint": "^4.4.2", - "typescript": "^2.1.6", - "matrix-appservice-bridge": "^1.3.5" + "typescript": "^2.1.6" }, "devDependencies": { + "@types/chai": "^3.4.35", + "@types/mocha": "^2.2.39", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "eslint": "^3.8.1", "istanbul": "^0.4.5", - "mocha": "^3.1.2" + "mocha": "^3.1.2", + "proxyquire": "^1.7.11" } } diff --git a/src/discordbot.ts b/src/discordbot.ts index 0bbf5f12107950c5cae47b3effe9adedfe13080e..28712e5812ffa0821a45ea009f0a99caa0405281 100644 --- a/src/discordbot.ts +++ b/src/discordbot.ts @@ -3,6 +3,7 @@ import * as Discord from "discord.js"; import * as log from "npmlog"; import { MatrixUser, RemoteUser } from "matrix-appservice-bridge"; import { Util } from "./util"; +import * as Bluebird from "bluebird"; import * as mime from "mime"; import * as marked from "marked"; @@ -16,20 +17,18 @@ export class DiscordBot { this.bridge = bridge; } - public run () { - this.bot = new Discord.Client(); - - this.bot.on("ready", () => { - log.info("DiscordBot", "I am ready!"); - }); - + public run (): Promise<null> { + this.bot = Bluebird.promisifyAll(new Discord.Client()); this.bot.on("typingStart", (c, u) => { this.OnTyping(c, u, true); }); this.bot.on("typingStop", (c, u) => { this.OnTyping(c, u, false); }); this.bot.on("userUpdate", (_, newUser) => { this.UpdateUser(newUser); }); this.bot.on("channelUpdate", (_, newChannel) => { this.UpdateRoom(<Discord.TextChannel> newChannel); }); this.bot.on("presenceUpdate", (_, newMember) => { this.UpdatePresence(newMember); }); this.bot.on("message", this.OnMessage.bind(this)); + const promise = (this.bot as any).onAsync("ready"); this.bot.login(this.config.auth.botToken); + + return promise; } public GetBot (): Discord.Client { @@ -40,14 +39,15 @@ export class DiscordBot { const guild = this.bot.guilds.find((g) => { return (g.id === server || g.name.toLowerCase().replace(/ /g, "-") === server.toLowerCase()); }); - if (guild === null) { + if (!guild) { return Promise.reject(`Guild "${server}" not found`); } const channel = guild.channels.find((c) => { return ((c.id === room || c.name.toLowerCase() === room.toLowerCase() ) && c.type === "text"); }); - if (channel === null) { + + if (!channel) { return Promise.reject(`Channel "${room}" not found`); } return Promise.resolve(channel); @@ -205,7 +205,7 @@ export class DiscordBot { } private OnTyping(channel: Discord.Channel, user: Discord.User, isTyping: boolean) { - this.GetRoomIdFromChannel(channel).then((room) => { + return this.GetRoomIdFromChannel(channel).then((room) => { const intent = this.bridge.getIntentFromLocalpart(`_discord_${user.id}`); intent.sendTyping(room, isTyping); }); diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts new file mode 100644 index 0000000000000000000000000000000000000000..a1a683fdeb54e3f2d4ed274d0af0f891af0b85c4 --- /dev/null +++ b/test/test_discordbot.ts @@ -0,0 +1,187 @@ +import * as Chai from "chai"; +import * as ChaiAsPromised from "chai-as-promised"; +import * as Proxyquire from "proxyquire"; +import { DiscordBridgeConfig } from "../src/config"; + +Chai.use(ChaiAsPromised); + +const assert = Chai.assert; +const should = Chai.should as any; +class DiscordClient { + public guilds: any; + private testLoggedIn: boolean = false; + private testCallbacks: Array<() => void> = []; + constructor() { + let channels = [ + { + id: "321", + name: "achannel", + type: "text", + }, { + id: "654", + name: "a-channel", + type: "text", + }, { + id: "987", + name: "a channel", + type: "text", + }, + ]; + let guilds = [ + { + id: "123", + name: "MyGuild", + channels, + }, + { + id: "456", + name: "My Spaces Guild", + channels, + }, + { + id: "789", + name: "My Dash-Guild", + channels, + }, + ]; + this.guilds = guilds; + + } + + public on(event: string, callback: () => void) { + if (event === "ready") { + this.testCallbacks[0] = callback; + } + } + + public login(token: string) { + this.testLoggedIn = true; + this.testCallbacks[0](); + } +} + +const mockDiscord = { + Client: DiscordClient, +}; + +const mockBridge = { + getRoomStore: () => { + return { + getEntriesByRemoteRoomData: (data) => { + if (data.discord_channel === "321") { + return Promise.resolve([{ + matrix: { + getId: () => {return "foobar:example.com"; }, + }, + }]); + } + return Promise.resolve([]); + }, + }; + }, + getIntentFromLocalpart: (localpart: string) => { + return{ + sendTyping: (room: string, isTyping: boolean) => { + return; + }, + }; + }, +}; + +const modDiscordBot = Proxyquire("../src/discordbot", { + "discord.js": mockDiscord, +}); + +describe("DiscordBot", () => { + const config = { + auth: { + botToken: "blah", + }, + }; + describe("run()", () => { + it("should start ok.", () => { + const discordBot = new modDiscordBot.DiscordBot( + config, + mockBridge, + ); + assert.doesNotThrow(discordBot.run.bind(discordBot)); + }); + it("should resolve when ready.", () => { + const discordBot = new modDiscordBot.DiscordBot( + config, + mockBridge, + ); + return discordBot.run(); + }); + }); + describe("LookupRoom()", () => { + const discordBot = new modDiscordBot.DiscordBot( + config, + mockBridge, + ); + discordBot.run(); + it("should reject a missing guild.", () => { + return assert.isRejected(discordBot.LookupRoom("MyMissingGuild", "achannel")); + }); + + it("should resolve a guild.", () => { + return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "achannel")); + }); + + it("should resolve a guild with an id.", () => { + return assert.isFulfilled(discordBot.LookupRoom("123", "achannel")); + }); + + it("should resolve a guild with spaces.", () => { + return assert.isFulfilled(discordBot.LookupRoom("My-Spaces-Guild", "achannel")); + }); + + it("should resolve a guild with dashes.", () => { + return assert.isFulfilled(discordBot.LookupRoom("My-Dash-Guild", "achannel")); + }); + + it("should reject a missing channel.", () => { + return assert.isRejected(discordBot.LookupRoom("MyGuild", "amissingchannel")); + }); + + it("should resolve a channel with spaces.", () => { + return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "a channel")); + }); + + it("should resolve a channel with dashes.", () => { + return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "a-channel")); + }); + + it("should resolve a channel with an id.", () => { + return assert.isFulfilled(discordBot.LookupRoom("MyGuild", "321")); + }); + }); + describe("ProcessMatrixMsgEvent()", () => { + + }); + describe("UpdateRoom()", () => { + + }); + describe("UpdateUser()", () => { + + }); + describe("UpdatePresence()", () => { + + }); + describe("OnTyping()", () => { + const discordBot = new modDiscordBot.DiscordBot( + config, + mockBridge, + ); + discordBot.run(); + it("should reject an unknown room.", () => { + return assert.isRejected(discordBot.OnTyping( {id: "512"} {id: "12345"}, true)); + }); + it("should resolve a known room.", () => { + return assert.isFulfilled(discordBot.OnTyping( {id: "321"} {id: "12345"}, true)); + }); + }); + describe("OnMessage()", () => { + + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b51be7ecfed92da985567e893e71390904ef1347..c5651a0591da08bfac5cd8395e06276590a4c28c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,10 +5,12 @@ "target": "ES6", "noImplicitAny": false, "sourceMap": false, - "outDir": "./build" + "outDir": "./build", + "types": ["mocha", "node"] }, "compileOnSave": true, "include": [ - "src/**/*" + "src/**/*", + "test/**/*" ] }