Skip to content

Commit

Permalink
Merge pull request #66 from osu-cascades/dev
Browse files Browse the repository at this point in the history
RC v2.0.1
  • Loading branch information
ctsstc authored Mar 14, 2019
2 parents 9d491d3 + c56e970 commit d372ecc
Show file tree
Hide file tree
Showing 27 changed files with 306 additions and 181 deletions.
1 change: 1 addition & 0 deletions .env-example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#DEFAULT_CHANNEL=CHANGEME
#WELCOME_CHANNEL=CHANGEME
DISCORD_APP_TOKEN=CHANGEME
GOOGLE_API_KEY=CHANGEME
GOOGLE_SEARCH_ENGINE_ID=CHANGEME
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# hackbot

|branch|build status|bots|
|---|---|---|
|master|[![Build Status](https://travis-ci.org/osu-cascades/hackbot.svg?branch=master)](https://travis-ci.org/osu-cascades/hackbot)| [![Greenkeeper badge](https://badges.greenkeeper.io/osu-cascades/hackbot.svg)](https://greenkeeper.io/) |
|staging|[![Build Status](https://travis-ci.org/osu-cascades/hackbot.svg?branch=staging)](https://travis-ci.org/osu-cascades/hackbot)|

|What|Badge|
|---|---|
|Master Build|[![Build Status](https://travis-ci.org/osu-cascades/hackbot.svg?branch=master)](https://travis-ci.org/osu-cascades/hackbot)|
|Staging Build|[![Build Status](https://travis-ci.org/osu-cascades/hackbot.svg?branch=dev)](https://travis-ci.org/osu-cascades/hackbot)|
|Maintainability|[![Maintainability](https://api.codeclimate.com/v1/badges/96320fe592c30381915f/maintainability)](https://codeclimate.com/github/osu-cascades/hackbot)|
|Test Coverage|[![Test Coverage](https://api.codeclimate.com/v1/badges/96320fe592c30381915f/test_coverage)](https://codeclimate.com/github/osu-cascades/hackbot)|
|GreenKeeper|[![Greenkeeper badge](https://badges.greenkeeper.io/osu-cascades/hackbot.svg)](https://greenkeeper.io/)|

A Discord bot for the Cascades Tech Club [Discord](http://discordapp.com) server. To add a command, see the [Commands](#commands) section below.

Expand Down Expand Up @@ -36,7 +38,7 @@ In a nutshell, work in the [dev](https://github.com/osu-cascades/hackbot/tree/de

In all cases, be sure to run the test suite to make sure all tests pass. _All tests should be passing before you merge dev to master_.

You should embrace testing. _hackbot_ uses the [Jest](https://facebook.github.io/jest/) test framework. Have two console panes open: one for running and watching the test suite, and the other for everything else you need to do. You can run the test suite once with `npm test`. Once you get tired of running `npm test` manually, use the watcher by running `npm run test:watch`. It is sweet and people will think you are a super hacker. Look at `__tests__/commands/add.test.ts` for an example of how to test commands and their channel messages.
You should embrace testing. _hackbot_ uses the [Jest](https://facebook.github.io/jest/) test framework. Have two console panes open: one for running and watching the test suite, and the other for everything else you need to do. You can run the test suite once with `npm test`. Once you get tired of running `npm test` manually, use the watcher by running `npm run test:watch`. It is sweet and people will think you are a super hacker. Look at `__tests__/commands/add.test.ts` for an example of how to test commands and their channel messages. If you use VSCode for your IDE there's a Jest extension that will run a watcher and give you inline updates on your tests, as well as a few other awesome features!

Please refer to [airbnb's style guide](https://github.com/airbnb/javascript) while coding.

Expand Down
8 changes: 8 additions & 0 deletions __tests__/__mocks__/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
get: jest.fn().mockResolvedValue({
data: {}
}),
request: jest.fn().mockResolvedValue({
data: {}
})
};
4 changes: 2 additions & 2 deletions __tests__/commands/add.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Add from '../../src/commands/add';
import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
17 changes: 9 additions & 8 deletions __tests__/commands/gitProfile.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import axios from 'axios';
import GitProfile, { IGitProfileResponse } from '../../src/commands/gitProfile';
import mocked from '../helpers/mocked';
import GitProfile from '../../src/commands/gitProfile';
import axiosMock from '../__mocks__/axios';
import mockGithubProfile from '../mockData/githubProfile';
import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

jest.mock('axios');

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
jest.resetModules();
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
});

describe('GitProfile Command', () => {
beforeEach(() => {
const resolvedData = Promise.resolve({data: mockGithubProfile});
axiosMock.request.mockResolvedValueOnce(resolvedData);
});
test('No username specified', () => {
GitProfile.execute([], mockMessage);
expect(sendMock).lastCalledWith('Please enter a username.');
});
test('Responds with profile information', async () => {
mocked<any, IGitProfileResponse>(axios.request).mockResolvedValue({data: mockGithubProfile});
await GitProfile.execute(['ctsstc'], mockMessage);
const sentMessage = sendMock.mock.calls[0][0];
expect(sentMessage).toContain(`[${mockGithubProfile.type}]`);
Expand All @@ -37,14 +39,13 @@ describe('GitProfile Command', () => {
mockGithubProfile.name = null;
mockGithubProfile.company = null;
mockGithubProfile.location = null;
mocked<any, IGitProfileResponse>(axios.request).mockResolvedValue({data: mockGithubProfile});
await GitProfile.execute(['ctsstc'], mockMessage);
const sentMessage = sendMock.mock.calls[0][0];
expect(sentMessage.startsWith(`[${mockGithubProfile.type}] with`));
});
test('Requests API using username', async () => {
await GitProfile.execute(['ctsstc'], mockMessage);
const requestOptions = mocked<any, IGitProfileResponse>(axios.request).mock.calls[0][0];
const requestOptions = axiosMock.request.mock.calls[0][0];
expect(requestOptions.url).toContain('api.github.com/users/ctsstc');
});
});
4 changes: 2 additions & 2 deletions __tests__/commands/lmgtfy.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Lmgtfy from '../../src/commands/lmgtfy';
import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/magic8ball.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Magic8Ball from '../../src/commands/magic8ball';
import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/rules.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Rules from '../../src/commands/rules';

import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/say.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Say from '../../src/commands/say';

import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
52 changes: 52 additions & 0 deletions __tests__/commands/search.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Search from '../../src/commands/search';
import config from '../../src/config';
import axiosMock from '../__mocks__/axios';
import { message as mockMessage, MockedMessage } from '../mocks/discord';
import { noResults, results } from './../mockData/search';

jest.mock('axios');
jest.mock('../../src/config.ts');

let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
mockMessage.reply = sendMock;
config.googleApiKey = 'abc123';
config.googleSearchEngineId = '12345678910:some-thing';
});

const setupRequiredMessage = 'Setup Required: Configure Google API keys in the environment variables';

describe('Search Command', () => {
describe('Environment Variables', () => {
test('Setup message when missing Google API Key ENV Var', async () => {
config.googleApiKey = undefined;
await Search.execute(['Missing Google API Key'], mockMessage);
expect(sendMock).lastCalledWith(setupRequiredMessage);
});
test('Setup message when missing Google Search Engine ID ENV Var', async () => {
config.googleSearchEngineId = undefined;
await Search.execute(['Missing Google Search Engine ID'], mockMessage);
expect(sendMock).lastCalledWith(setupRequiredMessage);
});
});
test('With no results', async () => {
const mockedData = Promise.resolve({ data: noResults });
axiosMock.get.mockResolvedValueOnce(mockedData);
await Search.execute(['Nothing here'], mockMessage);
expect(sendMock).lastCalledWith('`No results found.`');
});
test('With results', async () => {
const mockedData = Promise.resolve({ data: results });
axiosMock.get.mockResolvedValueOnce(mockedData);
await Search.execute(['dingusy'], mockMessage);
expect(sendMock).lastCalledWith(results.items[0].link);
});
test('Malformed Response', async () => {
const mockedData = Promise.resolve({ data: {} });
axiosMock.get.mockResolvedValueOnce(mockedData);
await Search.execute(['NOPE'], mockMessage);
expect(sendMock).lastCalledWith("I'm Sorry Dave, I'm afraid I can't do that...");
});
});
4 changes: 2 additions & 2 deletions __tests__/commands/source.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';
import Source from './../../src/commands/source';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
5 changes: 3 additions & 2 deletions __tests__/commands/version.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { message as mockMessage } from '../mocks/discord';
import { Message } from 'discord.js';
import { message as mockMessage, MockedMessage } from '../mocks/discord';
import Version from './../../src/commands/version';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/commands/xmas.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Xmas from '../../src/commands/xmas';
import { message as mockMessage } from '../mocks/discord';
import { message as mockMessage, MockedMessage } from '../mocks/discord';

let sendMock: jest.Mock<any, any>;
let sendMock: MockedMessage;
beforeEach(() => {
sendMock = jest.fn();
mockMessage.channel.send = sendMock;
Expand Down
6 changes: 0 additions & 6 deletions __tests__/helpers/mocked.ts

This file was deleted.

4 changes: 3 additions & 1 deletion __tests__/library/commandLoader.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import glob from 'glob';
import CommandLoader, { ICommandClasses } from '../../src/library/commandLoader';
import { COMMANDS_PATH_GLOB } from './../../src/library/commands';

describe('CommandLoader', () => {
let commandClasses: ICommandClasses;
const files = glob.sync('./src/commands/**/*.ts');
const files = glob.sync(COMMANDS_PATH_GLOB);

beforeEach(() => {
commandClasses = CommandLoader.getCommandClasses(files);
Expand All @@ -23,6 +24,7 @@ describe('CommandLoader', () => {
commandName = commandName.replace(/^\w/, (char) => {
return char.toUpperCase();
});
expect(commandClass).toBeDefined();
expect(commandName).toEqual(commandClass.name);
}
});
Expand Down
4 changes: 2 additions & 2 deletions __tests__/mockData/githubProfile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IGitProfileResponse } from "../../src/commands/gitProfile";
import IGithubProfile from "../../src/commands/interfaces/iGithubProfile";

export default {
login: "ctsstc",
Expand Down Expand Up @@ -32,4 +32,4 @@ export default {
following: 131,
created_at: "2010-09-26T21:31:50Z",
updated_at: "2018-09-05T16:21:19Z"
} as IGitProfileResponse;
} as IGithubProfile;
19 changes: 19 additions & 0 deletions __tests__/mockData/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

export const noResults = {
queries: {
request: [
{ totalResults: 0 }
]
}
};

export const results = {
queries: {
request: [
{ totalResults: 1 }
]
},
items: [
{ link: 'https://www.google.com/'}
]
};
3 changes: 2 additions & 1 deletion __tests__/mocks/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ jest.mock('discord.js');
export const client = new Client();
export const guild = new Guild(client, {});
export const textChannel = new TextChannel(guild, {});
export const message = new Message(textChannel, {}, client);
export const message = new Message(textChannel, false as any, client);
export type MockedMessage = jest.MockInstance<Promise<Message|Message[]>, any> | any;

message.channel = textChannel;
Loading

0 comments on commit d372ecc

Please sign in to comment.