Skip to content

Commit 2909994

Browse files
committed
Add minimum property detection test
1 parent 4960037 commit 2909994

File tree

1 file changed

+378
-0
lines changed

1 file changed

+378
-0
lines changed
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
package test_with_remote_apis;
2+
3+
import com.slack.api.Slack;
4+
import com.slack.api.methods.AsyncMethodsClient;
5+
import com.slack.api.methods.MethodsClient;
6+
import com.slack.api.methods.SlackApiException;
7+
import com.slack.api.methods.request.canvases.sections.CanvasesSectionsLookupRequest;
8+
import com.slack.api.methods.response.bookmarks.BookmarksAddResponse;
9+
import com.slack.api.methods.response.bookmarks.BookmarksEditResponse;
10+
import com.slack.api.methods.response.bookmarks.BookmarksListResponse;
11+
import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse;
12+
import com.slack.api.methods.response.bots.BotsInfoResponse;
13+
import com.slack.api.methods.response.canvases.CanvasesCreateResponse;
14+
import com.slack.api.methods.response.canvases.CanvasesDeleteResponse;
15+
import com.slack.api.methods.response.canvases.CanvasesEditResponse;
16+
import com.slack.api.methods.response.canvases.access.CanvasesAccessDeleteResponse;
17+
import com.slack.api.methods.response.canvases.access.CanvasesAccessSetResponse;
18+
import com.slack.api.methods.response.canvases.sections.CanvasesSectionsLookupResponse;
19+
import com.slack.api.methods.response.chat.ChatDeleteResponse;
20+
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
21+
import com.slack.api.methods.response.chat.ChatUpdateResponse;
22+
import com.slack.api.methods.response.conversations.ConversationsHistoryResponse;
23+
import com.slack.api.methods.response.conversations.ConversationsListResponse;
24+
import com.slack.api.methods.response.conversations.ConversationsRepliesResponse;
25+
import com.slack.api.methods.response.files.FilesInfoResponse;
26+
import com.slack.api.methods.response.files.FilesUploadV2Response;
27+
import com.slack.api.methods.response.team.TeamInfoResponse;
28+
import com.slack.api.methods.response.team.TeamPreferencesListResponse;
29+
import com.slack.api.methods.response.team.profile.TeamProfileGetResponse;
30+
import com.slack.api.methods.response.users.UsersListResponse;
31+
import com.slack.api.methods.response.users.profile.UsersProfileGetResponse;
32+
import com.slack.api.model.Conversation;
33+
import com.slack.api.model.User;
34+
import com.slack.api.model.canvas.*;
35+
import config.Constants;
36+
import config.SlackTestConfig;
37+
import lombok.extern.slf4j.Slf4j;
38+
import org.junit.AfterClass;
39+
import org.junit.Before;
40+
import org.junit.BeforeClass;
41+
import org.junit.Test;
42+
43+
import java.io.File;
44+
import java.io.IOException;
45+
import java.util.ArrayList;
46+
import java.util.Collections;
47+
import java.util.List;
48+
49+
import static java.util.stream.Collectors.toList;
50+
import static org.hamcrest.CoreMatchers.*;
51+
import static org.hamcrest.MatcherAssert.assertThat;
52+
import static org.hamcrest.Matchers.greaterThan;
53+
54+
/**
55+
* The purpose of this test suite is to detect important property updates in API responses.
56+
* In particular, the following elements are often quietly added to production API responses,
57+
* and the lack of these properties can affect developers so much:
58+
* - Block Kit components
59+
* - File object metadata
60+
* - User info details
61+
* Therefore, regularly running these tests will help quickly identify any changes.
62+
* <p>
63+
* To execute this test suite, you need to create a sandbox environment
64+
* and install your custom app with appropriate bot scopes.
65+
* <p>
66+
* For future updates, you can reuse the code under `test_with_remote_apis.methods` as needed.
67+
* It's okay if it's not perfect initially; you can gradually enhance your test assets over time.
68+
*/
69+
@Slf4j
70+
public class MinimumPropertyDetectionTest {
71+
72+
// Setting this env variable is required;
73+
// If you want to go with a different env variable name, feel free to go with it
74+
static String botToken = System.getenv(Constants.SLACK_SDK_TEST_BOT_TOKEN);
75+
76+
// This TestConfig enables this test execution to detect missing properties in Java code
77+
static SlackTestConfig testConfig = SlackTestConfig.getInstance();
78+
static Slack slack = Slack.getInstance(testConfig.getConfig());
79+
// Use this Web API client for testing
80+
static MethodsClient client = slack.methods(botToken);
81+
// You can use this async client to avoid failing with a "ratelimited" error
82+
static AsyncMethodsClient asyncClient = slack.methodsAsync(botToken);
83+
84+
@BeforeClass
85+
public static void setUp() throws Exception {
86+
// Usually these invocations do nothing; check the code to learn what it does
87+
SlackTestConfig.initializeRawJSONDataFiles("bookmarks.*");
88+
SlackTestConfig.initializeRawJSONDataFiles("bots.*");
89+
SlackTestConfig.initializeRawJSONDataFiles("canvases.*");
90+
SlackTestConfig.initializeRawJSONDataFiles("chat.*");
91+
SlackTestConfig.initializeRawJSONDataFiles("conversations.*");
92+
SlackTestConfig.initializeRawJSONDataFiles("files.*");
93+
SlackTestConfig.initializeRawJSONDataFiles("team.*");
94+
SlackTestConfig.initializeRawJSONDataFiles("usergroups.*");
95+
SlackTestConfig.initializeRawJSONDataFiles("users.*");
96+
}
97+
98+
@Before
99+
public void beforeEachTest() throws Exception {
100+
// Changing this method for optimizing the test performance is totally fine
101+
// The fastest approach would be to have the channel ID as an env variable
102+
loadTestChannelId();
103+
}
104+
105+
@AfterClass
106+
public static void tearDown() throws InterruptedException {
107+
// This part finalizes the generated JSON files; there may be some rooms for improvements
108+
SlackTestConfig.awaitCompletion(testConfig);
109+
}
110+
111+
// If #random does not work for your testing workspace, replacing this name with a different one is totally fine
112+
private static final String TEST_CHANNEL_NAME = "random";
113+
private String testChannelId = null;
114+
115+
void loadTestChannelId() throws IOException, SlackApiException {
116+
if (testChannelId == null) {
117+
ConversationsListResponse channels = slack.methods().conversationsList(r -> r
118+
.token(botToken)
119+
.excludeArchived(true)
120+
.limit(100)
121+
);
122+
assertThat(channels.getError(), is(nullValue()));
123+
124+
for (Conversation channel : channels.getChannels()) {
125+
if (channel.getName().equals(TEST_CHANNEL_NAME)) {
126+
testChannelId = channel.getId();
127+
break;
128+
}
129+
}
130+
}
131+
}
132+
133+
// -----------------------------------------------------------------------------------------
134+
// When you run the following test methods and the returned API response has unknown properties,
135+
// The test fails with an exception; check the error message to know what's missing in current Java code
136+
// -----------------------------------------------------------------------------------------
137+
138+
@Test
139+
public void bookmarks() throws Exception {
140+
BookmarksListResponse bookmarks = client.bookmarksList(r -> r
141+
.channelId(testChannelId)
142+
);
143+
assertThat(bookmarks.getError(), is(nullValue()));
144+
145+
ChatPostMessageResponse message = client.chatPostMessage(r -> r
146+
.channel(testChannelId)
147+
.text("A very important message!"));
148+
assertThat(message.getError(), is(nullValue()));
149+
150+
String permalink = client.chatGetPermalink(r -> r
151+
.channel(testChannelId)
152+
.messageTs(message.getTs())
153+
).getPermalink();
154+
155+
BookmarksAddResponse creation = client.bookmarksAdd(req -> req
156+
.channelId(testChannelId)
157+
.title("test")
158+
.link(permalink)
159+
.type("link")
160+
);
161+
assertThat(creation.getError(), is(nullValue()));
162+
163+
BookmarksEditResponse modification = client.bookmarksEdit(req -> req
164+
.channelId(testChannelId)
165+
.bookmarkId(creation.getBookmark().getId())
166+
.title("test")
167+
.link(permalink)
168+
);
169+
assertThat(modification.getError(), is(nullValue()));
170+
171+
BookmarksRemoveResponse removal = client.bookmarksRemove(req -> req
172+
.channelId(testChannelId)
173+
.bookmarkId(modification.getBookmark().getId())
174+
);
175+
assertThat(removal.getError(), is(nullValue()));
176+
}
177+
178+
@Test
179+
public void bots() throws Exception {
180+
User botUser = null;
181+
String cursor = null;
182+
while (botUser == null && (cursor == null || !cursor.isEmpty())) {
183+
// using async client to prevent failing due to a rate limited error
184+
UsersListResponse response = asyncClient.usersList(req -> req).get();
185+
for (User u : response.getMembers()) {
186+
if (u.isBot() && !"USLACKBOT".equals(u.getId())) {
187+
botUser = u;
188+
break;
189+
}
190+
}
191+
if (response.getResponseMetadata() != null) {
192+
cursor = response.getResponseMetadata().getNextCursor();
193+
}
194+
}
195+
assertThat(botUser, is(notNullValue()));
196+
197+
String botId = botUser.getProfile().getBotId();
198+
BotsInfoResponse response = client.botsInfo(req -> req.bot(botId));
199+
assertThat(response.getError(), is(nullValue()));
200+
}
201+
202+
@Test
203+
public void canvases() throws Exception {
204+
CanvasesCreateResponse creation = client.canvasesCreate(r -> r
205+
.title("My canvas " + System.currentTimeMillis())
206+
.documentContent(CanvasDocumentContent.builder().markdown(
207+
"# Standalone canvas document\n" +
208+
"\n" +
209+
"---\n" +
210+
"## Before\n" +
211+
"**foo** _bar_\n" +
212+
"hey hey\n" +
213+
"\n").build())
214+
);
215+
assertThat(creation.getError(), is(nullValue()));
216+
217+
String canvasId = creation.getCanvasId();
218+
try {
219+
List<String> userIds = Collections.singletonList(client.authTest(r -> r).getUserId());
220+
CanvasesSectionsLookupResponse lookupResult = client.canvasesSectionsLookup(r -> r
221+
.canvasId(canvasId)
222+
.criteria(CanvasesSectionsLookupRequest.Criteria.builder()
223+
.sectionTypes(Collections.singletonList(CanvasDocumentSectionType.H2))
224+
.containsText("Before")
225+
.build()
226+
)
227+
);
228+
assertThat(lookupResult.getError(), is(nullValue()));
229+
230+
String sectionId = lookupResult.getSections().get(0).getId();
231+
CanvasesEditResponse edit = client.canvasesEdit(r -> r
232+
.canvasId(canvasId)
233+
.changes(Collections.singletonList(CanvasDocumentChange.builder()
234+
.sectionId(sectionId)
235+
.operation(CanvasEditOperation.REPLACE)
236+
.documentContent(CanvasDocumentContent.builder().markdown("## After").build())
237+
.build()
238+
))
239+
);
240+
assertThat(edit.getError(), is(nullValue()));
241+
242+
FilesInfoResponse details = client.filesInfo(r -> r.file(canvasId));
243+
assertThat(details.getError(), is(nullValue()));
244+
245+
CanvasesAccessSetResponse set = client.canvasesAccessSet(r -> r
246+
.canvasId(canvasId)
247+
.accessLevel(CanvasDocumentAccessLevel.WRITE)
248+
.userIds(userIds)
249+
);
250+
assertThat(set.getError(), is(nullValue()));
251+
CanvasesAccessDeleteResponse delete = client.canvasesAccessDelete(r -> r
252+
.canvasId(canvasId)
253+
.userIds(userIds)
254+
);
255+
assertThat(delete.getError(), is(nullValue()));
256+
257+
} finally {
258+
CanvasesDeleteResponse deletion = client.canvasesDelete(r -> r.canvasId(canvasId));
259+
assertThat(deletion.getError(), is(nullValue()));
260+
}
261+
}
262+
263+
@Test
264+
public void chat() throws Exception {
265+
String text = "This is a _test_ message posted by `test_with_remote_apis.MinimumPropertyDetectionTest`";
266+
ChatPostMessageResponse newMessage = client.chatPostMessage(req -> req
267+
.channel(testChannelId)
268+
.text(text)
269+
);
270+
assertThat(newMessage.getError(), is(nullValue()));
271+
assertThat(newMessage.getMessage().getText(), is(text));
272+
273+
String messageTs = newMessage.getTs();
274+
275+
ConversationsHistoryResponse history = client.conversationsHistory(req -> req
276+
.channel(testChannelId)
277+
.latest(messageTs)
278+
.inclusive(true)
279+
);
280+
assertThat(history.getError(), is(nullValue()));
281+
282+
ConversationsRepliesResponse replies = client.conversationsReplies(req -> req
283+
.channel(testChannelId)
284+
.ts(messageTs)
285+
.inclusive(true)
286+
);
287+
assertThat(replies.getError(), is(nullValue()));
288+
289+
ChatUpdateResponse modification = client.chatUpdate(req -> req
290+
.channel(testChannelId)
291+
.ts(messageTs)
292+
.text("**EDIT:** " + text)
293+
);
294+
assertThat(modification.getError(), is(nullValue()));
295+
296+
ChatDeleteResponse deletion = client.chatDelete(req -> req.channel(testChannelId).ts(messageTs));
297+
assertThat(deletion.getError(), is(nullValue()));
298+
}
299+
300+
@Test
301+
public void files() throws Exception {
302+
File file = new File("src/test/resources/sample_long.txt");
303+
FilesUploadV2Response upload = client.filesUploadV2(r -> r
304+
.file(file)
305+
.channel(testChannelId)
306+
.initialComment("initial comment")
307+
.filename("sample.txt")
308+
.title("file title")
309+
);
310+
assertThat(upload.getError(), is(nullValue()));
311+
assertThat(upload.isOk(), is(true));
312+
assertThat(upload.getFile().getTitle(), is("file title"));
313+
assertThat(upload.getFile().getName(), is("sample.txt"));
314+
315+
com.slack.api.model.File fileObj = null;
316+
while (fileObj == null || fileObj.getShares() == null || fileObj.getShares().getPublicChannels() == null) {
317+
fileObj = client.filesInfo(r -> r.file(upload.getFile().getId())).getFile();
318+
Thread.sleep(1_000L);
319+
}
320+
assertThat(fileObj.getTitle(), is("file title"));
321+
assertThat(fileObj.getName(), is("sample.txt"));
322+
323+
String messageTs = fileObj.getShares().getPublicChannels().get(testChannelId).get(0).getTs();
324+
ChatDeleteResponse response = slack.methods(botToken).chatDelete(r -> r
325+
.channel(testChannelId)
326+
.ts(messageTs)
327+
);
328+
assertThat(response.getError(), is(nullValue()));
329+
assertThat(response.isOk(), is(true));
330+
}
331+
332+
333+
@Test
334+
public void team() throws Exception {
335+
TeamInfoResponse team = client.teamInfo(r -> r);
336+
assertThat(team.getError(), is(nullValue()));
337+
338+
String teamId = team.getTeam().getId();
339+
340+
TeamProfileGetResponse profile = client.teamProfileGet(r -> r.teamId(teamId));
341+
assertThat(profile.getError(), is(nullValue()));
342+
343+
TeamPreferencesListResponse preferences = client.teamPreferencesList(r -> r);
344+
assertThat(preferences.getError(), is(nullValue()));
345+
}
346+
347+
@Test
348+
public void users_profile() throws Exception {
349+
User humanUser = null;
350+
// Using async client to avoid an exception due to rate limited errors
351+
for (User user : asyncClient.usersList(r -> r).get().getMembers()) {
352+
if (!user.isBot() && !user.isAppUser() && !user.isStranger() && !user.isDeleted()) {
353+
humanUser = user;
354+
break;
355+
}
356+
}
357+
String humanUserId = humanUser.getId();
358+
UsersProfileGetResponse profileGet = client.usersProfileGet(r -> r.user(humanUserId));
359+
assertThat(profileGet.getError(), is(nullValue()));
360+
}
361+
362+
@Test
363+
public void users() throws Exception {
364+
// Scanning all users is useful to detect optional property existence
365+
List<String> userIds = new ArrayList<>();
366+
String nextCursor = null;
367+
while (nextCursor == null || !nextCursor.isEmpty()) {
368+
// Using async client to avoid an exception due to rate limited errors
369+
UsersListResponse response = asyncClient.usersList(r -> r
370+
.includeLocale(true)
371+
.limit(3000)
372+
).get();
373+
nextCursor = response.getResponseMetadata().getNextCursor();
374+
userIds.addAll(response.getMembers().stream().map(User::getId).collect(toList()));
375+
}
376+
assertThat(userIds.size(), is(greaterThan(0)));
377+
}
378+
}

0 commit comments

Comments
 (0)