Skip to content

Commit

Permalink
feat: Add console logs for permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
schw4rzlicht committed Jun 7, 2020
1 parent 82e693b commit 3928982
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 71 deletions.
103 changes: 103 additions & 0 deletions __tests__/PermissionController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {PermissionCollector, PermissionController, PermissionError, Reason} from "../src/lib/PermissionController";
import {Config} from "../src/lib/Config";
import CooldownPermission from "../src/lib/permissions/CooldownPermission";
import ModeratorPermission from "../src/lib/permissions/ModeratorPermission";
import OwnerPermission from "../src/lib/permissions/OwnerPermission";
import {RuntimeInformation} from "../src/lib/RuntimeInformation";
import TwitchPrivateMessage from "twitch-chat-client/lib/StandardCommands/TwitchPrivateMessage";

const Fs = require("fs");
const _ = require("lodash");

let config: Config;
let permissionController: PermissionController;

beforeAll(() => {
config = loadConfig();
permissionController = new PermissionController()
.withPermissionInstance(new CooldownPermission())
.withPermissionInstance(new ModeratorPermission())
.withPermissionInstance(new OwnerPermission());
})

test("Permission collector denies permission", () => {

let permissionCollector = new PermissionCollector();
permissionCollector.denyPermission("test", "i want to");

expect(permissionCollector.permissionDenied).toBe(true);
expect(permissionCollector.godMode).toBe(false);
expect(permissionCollector.permissionDeniedReasons).toContainEqual(new Reason("test", "i want to"));
expect(permissionCollector.godModeReasons.length).toBe(0);
});

test("Permission collector grants permission b/c of godMode", () => {

let permissionCollector = new PermissionCollector();
permissionCollector.denyPermission("test", "i want to");
permissionCollector.enableGodMode("i know why");

expect(permissionCollector.permissionDenied).toBe(false);
expect(permissionCollector.godMode).toBe(true);
expect(permissionCollector.permissionDeniedReasons).toContainEqual(new Reason("test", "i want to"));
expect(permissionCollector.godModeReasons).toContainEqual("i know why");
});

test("Permission collector's god mode cannot be overwritten", () => {
let permissionCollector = new PermissionCollector();
permissionCollector.denyPermission("test", "i want to");

expect(permissionCollector.permissionDenied).toBe(true);
expect(permissionCollector.godMode).toBe(false);

permissionCollector.enableGodMode("i know why");

expect(permissionCollector.permissionDenied).toBe(false);
expect(permissionCollector.godMode).toBe(true);

permissionCollector.denyPermission("test2", "i still want to!");

expect(permissionCollector.permissionDenied).toBe(false);
expect(permissionCollector.godMode).toBe(true);

expect(permissionCollector.permissionDeniedReasons).toContainEqual(new Reason("test", "i want to"));
expect(permissionCollector.permissionDeniedReasons).toContainEqual(new Reason("test2", "i still want to!"));

expect(permissionCollector.godModeReasons).toContainEqual("i know why");
});

test("Permission denied b/c of active cooldown", () => {

permissionController.setAdditionalRuntimeInformation("lastCall", new Date().getTime() - 10000);

let rawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"});

return expect(permissionController.checkPermissions(new RuntimeInformation(config, "Alice",
rawMessage))).rejects.toBeInstanceOf(PermissionError);
});

test("Permission granted b/c user is moderator", async () => {

permissionController.setAdditionalRuntimeInformation("lastCall", new Date().getTime() - 10000);

let rawMessage = new TwitchPrivateMessage("doesNotMatter", null, new Map([["badges", "moderator"]]), {nick: "Alice"});
let permissionCollector = await permissionController.checkPermissions(new RuntimeInformation(config, "Alice", rawMessage));

expect(permissionCollector.godModeReasons).toContain("user is moderator");
});

test("Permission granted b/c user is owner", async () => {

permissionController.setAdditionalRuntimeInformation("lastCall", new Date().getTime() - 10000);

let rawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"});
let permissionCollector = await permissionController.checkPermissions(new RuntimeInformation(
loadConfig({twitch: {channel: "Alice", clientId: "clientId", accessToken: "accessToken"}}),
"Alice", rawMessage))

expect(permissionCollector.godModeReasons).toContain("user is channel owner");
});

function loadConfig(overrideConfigValues?: any): Config {
return new Config(_.assign({}, JSON.parse(Fs.readFileSync("config.json.sample", {encoding: "utf-8"})), overrideConfigValues));
}
68 changes: 36 additions & 32 deletions __tests__/Twitch2Ma.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,38 +190,6 @@ test("Send help w/o commands", async () => {
expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Alice");
});

test("Cooldown calculation", () => {

let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"});
let bobRawMessage = new TwitchPrivateMessage("doesNotMatter", null, new Map([["badges", "moderator"]]), {nick: "Bob"});
let ownerRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: config.twitch.channel});

let twitch2Ma = getTwitch2MaInstanceAndEnableLogin();

let now = new Date().getTime();

expect(twitch2Ma.cooldown(now, null, aliceRawMessage)).toBeLessThanOrEqual(0);
expect(twitch2Ma.cooldown(now, now, aliceRawMessage)).toBeGreaterThan(0);
expect(twitch2Ma.cooldown(now, now, bobRawMessage)).toBeLessThanOrEqual(0);
expect(twitch2Ma.cooldown(now, now, aliceRawMessage)).toBeGreaterThan(0);
expect(twitch2Ma.cooldown(now, now, ownerRawMessage)).toBeLessThanOrEqual(0);
expect(twitch2Ma.cooldown(now, now, aliceRawMessage)).toBeGreaterThan(0);
expect(twitch2Ma.cooldown(now, 666, aliceRawMessage)).toBeLessThanOrEqual(0);
});

test("Cooldown active", async () => {

let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"});

let twitch2Ma = getTwitch2MaInstanceAndEnableLogin(loadConfig());
await twitch2Ma.start();

twitch2Ma["lastCall"] = new Date().getTime();

await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter",
"Alice", "!red", aliceRawMessage, "@Alice, please wait \\d{1,2} seconds and try again!");
});

test("Command successful", async () => {

let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"});
Expand Down Expand Up @@ -311,6 +279,42 @@ test("Telnet command failed", async () => {
expect(commandExecutedHandler).not.toBeCalled();
});

test("Command permissions denied", async () => {

let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"});
let permissionDeniedHandler = jest.fn();

let twitch2Ma = getTwitch2MaInstanceAndEnableLogin();
twitch2Ma.onPermissionDenied(permissionDeniedHandler);
await twitch2Ma.start();

await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter",
"Alice", "!red", aliceRawMessage, "@Alice set the lights to red!");

await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter",
"Alice", "!red", aliceRawMessage, "@Alice, please wait \\d{1,2} seconds and try again!");

return expect(permissionDeniedHandler).toBeCalledWith("#doesNotMatter", "Alice", "cooldown");
});

test("Command permissions denied but godMode enabled", async () => {

let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, new Map([["badges", "moderator"]]), {nick: "Alice"});
let godModeHandler = jest.fn();

let twitch2Ma = getTwitch2MaInstanceAndEnableLogin();
twitch2Ma.onGodMode(godModeHandler);
await twitch2Ma.start();

await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter",
"Alice", "!red", aliceRawMessage, "@Alice set the lights to red!");

await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter",
"Alice", "!red", aliceRawMessage, "@Alice set the lights to red!");

return expect(godModeHandler).toBeCalledWith("#doesNotMatter", "Alice", "user is moderator");
})

test("Message not involving the bot", async () => {

let twitch2Ma = getTwitch2MaInstanceAndEnableLogin();
Expand Down
4 changes: 4 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ test("Event handlers attached", () => {
twitch2Ma.onTwitchConnected = jest.fn();
twitch2Ma.onCommandExecuted = jest.fn();
twitch2Ma.onHelpExecuted = jest.fn();
twitch2Ma.onPermissionDenied = jest.fn();
twitch2Ma.onGodMode = jest.fn();
twitch2Ma.onError = jest.fn();

index.attachEventHandlers(twitch2Ma);
Expand All @@ -37,6 +39,8 @@ test("Event handlers attached", () => {
expect(twitch2Ma.onTwitchConnected).toBeCalled();
expect(twitch2Ma.onCommandExecuted).toBeCalled();
expect(twitch2Ma.onHelpExecuted).toBeCalled();
expect(twitch2Ma.onPermissionDenied).toBeCalled();
expect(twitch2Ma.onGodMode).toBeCalled();
expect(twitch2Ma.onError).toBeCalled();
});

Expand Down
106 changes: 106 additions & 0 deletions src/lib/PermissionController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {RuntimeInformation} from "./RuntimeInformation";

export interface PermissionInstance {
check(permissionCollector: PermissionCollector,
runtimeInformation: RuntimeInformation,
additionalRuntimeInformation: Map<String, any>): void;
}

export class PermissionError extends Error {

readonly permissionCollector: PermissionCollector;

constructor(permissionCollector: PermissionCollector) {
super();
this.permissionCollector = permissionCollector;
Object.setPrototypeOf(this, PermissionError.prototype);
this.name = PermissionError.name;
}
}

export class Reason {
public readonly name: string;
public readonly viewerMessage: string;

constructor(name: string, viewerMessage: string) {
this.name = name;
this.viewerMessage = viewerMessage;
}
}

export class PermissionController {

private readonly permissionInstances: Array<PermissionInstance>;
private readonly additionalRuntimeInformation: Map<String, any>;

constructor() {
this.permissionInstances = [];
this.additionalRuntimeInformation = new Map();
}

withPermissionInstance(instance: PermissionInstance): PermissionController {
this.permissionInstances.push(instance);
return this;
}

async checkPermissions(runtimeInformation: RuntimeInformation) {

let permissionCollector = new PermissionCollector();
for (const permissionInstance of this.permissionInstances) {
permissionInstance.check(permissionCollector, runtimeInformation, this.additionalRuntimeInformation);
}

if (permissionCollector.permissionDenied) {
throw new PermissionError(permissionCollector);
}

return permissionCollector;
}

setAdditionalRuntimeInformation(name: string, value: any) {
this.additionalRuntimeInformation.set(name, value);
}
}

export class PermissionCollector {

private _permissionDenied: boolean;
private _godMode: boolean;

private readonly _godModeReasons: Array<String>;
private readonly _permissionDeniedReasons: Array<Reason>;

constructor() {
this._permissionDenied = false;
this._godMode = false;
this._godModeReasons = [];
this._permissionDeniedReasons = [];
}

denyPermission(reason: string, message: string) {
this._permissionDenied = !this._godMode;
this._permissionDeniedReasons.push(new Reason(reason, message));
}

enableGodMode(reason: string) {
this._permissionDenied = false;
this._godMode = true;
this._godModeReasons.push(reason);
}

get permissionDenied(): boolean {
return this._permissionDenied;
}

get permissionDeniedReasons(): Array<Reason> {
return this._permissionDeniedReasons;
}

get godMode(): boolean {
return this._godMode;
}

get godModeReasons(): Array<String> {
return this._godModeReasons;
}
}
15 changes: 15 additions & 0 deletions src/lib/RuntimeInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Config} from "./Config";
import TwitchPrivateMessage from "twitch-chat-client/lib/StandardCommands/TwitchPrivateMessage";

export class RuntimeInformation {

public readonly config: Config;
public readonly userName: string;
public readonly rawMessage: TwitchPrivateMessage;

constructor(config: Config, userName: string, rawMessage: TwitchPrivateMessage) {
this.config = config;
this.userName = userName;
this.rawMessage = rawMessage;
}
}
Loading

0 comments on commit 3928982

Please sign in to comment.