diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts
index 13940185d67467539f9e0098c21094ae30dd93e2..c9fe11f3980fb7a71099fe72686c99f6c3d24f2c 100644
--- a/src/matrixroomhandler.ts
+++ b/src/matrixroomhandler.ts
@@ -67,7 +67,7 @@ export class MatrixRoomHandler {
     /* We delay the joins to give some implementations a chance to breathe */
     let delay = this.config.limits.roomGhostJoinDelay;
     return this.discord.GetChannelFromRoomId(roomId).then((channel: Discord.Channel) => {
-      for (const member of (<Discord.TextChannel> channel).guild.members.array()) {
+      for (const member of (<Discord.TextChannel> channel).members.array()) {
         if (member.id === this.discord.GetBotId()) {
           continue;
         }
@@ -78,19 +78,20 @@ export class MatrixRoomHandler {
       }
     }).catch((err) => {
       log.verbose("OnAliasQueried => %s", err);
+      throw err;
     });
   }
 
-  public OnEvent (request, context) {
+  public OnEvent (request, context): Promise<any> {
     const event = request.getData();
     if (event.unsigned.age > AGE_LIMIT) {
       log.warn("MatrixRoomHandler", "Skipping event due to age %s > %s", event.unsigned.age, AGE_LIMIT);
-      return;
+      return Promise.reject("Event too old");
     }
     if (event.type === "m.room.member" && event.content.membership === "invite") {
-      this.HandleInvite(event);
+      return this.HandleInvite(event);
     } else if (event.type === "m.room.redaction" && context.rooms.remote) {
-      this.discord.ProcessMatrixRedact(event);
+      return this.discord.ProcessMatrixRedact(event);
     } else if (event.type === "m.room.message") {
       log.verbose("MatrixRoomHandler", "Got m.room.message event");
       if (event.content.body && event.content.body.startsWith("!discord")) {
@@ -104,6 +105,7 @@ export class MatrixRoomHandler {
     } else {
       log.verbose("MatrixRoomHandler", "Got non m.room.message event");
     }
+    return Promise.reject("Event not processed by bridge");
   }
 
   public HandleInvite(event: any) {
@@ -160,7 +162,7 @@ export class MatrixRoomHandler {
 
       if (command === "help" && args[0] === "bridge") {
           const link = Util.GetBotLink(this.config);
-          this.bridge.getIntent().sendMessage(event.room_id, {
+          return this.bridge.getIntent().sendMessage(event.room_id, {
               msgtype: "m.notice",
               body: "How to bridge a Discord guild:\n" +
               "1. Invite the bot to your Discord guild using this link: " + link + "\n" +
@@ -256,7 +258,7 @@ export class MatrixRoomHandler {
           }
       } else if (command === "help") {
           // Unknown command or no command given to get help on, so we'll just give them the help
-          this.bridge.getIntent().sendMessage(event.room_id, {
+          return this.bridge.getIntent().sendMessage(event.room_id, {
               msgtype: "m.notice",
               body: "Available commands:\n" +
               "!discord bridge <guild id> <channel id>   - Bridges this room to a Discord channel\n" +
diff --git a/test/test_matrixroomhandler.ts b/test/test_matrixroomhandler.ts
index a47fb07588818e9ade624b48a8c454381c23973c..142becea3ba0263d100b09cd1e8f665b50b91574 100644
--- a/test/test_matrixroomhandler.ts
+++ b/test/test_matrixroomhandler.ts
@@ -9,6 +9,7 @@ import {DiscordBot} from "../src/bot";
 import {MatrixRoomHandler} from "../src/matrixroomhandler";
 import {MockChannel} from "./mocks/channel";
 import {MockMember} from "./mocks/member";
+import * as Bluebird from "bluebird";
 
 Chai.use(ChaiAsPromised);
 const expect = Chai.expect;
@@ -19,6 +20,15 @@ const expect = Chai.expect;
 
 let USERSJOINED = 0;
 
+function buildRequest(eventData) {
+    if (eventData.unsigned === undefined) {
+        eventData.unsigned = {age: 0};
+    }
+    return {
+        getData: () => eventData,
+    };
+}
+
 function createRH(opts: any = {}) {
     USERSJOINED = 0;
     const bot = {
@@ -44,32 +54,219 @@ function createRH(opts: any = {}) {
 
         },
         GetBotId: () => "bot12345",
+        ProcessMatrixRedact: () => Promise.resolve("redacted"),
+        ProcessMatrixMsgEvent: () => Promise.resolve("processed"),
     };
     const config = new DiscordBridgeConfig();
     config.limits.roomGhostJoinDelay = 0;
+    if (opts.disableSS) {
+        config.bridge.enableSelfServiceBridging = false;
+    } else {
+        config.bridge.enableSelfServiceBridging = true;
+    }
+    const mxClient = {
+        getStateEvent: () => {
+            return Promise.resolve(opts.powerLevels || {});
+        },
+    };
     const provisioner = null;
-    return new MatrixRoomHandler(bot as any, config, "@botuser:localhost", provisioner);
+    const handler = new MatrixRoomHandler(bot as any, config, "@botuser:localhost", provisioner);
+    handler.setBridge({
+        getIntent: () => { return {
+            sendMessage: (roomId, content) => Promise.resolve(content),
+            getClient: () => mxClient,
+        }; },
+    });
+    return handler;
 }
 
 describe("MatrixRoomHandler", () => {
     describe("OnAliasQueried", () => {
         it("should join successfully", () => {
             const handler = createRH();
-            return handler.OnAliasQueried("#accept:localhost", "!accept:localhost")
-                .then(() => {
-                    // test for something
-                    return true;
-            });
+            return expect(handler.OnAliasQueried("#accept:localhost", "!accept:localhost")).to.be.fulfilled;
         });
         it("should join successfully and create ghosts", () => {
             const EXPECTEDUSERS = 2;
+            const TESTDELAY = 50;
             const handler = createRH({createMembers: true});
-            return handler.OnAliasQueried("#accept:localhost", "!accept:localhost")
-                .then(() => {
+            return  handler.OnAliasQueried("#accept:localhost", "!accept:localhost").then(() => {
+                return Bluebird.delay(TESTDELAY);
+            }).then(() => {
                     expect(USERSJOINED).to.equal(EXPECTEDUSERS);
                     // test for something
                     return true;
-                });
+            });
+        });
+        it("should not join successfully", () => {
+            const handler = createRH();
+            return expect(handler.OnAliasQueried("#reject:localhost", "!reject:localhost")).to.be.rejected;
+        });
+    });
+    describe("OnEvent", () => {
+        it("should reject old events", () => {
+            const AGE = 900001; // 15 * 60 * 1000
+            const handler = createRH();
+            return expect(handler.OnEvent(
+                buildRequest({unsigned: {age: AGE}}), null))
+                .to.be.rejectedWith("Event too old");
+        });
+        it("should reject un-processable events", () => {
+            const AGE = 900000; // 15 * 60 * 1000
+            const handler = createRH();
+            return expect(handler.OnEvent(buildRequest({
+                content: {},
+                type: "m.potato",
+                unsigned: {age: AGE}}), null)).to.be.rejectedWith("Event not processed by bridge");
+        });
+        it("should handle invites", () => {
+            const handler = createRH();
+            handler.HandleInvite = (ev) => Promise.resolve("invited");
+            return expect(handler.OnEvent(buildRequest({
+                content: {membership: "invite"},
+                type: "m.room.member"}), null)).to.eventually.equal("invited");
+        });
+        it("should ignore other member types", () => {
+            const handler = createRH();
+            handler.HandleInvite = (ev) => Promise.resolve("invited");
+            return expect(handler.OnEvent(buildRequest({
+                content: {membership: "join"},
+                type: "m.room.member"}), null)).to.be.rejectedWith("Event not processed by bridge");
+        });
+        it("should handle redactions with existing rooms", () => {
+            const handler = createRH();
+            const context = {
+                rooms: {
+                    remote: true,
+                },
+            };
+            return expect(handler.OnEvent(buildRequest({
+                type: "m.room.redaction"}), context)).to.eventually.equal("redacted");
+        });
+        it("should ignore redactions with no linked room", () => {
+            const handler = createRH();
+            const context = {
+                rooms: {
+                    remote: null,
+                },
+            };
+            return expect(handler.OnEvent(buildRequest({
+                type: "m.room.redaction"}), context)).to.be.rejectedWith("Event not processed by bridge");
+        });
+        it("should process regular messages", () => {
+            const handler = createRH();
+            const context = {
+                rooms: {
+                    remote: {
+                        roomId: "_discord_123_456",
+                    },
+                },
+            };
+            return expect(handler.OnEvent(buildRequest({
+                type: "m.room.message", content: {body: "abc"}}), context)).to.eventually.equal("processed");
+        });
+        it("should process !discord commands", () => {
+            const handler = createRH();
+            handler.ProcessCommand = (ev) => Promise.resolve("processedcmd");
+            return expect(handler.OnEvent(buildRequest({
+                type: "m.room.message", content: {body: "!discord cmd"}}), null))
+                .to.eventually.equal("processedcmd");
+        });
+        it("should ignore regular messages with no linked room", () => {
+            const handler = createRH();
+            const context = {
+                rooms: {
+                    remote: null,
+                },
+            };
+            return expect(handler.OnEvent(buildRequest({
+                type: "m.room.message", content: {body: "abc"}}), context))
+                .to.be.rejectedWith("Event not processed by bridge");
+        });
+    });
+    describe("HandleInvite", () => {
+        it("should accept invite for bot user", () => {
+            const handler: any = createRH();
+            handler.joinRoom = () => Promise.resolve("joinedroom");
+            return expect(handler.HandleInvite({
+                state_key: "@botuser:localhost",
+            })).to.eventually.be.equal("joinedroom");
+        });
+        it("should deny invite for other users", () => {
+            const handler: any = createRH();
+            handler.joinRoom = () => Promise.resolve("joinedroom");
+            return expect(handler.HandleInvite({
+                state_key: "@user:localhost",
+            })).to.be.undefined;
+        });
+    });
+    describe("ProcessCommand", () => {
+        it("should warn if self service is disabled", () => {
+            const handler: any = createRH({disableSS: true});
+            return expect(handler.ProcessCommand({
+                room_id: "!123:localhost",
+            })).to.eventually.be.deep.equal({
+                msgtype: "m.notice",
+                body: "The owner of this bridge does not permit self-service bridging.",
+            });
+        });
+        it("should warn if user is not powerful enough with defaults", () => {
+            const handler: any = createRH();
+            return expect(handler.ProcessCommand({
+                room_id: "!123:localhost",
+            })).to.eventually.be.deep.equal({
+                msgtype: "m.notice",
+                body: "You do not have the required power level in this room to create a bridge to a Discord channel.",
+            });
+        });
+        it("should warn if user is not powerful enough with custom state default", () => {
+            const handler: any = createRH({powerLevels: {
+                state_default: 67,
+            }});
+            return expect(handler.ProcessCommand({
+                room_id: "!123:localhost",
+            })).to.eventually.be.deep.equal({
+                msgtype: "m.notice",
+                body: "You do not have the required power level in this room to create a bridge to a Discord channel.",
+            });
+        });
+        it("should allow if user is powerful enough with defaults", () => {
+            const handler: any = createRH({powerLevels: {
+                    users_default: 60,
+                }});
+            return handler.ProcessCommand({
+                room_id: "!123:localhost",
+                content: {body: "!discord help"},
+            }).then((evt) => {
+                console.log(evt);
+                return expect(evt.body.startsWith("Available commands")).to.be.true;
+            });
+        });
+        it("should allow if user is powerful enough with their own state", () => {
+            const handler: any = createRH({powerLevels: {
+                    users: {
+                     "@user:localhost": 100,
+                    },
+                }});
+            return handler.ProcessCommand({
+                room_id: "!123:localhost",
+                sender: "@user:localhost",
+                content: {body: "!discord help"},
+            }).then((evt) => {
+                return expect(evt.body.startsWith("Available commands")).to.be.true;
+            });
+        });
+        it("will not bridge if a link already exists", () => {
+            const handler: any = createRH({powerLevels: {
+                    users_default: 100,
+                }});
+            const context = {rooms: { remote: true }};
+            return handler.ProcessCommand({
+                room_id: "!123:localhost",
+                content: {body: "!discord bridge"},
+            }, context).then((evt) => {
+                return expect(evt.body.startsWith("This room is already bridged to a Discord guild")).to.be.true;
+            });
         });
     });
 });