Skip to content

Commit 0472137

Browse files
Create Slack data fetching script for test data generation (fixes #262) (#286)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent bb5dc87 commit 0472137

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

integration-tests/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ integration-tests/
1414
│ ├── e2e/ # End-to-end tests with Playwright
1515
│ └── api/ # API tests with pytest
1616
└── utils/ # Utility functions for tests
17+
└── slack-data-fetcher.js # Script to fetch Slack data for test generation
1718
```
1819

1920
## Setup Instructions
@@ -82,3 +83,36 @@ Add new Playwright test files to the `tests/e2e` directory. See the [Playwright
8283
### API Tests
8384

8485
Add new pytest test files to the `tests/api` directory. See the [pytest documentation](https://docs.pytest.org/en/stable/) for more information.
86+
87+
## Test Data Generation
88+
89+
For generating test data from real Slack workspaces, use the data fetcher script:
90+
91+
```bash
92+
# Install dependencies
93+
npm install
94+
95+
# Run with a Slack API token
96+
./utils/slack-data-fetcher.js --token "xoxb-your-token"
97+
98+
# Or use environment variables
99+
export SLACK_TOKEN="xoxb-your-token"
100+
./utils/slack-data-fetcher.js
101+
102+
# Customize the number of items to fetch
103+
./utils/slack-data-fetcher.js --channels 5 --messages 10 --users 15
104+
105+
# Specify a custom output directory
106+
./utils/slack-data-fetcher.js --output /path/to/output/dir
107+
108+
# Disable data sanitization (not recommended for commits)
109+
./utils/slack-data-fetcher.js --no-sanitize
110+
```
111+
112+
The script will fetch:
113+
- Channel list
114+
- Messages from selected channels
115+
- User information
116+
- Mock OAuth response
117+
118+
Data is saved to `mocks/slack-api/data/` by default.
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Slack Data Fetcher
5+
*
6+
* This script fetches data from the Slack API and saves it as JSON files
7+
* for use by the mock Slack API service in integration tests.
8+
*/
9+
10+
const fs = require('fs');
11+
const path = require('path');
12+
const { WebClient } = require('@slack/web-api');
13+
const { program } = require('commander');
14+
const chalk = require('chalk');
15+
require('dotenv').config();
16+
17+
const DEFAULT_OUTPUT_DIR = path.join(__dirname, '../mocks/slack-api/data');
18+
const DEFAULT_CHANNEL_LIMIT = 10;
19+
const DEFAULT_MESSAGE_LIMIT = 20;
20+
const DEFAULT_USER_LIMIT = 20;
21+
22+
program
23+
.version('1.0.0')
24+
.description('Fetch Slack data for test data generation')
25+
.option('-t, --token <token>', 'Slack API token (or set SLACK_TOKEN env var)')
26+
.option('-o, --output <directory>', 'Output directory for data files', DEFAULT_OUTPUT_DIR)
27+
.option('-c, --channels <number>', 'Number of channels to fetch', DEFAULT_CHANNEL_LIMIT)
28+
.option('-m, --messages <number>', 'Number of messages per channel', DEFAULT_MESSAGE_LIMIT)
29+
.option('-u, --users <number>', 'Number of users to fetch', DEFAULT_USER_LIMIT)
30+
.option('--client-id <id>', 'Slack app client ID for OAuth data')
31+
.option('--client-secret <secret>', 'Slack app client secret for OAuth data')
32+
.option('--no-sanitize', 'Disable sanitization of sensitive data');
33+
34+
program.parse(process.argv);
35+
const options = program.opts();
36+
37+
const log = {
38+
info: (msg) => console.log(chalk.blue(`[INFO] ${msg}`)),
39+
warn: (msg) => console.log(chalk.yellow(`[WARN] ${msg}`)),
40+
error: (msg) => console.error(chalk.red(`[ERROR] ${msg}`)),
41+
success: (msg) => console.log(chalk.green(`[SUCCESS] ${msg}`))
42+
};
43+
44+
const token = options.token || process.env.SLACK_TOKEN;
45+
if (!token) {
46+
log.error('Slack API token is required. Use --token option or set SLACK_TOKEN env var.');
47+
process.exit(1);
48+
}
49+
50+
const outputDir = options.output;
51+
fs.mkdirSync(outputDir, { recursive: true });
52+
53+
const channelLimit = parseInt(options.channels);
54+
const messageLimit = parseInt(options.messages);
55+
const userLimit = parseInt(options.users);
56+
const shouldSanitize = options.sanitize !== false;
57+
58+
log.info(`Output directory: ${outputDir}`);
59+
log.info(`Fetching up to ${channelLimit} channels, ${messageLimit} messages/channel, and ${userLimit} users`);
60+
if (shouldSanitize) {
61+
log.info('Sanitization is enabled - sensitive data will be anonymized');
62+
} else {
63+
log.warn('Sanitization is disabled - data will contain real information');
64+
}
65+
66+
const slack = new WebClient(token);
67+
68+
/**
69+
* Sanitize sensitive data in objects
70+
*/
71+
function sanitizeData(data, type) {
72+
if (!shouldSanitize) return data;
73+
74+
if (type === 'users') {
75+
const members = data.members.map((user, index) => {
76+
const sanitizedUser = { ...user };
77+
78+
if (sanitizedUser.profile && sanitizedUser.profile.email) {
79+
sanitizedUser.profile.email = `user${index + 1}@example.com`;
80+
}
81+
82+
if (sanitizedUser.real_name) {
83+
sanitizedUser.real_name = `User ${index + 1}`;
84+
}
85+
86+
if (sanitizedUser.profile) {
87+
if (sanitizedUser.profile.real_name) {
88+
sanitizedUser.profile.real_name = `User ${index + 1}`;
89+
}
90+
if (sanitizedUser.profile.real_name_normalized) {
91+
sanitizedUser.profile.real_name_normalized = `User ${index + 1}`;
92+
}
93+
if (sanitizedUser.profile.display_name) {
94+
sanitizedUser.profile.display_name = `User ${index + 1}`;
95+
}
96+
if (sanitizedUser.profile.display_name_normalized) {
97+
sanitizedUser.profile.display_name_normalized = `User ${index + 1}`;
98+
}
99+
100+
if (sanitizedUser.profile.phone) {
101+
sanitizedUser.profile.phone = '';
102+
}
103+
}
104+
105+
return sanitizedUser;
106+
});
107+
108+
return { ...data, members };
109+
}
110+
111+
if (type === 'oauth') {
112+
const sanitizedData = { ...data };
113+
114+
if (sanitizedData.access_token) {
115+
sanitizedData.access_token = 'xoxb-test-token';
116+
}
117+
if (sanitizedData.authed_user && sanitizedData.authed_user.access_token) {
118+
sanitizedData.authed_user.access_token = 'xoxp-test-token';
119+
}
120+
121+
return sanitizedData;
122+
}
123+
124+
return data;
125+
}
126+
127+
/**
128+
* Save data to a JSON file
129+
*/
130+
function saveToFile(data, filename) {
131+
const filePath = path.join(outputDir, filename);
132+
try {
133+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
134+
log.success(`Saved data to ${filePath}`);
135+
} catch (error) {
136+
log.error(`Failed to save ${filename}: ${error.message}`);
137+
}
138+
}
139+
140+
/**
141+
* Fetch users from Slack API
142+
*/
143+
async function fetchUsers() {
144+
try {
145+
log.info('Fetching user list...');
146+
const result = await slack.users.list({ limit: userLimit });
147+
const sanitizedData = sanitizeData(result, 'users');
148+
saveToFile(sanitizedData, 'users.json');
149+
return sanitizedData.members;
150+
} catch (error) {
151+
log.error(`Failed to fetch users: ${error.message}`);
152+
return [];
153+
}
154+
}
155+
156+
/**
157+
* Fetch channels from Slack API
158+
*/
159+
async function fetchChannels() {
160+
try {
161+
log.info('Fetching channel list...');
162+
const result = await slack.conversations.list({
163+
limit: channelLimit,
164+
types: 'public_channel,private_channel'
165+
});
166+
saveToFile(result, 'conversations.json');
167+
return result.channels;
168+
} catch (error) {
169+
log.error(`Failed to fetch channels: ${error.message}`);
170+
return [];
171+
}
172+
}
173+
174+
/**
175+
* Fetch message history for a channel
176+
*/
177+
async function fetchMessages(channel) {
178+
try {
179+
log.info(`Fetching messages for channel ${channel.name} (${channel.id})...`);
180+
const result = await slack.conversations.history({
181+
channel: channel.id,
182+
limit: messageLimit
183+
});
184+
saveToFile(result, `conversations_history_${channel.id}.json`);
185+
return result.messages;
186+
} catch (error) {
187+
log.error(`Failed to fetch messages for channel ${channel.id}: ${error.message}`);
188+
return [];
189+
}
190+
}
191+
192+
/**
193+
* Create mock OAuth response
194+
*/
195+
function createOAuthResponse() {
196+
const oauthResponse = {
197+
ok: true,
198+
access_token: `xoxb-${Date.now()}`,
199+
token_type: "bot",
200+
scope: "channels:history,channels:read,users:read",
201+
bot_user_id: "B12345",
202+
app_id: "A12345",
203+
team: {
204+
id: "T12345",
205+
name: "Test Team",
206+
domain: "testteam"
207+
},
208+
enterprise: null,
209+
authed_user: {
210+
id: "U12345",
211+
scope: "chat:write",
212+
access_token: `xoxp-${Date.now()}`,
213+
token_type: "user"
214+
},
215+
is_enterprise_install: false
216+
};
217+
218+
const sanitizedData = sanitizeData(oauthResponse, 'oauth');
219+
saveToFile(sanitizedData, 'oauth.json');
220+
}
221+
222+
/**
223+
* Main execution function
224+
*/
225+
async function main() {
226+
try {
227+
log.info('Verifying Slack API token...');
228+
await slack.auth.test();
229+
230+
const users = await fetchUsers();
231+
const channels = await fetchChannels();
232+
233+
if (channels.length > 0) {
234+
for (const channel of channels.slice(0, 2)) { // Limit to 2 channels for messages
235+
await fetchMessages(channel);
236+
}
237+
}
238+
239+
createOAuthResponse();
240+
241+
log.success('Data fetching completed successfully!');
242+
} catch (error) {
243+
log.error(`Failed to fetch data: ${error.message}`);
244+
process.exit(1);
245+
}
246+
}
247+
248+
main();

0 commit comments

Comments
 (0)