Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Implemented a mock slack api for testing #41

Open
wants to merge 4 commits into
base: feature/typescript-convert
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,32 @@ Many linting issues can be automatically fixed by running `yarn fix`.
## Local docker setup
This bot comes shipped with possibility to run it in docker.

ts-node-esm ./node_modules/.bin/knex migrate:make create_tables -x ts
benjamin-cizej marked this conversation as resolved.
Show resolved Hide resolved


1. Copy the example.env to .env
2. Fill the .env file
3. docker-compose up -d
4. (on the first time setup) ssh into nodejs container run `npm run db-migrate-latest` to prepare the database structure.
5. Visit http://${PROJECT_NAME}.localhost:4551/ to get the ngrok url
6. Paste the url into slack api Event subscriptions.

## Local testing slack api

It's possible to test the project without connecting to slack servers
- SLACK_API_TYPE=mock must be set in the .env file, also MOCK_API_PORT
- Slack credentials are also needed, but can be anything
- to run the mock server, ssh into the nodejs container and run `npm run mock_serve`

Talking to the mock slack api:
- possibly `chmod +x scontrol` in the project root
- ssh into nodejs container
- `./scontrol fromUser toUser channel ++` to send an event
- `./scontrol challenge` to send the challenge request

There are predefined users and channels already in the mock slack api:
- users: janez.kranjski, bobby, slackbot, agilekarma_bot
- channels: random, general

When sending an event using a user or a channel that don't exist yet in the api, they will be created,
however they will be lost when closing the mock server.
Same names can be used later, but they will have a different slackId and will thus be created anew in the
agilekarma database.
3 changes: 3 additions & 0 deletions docker/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ SCOREBOT_USE_SSL=1
SCOREBOT_LEADERBOARD_URL=localhost:3000
USER_LIMIT_VOTING_MAX=3
UNDO_TIME_LIMIT=300
# mock|default
SLACK_API_TYPE=
MOCK_SLACK_PORT=5010

# Database variables
DATABASE_NAME=
Expand Down
13 changes: 10 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import slackClient from '@slack/client';
import slackClient, { LogLevel } from '@slack/client';
import bodyParser from 'body-parser';
import * as dotenv from 'dotenv';
import express from 'express';
Expand All @@ -12,14 +12,21 @@ const {
SCOREBOT_LEADERBOARD_URL: leaderboardUrl = '',
SCOREBOT_PORT: port = '80',
SCOREBOT_USE_SSL: useHttps = '0',
MOCK_SLACK_PORT: mockApiPort = '5010',
SLACK_API_TYPE: slackApiType,
} = process.env;

const protocol = useHttps !== '1' ? 'http://' : 'https://';
const frontendUrl = protocol + leaderboardUrl;
const server = express();
// @ts-ignore
setSlackClient(new slackClient.WebClient(accessToken));

if (slackApiType === 'mock') {
// @ts-ignore
benjamin-cizej marked this conversation as resolved.
Show resolved Hide resolved
setSlackClient(new slackClient.WebClient(accessToken, { slackApiUrl: `http://localhost:${mockApiPort}/api/`, logLevel: LogLevel.DEBUG }));
} else {
// @ts-ignore
setSlackClient(new slackClient.WebClient(accessToken));
}
server.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', frontendUrl);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
Expand Down
11 changes: 11 additions & 0 deletions mock_slack_api/mock_data/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const {
SLACK_VERIFICATION_TOKEN: token,
} = process.env;

const workspaceData = {
context_team_id: 'T04DD0FC16Z',
token,
api_app_id: 'A04DFUS9W58',
};

export default workspaceData;
154 changes: 154 additions & 0 deletions mock_slack_api/mock_data/channels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import type {
BlankChannel, ChannelData, ChannelResponse, Channel,
} from '../slack_types.js';
import workspaceData from './base.js';
import { generateSlackId } from '../slack_util.js';

const { context_team_id: teamId } = workspaceData;

const seedChannelData: ChannelData[] = [
{
id: 'C04DFRBRDR9',
name: 'testing-bots',
name_normalized: 'testing-bots',
context_team_id: teamId,
created: 1669973344,
updated: 1669973344512,
creator: 'U04DFR727UK',
num_members: 3,
},
{
id: 'C04DJCCCSNQ',
name: 'random',
created: 1669973282,
name_normalized: 'random',
context_team_id: teamId,
updated: 1669973282559,
creator: 'U04DFR727UK',
num_members: 3,
},
{
id: 'C04DUHMUZ3K',
name: 'general',
created: 1669973282,
name_normalized: 'general',
context_team_id: teamId,
updated: 1669973282349,
creator: 'U04DFR727UK',
num_members: 3,
},
];

const channelResponse: ChannelResponse = {
ok: true,
channels: [],
response_metadata: {
next_cursor: '',
scopes: [
'chat:write',
'calls:read',
'channels:join',
'commands',
'groups:read',
'groups:write',
'groups:history',
'reactions:read',
'reactions:write',
'channels:history',
'chat:write.customize',
'channels:read',
'users:read',
],
acceptedScopes: ['channels:read', 'groups:read', 'mpim:read', 'im:read', 'read'],
},
};

const blankChannel: BlankChannel = {
is_channel: true,
is_group: false,
is_im: false,
is_mpim: false,
is_private: false,
is_archived: false,
is_general: false,
unlinked: 0,
is_shared: false,
is_org_shared: false,
is_pending_ext_shared: false,
pending_shared: [],
parent_conversation: null,
is_ext_shared: false,
shared_team_ids: [],
pending_connected_team_ids: [],
is_member: true,
topic: [],
purpose: [],
previous_names: [],
num_members: 3,
};

class Channels {
private static instance: Channels;

private channels: Channel[] = [];

private constructor() {
this.channels = seedChannelData.map((seed) => ({ ...blankChannel, ...seed }));
}

static getInstance() {
if (this.instance) {
return this.instance;
}

this.instance = new Channels();
return this.instance;
}

public getChannels() {
const channelsRes = channelResponse;
channelsRes.channels = this.channels;
return channelsRes;
}

public getChannelId(channelName: string) {
const channel = this.channels.find((ch) => ch.name === channelName);
if (channel !== undefined) {
return channel.id;
}
return undefined;
}

public getChannelName(channelId: string) {
const channel = this.channels.find((ch) => ch.id === channelId);
if (channel !== undefined) {
return channel.name;
}
return undefined;
}

/*
Creates a channel if there isn't one already
*/
public createChannel(name: string): 'exists' | 'created' {
if (this.getChannelId(name) === undefined) {
const data = {
id: generateSlackId(11, 'channel'),
name,
created: Date.now(),
name_normalized: name,
context_team_id: teamId,
updated: Date.now(),
creator: 'U04DFR727UK',
num_members: 3,
};

this.channels.push({ ...blankChannel, ...data });
return 'created';
}

return 'exists';
}
}

export default Channels;
Loading