slack-ruby-client 0.12.0 → 0.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +21 -9
  4. data/.rubocop_todo.yml +64 -56
  5. data/.travis.yml +5 -3
  6. data/CHANGELOG.md +90 -32
  7. data/Dangerfile +2 -0
  8. data/Gemfile +10 -4
  9. data/LICENSE.md +1 -1
  10. data/README.md +176 -32
  11. data/Rakefile +2 -1
  12. data/UPGRADING.md +27 -1
  13. data/bin/commands.rb +21 -0
  14. data/bin/commands/admin_apps.rb +27 -0
  15. data/bin/commands/admin_apps_approved.rb +17 -0
  16. data/bin/commands/admin_apps_requests.rb +16 -0
  17. data/bin/commands/admin_apps_restricted.rb +17 -0
  18. data/bin/commands/admin_conversations.rb +17 -0
  19. data/bin/commands/admin_emoji.rb +54 -0
  20. data/bin/commands/admin_inviteRequests.rb +36 -0
  21. data/bin/commands/admin_inviteRequests_approved.rb +16 -0
  22. data/bin/commands/admin_inviteRequests_denied.rb +16 -0
  23. data/bin/commands/admin_teams.rb +27 -0
  24. data/bin/commands/admin_teams_admins.rb +16 -0
  25. data/bin/commands/admin_teams_owners.rb +16 -0
  26. data/bin/commands/admin_teams_settings.rb +64 -0
  27. data/bin/commands/admin_users.rb +97 -0
  28. data/bin/commands/admin_users_session.rb +16 -0
  29. data/bin/commands/api.rb +1 -0
  30. data/bin/commands/apps.rb +15 -0
  31. data/bin/commands/apps_permissions.rb +1 -0
  32. data/bin/commands/apps_permissions_resources.rb +1 -0
  33. data/bin/commands/apps_permissions_scopes.rb +1 -0
  34. data/bin/commands/apps_permissions_users.rb +1 -0
  35. data/bin/commands/auth.rb +1 -0
  36. data/bin/commands/bots.rb +1 -0
  37. data/bin/commands/channels.rb +3 -1
  38. data/bin/commands/chat.rb +48 -9
  39. data/bin/commands/chat_scheduledMessages.rb +18 -0
  40. data/bin/commands/conversations.rb +4 -2
  41. data/bin/commands/dialog.rb +1 -0
  42. data/bin/commands/dnd.rb +4 -3
  43. data/bin/commands/emoji.rb +1 -0
  44. data/bin/commands/files.rb +4 -14
  45. data/bin/commands/files_comments.rb +1 -21
  46. data/bin/commands/files_remote.rb +78 -0
  47. data/bin/commands/groups.rb +2 -1
  48. data/bin/commands/im.rb +2 -1
  49. data/bin/commands/migration.rb +1 -0
  50. data/bin/commands/mpim.rb +2 -1
  51. data/bin/commands/oauth.rb +2 -1
  52. data/bin/commands/oauth_v2.rb +17 -0
  53. data/bin/commands/pins.rb +1 -2
  54. data/bin/commands/reactions.rb +2 -3
  55. data/bin/commands/reminders.rb +1 -0
  56. data/bin/commands/rtm.rb +1 -0
  57. data/bin/commands/search.rb +2 -1
  58. data/bin/commands/stars.rb +1 -0
  59. data/bin/commands/team.rb +2 -0
  60. data/bin/commands/team_profile.rb +1 -0
  61. data/bin/commands/usergroups.rb +2 -1
  62. data/bin/commands/usergroups_users.rb +1 -0
  63. data/bin/commands/users.rb +2 -2
  64. data/bin/commands/users_admin.rb +1 -0
  65. data/bin/commands/users_prefs.rb +1 -0
  66. data/bin/commands/users_profile.rb +1 -0
  67. data/bin/commands/views.rb +48 -0
  68. data/bin/slack +2 -3
  69. data/examples/hi_real_time/Gemfile +1 -0
  70. data/examples/hi_real_time/hi.rb +7 -3
  71. data/examples/hi_real_time_and_web/Gemfile +1 -0
  72. data/examples/hi_real_time_and_web/hi.rb +7 -3
  73. data/examples/hi_real_time_async_async/Gemfile +7 -0
  74. data/examples/hi_real_time_async_async/Procfile +2 -0
  75. data/examples/hi_real_time_async_async/hi.rb +41 -0
  76. data/examples/hi_real_time_async_celluloid/Gemfile +1 -0
  77. data/examples/hi_real_time_async_celluloid/hi.rb +7 -3
  78. data/examples/hi_real_time_async_eventmachine/Gemfile +1 -0
  79. data/examples/hi_real_time_async_eventmachine/hi.rb +7 -3
  80. data/examples/hi_web/Gemfile +1 -0
  81. data/examples/hi_web/hi.rb +1 -0
  82. data/examples/new_ticket/Gemfile +1 -0
  83. data/examples/new_ticket/new_ticket.rb +1 -0
  84. data/lib/slack-ruby-client.rb +8 -2
  85. data/lib/slack.rb +1 -0
  86. data/lib/slack/config.rb +1 -0
  87. data/lib/slack/events/config.rb +32 -0
  88. data/lib/slack/events/request.rb +66 -0
  89. data/lib/slack/logger.rb +6 -5
  90. data/lib/slack/messages/formatting.rb +1 -0
  91. data/lib/slack/messages/message.rb +1 -0
  92. data/lib/slack/real_time/api/message.rb +3 -1
  93. data/lib/slack/real_time/api/message_id.rb +1 -0
  94. data/lib/slack/real_time/api/ping.rb +5 -2
  95. data/lib/slack/real_time/api/typing.rb +3 -1
  96. data/lib/slack/real_time/client.rb +97 -28
  97. data/lib/slack/real_time/concurrency.rb +2 -0
  98. data/lib/slack/real_time/concurrency/async.rb +142 -0
  99. data/lib/slack/real_time/concurrency/celluloid.rb +33 -9
  100. data/lib/slack/real_time/concurrency/eventmachine.rb +32 -7
  101. data/lib/slack/real_time/config.rb +7 -2
  102. data/lib/slack/real_time/models.rb +1 -0
  103. data/lib/slack/real_time/models/base.rb +1 -0
  104. data/lib/slack/real_time/models/bot.rb +1 -0
  105. data/lib/slack/real_time/models/channel.rb +1 -0
  106. data/lib/slack/real_time/models/group.rb +1 -0
  107. data/lib/slack/real_time/models/im.rb +1 -0
  108. data/lib/slack/real_time/models/team.rb +1 -0
  109. data/lib/slack/real_time/models/user.rb +1 -0
  110. data/lib/slack/real_time/socket.rb +42 -13
  111. data/lib/slack/real_time/stores.rb +1 -0
  112. data/lib/slack/real_time/stores/base.rb +1 -0
  113. data/lib/slack/real_time/stores/starter.rb +11 -0
  114. data/lib/slack/real_time/stores/store.rb +28 -25
  115. data/lib/slack/version.rb +2 -1
  116. data/lib/slack/web/api/endpoints.rb +41 -0
  117. data/lib/slack/web/api/endpoints/admin_apps.rb +42 -0
  118. data/lib/slack/web/api/endpoints/admin_apps_approved.rb +35 -0
  119. data/lib/slack/web/api/endpoints/admin_apps_requests.rb +33 -0
  120. data/lib/slack/web/api/endpoints/admin_apps_restricted.rb +35 -0
  121. data/lib/slack/web/api/endpoints/admin_conversations.rb +30 -0
  122. data/lib/slack/web/api/endpoints/admin_emoji.rb +88 -0
  123. data/lib/slack/web/api/endpoints/admin_inviteRequests.rb +61 -0
  124. data/lib/slack/web/api/endpoints/admin_inviteRequests_approved.rb +33 -0
  125. data/lib/slack/web/api/endpoints/admin_inviteRequests_denied.rb +33 -0
  126. data/lib/slack/web/api/endpoints/admin_teams.rb +50 -0
  127. data/lib/slack/web/api/endpoints/admin_teams_admins.rb +34 -0
  128. data/lib/slack/web/api/endpoints/admin_teams_owners.rb +34 -0
  129. data/lib/slack/web/api/endpoints/admin_teams_settings.rb +99 -0
  130. data/lib/slack/web/api/endpoints/admin_users.rb +163 -0
  131. data/lib/slack/web/api/endpoints/admin_users_session.rb +28 -0
  132. data/lib/slack/web/api/endpoints/api.rb +1 -0
  133. data/lib/slack/web/api/endpoints/apps.rb +27 -0
  134. data/lib/slack/web/api/endpoints/apps_permissions.rb +1 -0
  135. data/lib/slack/web/api/endpoints/apps_permissions_resources.rb +1 -0
  136. data/lib/slack/web/api/endpoints/apps_permissions_scopes.rb +1 -0
  137. data/lib/slack/web/api/endpoints/apps_permissions_users.rb +1 -0
  138. data/lib/slack/web/api/endpoints/auth.rb +1 -0
  139. data/lib/slack/web/api/endpoints/bots.rb +1 -0
  140. data/lib/slack/web/api/endpoints/channels.rb +3 -0
  141. data/lib/slack/web/api/endpoints/chat.rb +99 -12
  142. data/lib/slack/web/api/endpoints/chat_scheduledMessages.rb +38 -0
  143. data/lib/slack/web/api/endpoints/conversations.rb +4 -1
  144. data/lib/slack/web/api/endpoints/dialog.rb +1 -0
  145. data/lib/slack/web/api/endpoints/dnd.rb +3 -1
  146. data/lib/slack/web/api/endpoints/emoji.rb +1 -0
  147. data/lib/slack/web/api/endpoints/files.rb +4 -13
  148. data/lib/slack/web/api/endpoints/files_comments.rb +1 -33
  149. data/lib/slack/web/api/endpoints/files_remote.rb +127 -0
  150. data/lib/slack/web/api/endpoints/groups.rb +1 -0
  151. data/lib/slack/web/api/endpoints/im.rb +1 -0
  152. data/lib/slack/web/api/endpoints/migration.rb +1 -0
  153. data/lib/slack/web/api/endpoints/mpim.rb +1 -0
  154. data/lib/slack/web/api/endpoints/oauth.rb +2 -1
  155. data/lib/slack/web/api/endpoints/oauth_v2.rb +30 -0
  156. data/lib/slack/web/api/endpoints/pins.rb +2 -4
  157. data/lib/slack/web/api/endpoints/reactions.rb +5 -6
  158. data/lib/slack/web/api/endpoints/reminders.rb +1 -0
  159. data/lib/slack/web/api/endpoints/rtm.rb +1 -0
  160. data/lib/slack/web/api/endpoints/search.rb +1 -0
  161. data/lib/slack/web/api/endpoints/stars.rb +1 -0
  162. data/lib/slack/web/api/endpoints/team.rb +3 -0
  163. data/lib/slack/web/api/endpoints/team_profile.rb +1 -0
  164. data/lib/slack/web/api/endpoints/usergroups.rb +1 -0
  165. data/lib/slack/web/api/endpoints/usergroups_users.rb +1 -0
  166. data/lib/slack/web/api/endpoints/users.rb +1 -2
  167. data/lib/slack/web/api/endpoints/users_admin.rb +1 -0
  168. data/lib/slack/web/api/endpoints/users_prefs.rb +1 -0
  169. data/lib/slack/web/api/endpoints/users_profile.rb +1 -0
  170. data/lib/slack/web/api/endpoints/views.rb +97 -0
  171. data/lib/slack/web/api/error.rb +1 -0
  172. data/lib/slack/web/api/errors.rb +566 -0
  173. data/lib/slack/web/api/errors/slack_error.rb +14 -1
  174. data/lib/slack/web/api/errors/too_many_requests_error.rb +1 -0
  175. data/lib/slack/web/api/mixins.rb +1 -0
  176. data/lib/slack/web/api/mixins/channels.id.rb +1 -0
  177. data/lib/slack/web/api/mixins/groups.id.rb +1 -0
  178. data/lib/slack/web/api/mixins/ids.id.rb +4 -1
  179. data/lib/slack/web/api/mixins/users.id.rb +1 -0
  180. data/lib/slack/web/api/mixins/users.search.rb +1 -0
  181. data/lib/slack/web/api/patches/chat.5.postEphemeral-text-or-attachments.patch +5 -3
  182. data/lib/slack/web/api/patches/chat.6.block-kit-support.patch +69 -0
  183. data/lib/slack/web/api/patches/views.1.view-json.patch +40 -0
  184. data/lib/slack/web/api/patches/views.1.views-published.patch +16 -0
  185. data/lib/slack/web/api/templates/command.erb +1 -0
  186. data/lib/slack/web/api/templates/commands.erb +1 -0
  187. data/lib/slack/web/api/templates/endpoints.erb +1 -0
  188. data/lib/slack/web/api/templates/errors.erb +20 -0
  189. data/lib/slack/web/api/templates/method.erb +1 -0
  190. data/lib/slack/web/api/templates/method_spec.erb +1 -0
  191. data/lib/slack/web/client.rb +2 -1
  192. data/lib/slack/web/config.rb +1 -0
  193. data/lib/slack/web/faraday/connection.rb +1 -0
  194. data/lib/slack/web/faraday/request.rb +1 -0
  195. data/lib/slack/web/faraday/response/raise_error.rb +10 -6
  196. data/lib/slack/web/pagination/cursor.rb +4 -0
  197. data/lib/slack_ruby_client.rb +1 -0
  198. data/lib/tasks/git.rake +1 -0
  199. data/lib/tasks/real_time.rake +15 -5
  200. data/lib/tasks/update.rake +1 -0
  201. data/lib/tasks/web.rake +28 -7
  202. data/screenshots/create-app.png +0 -0
  203. data/slack-ruby-client.gemspec +6 -2
  204. data/spec/fixtures/slack/web/views_open_error.yml +76 -0
  205. data/spec/integration/integration_spec.rb +116 -48
  206. data/spec/slack/config_spec.rb +2 -0
  207. data/spec/slack/events/config_spec.rb +33 -0
  208. data/spec/slack/events/request_spec.rb +179 -0
  209. data/spec/slack/messages/formatting_spec.rb +25 -13
  210. data/spec/slack/real_time/api/message_spec.rb +6 -1
  211. data/spec/slack/real_time/api/ping_spec.rb +2 -0
  212. data/spec/slack/real_time/api/typing_spec.rb +5 -1
  213. data/spec/slack/real_time/client_spec.rb +212 -31
  214. data/spec/slack/real_time/concurrency/celluloid_spec.rb +15 -5
  215. data/spec/slack/real_time/concurrency/eventmachine_spec.rb +11 -1
  216. data/spec/slack/real_time/concurrency/it_behaves_like_a_realtime_socket.rb +2 -0
  217. data/spec/slack/real_time/event_handlers/bot_spec.rb +2 -1
  218. data/spec/slack/real_time/event_handlers/channel_spec.rb +9 -6
  219. data/spec/slack/real_time/event_handlers/event_handlers_spec.rb +2 -1
  220. data/spec/slack/real_time/event_handlers/group_spec.rb +5 -4
  221. data/spec/slack/real_time/event_handlers/im_spec.rb +4 -3
  222. data/spec/slack/real_time/event_handlers/team_spec.rb +3 -1
  223. data/spec/slack/real_time/event_handlers/user_spec.rb +1 -0
  224. data/spec/slack/real_time/rtm_connect_spec.rb +1 -0
  225. data/spec/slack/real_time/rtm_start_spec.rb +1 -0
  226. data/spec/slack/real_time/store_spec.rb +2 -1
  227. data/spec/slack/slack_spec.rb +37 -5
  228. data/spec/slack/version_spec.rb +2 -1
  229. data/spec/slack/web/api/endpoints/admin_apps_approved_spec.rb +8 -0
  230. data/spec/slack/web/api/endpoints/admin_apps_requests_spec.rb +8 -0
  231. data/spec/slack/web/api/endpoints/admin_apps_restricted_spec.rb +8 -0
  232. data/spec/slack/web/api/endpoints/admin_apps_spec.rb +8 -0
  233. data/spec/slack/web/api/endpoints/admin_conversations_spec.rb +13 -0
  234. data/spec/slack/web/api/endpoints/admin_emoji_spec.rb +37 -0
  235. data/spec/slack/web/api/endpoints/admin_inviteRequests_approved_spec.rb +8 -0
  236. data/spec/slack/web/api/endpoints/admin_inviteRequests_denied_spec.rb +8 -0
  237. data/spec/slack/web/api/endpoints/admin_inviteRequests_spec.rb +18 -0
  238. data/spec/slack/web/api/endpoints/admin_teams_admins_spec.rb +13 -0
  239. data/spec/slack/web/api/endpoints/admin_teams_owners_spec.rb +13 -0
  240. data/spec/slack/web/api/endpoints/admin_teams_settings_spec.rb +53 -0
  241. data/spec/slack/web/api/endpoints/admin_teams_spec.rb +16 -0
  242. data/spec/slack/web/api/endpoints/admin_users_session_spec.rb +13 -0
  243. data/spec/slack/web/api/endpoints/admin_users_spec.rb +75 -0
  244. data/spec/slack/web/api/endpoints/api_spec.rb +1 -0
  245. data/spec/slack/web/api/endpoints/apps_permissions_resources_spec.rb +1 -0
  246. data/spec/slack/web/api/endpoints/apps_permissions_scopes_spec.rb +1 -0
  247. data/spec/slack/web/api/endpoints/apps_permissions_spec.rb +3 -2
  248. data/spec/slack/web/api/endpoints/apps_permissions_users_spec.rb +4 -3
  249. data/spec/slack/web/api/endpoints/apps_spec.rb +16 -0
  250. data/spec/slack/web/api/endpoints/bots_spec.rb +1 -0
  251. data/spec/slack/web/api/endpoints/chat_scheduledMessages_spec.rb +8 -0
  252. data/spec/slack/web/api/endpoints/conversations_spec.rb +2 -1
  253. data/spec/slack/web/api/endpoints/custom_specs/auth_spec.rb +5 -1
  254. data/spec/slack/web/api/endpoints/custom_specs/channels_spec.rb +2 -0
  255. data/spec/slack/web/api/endpoints/custom_specs/chat_spec.rb +112 -36
  256. data/spec/slack/web/api/endpoints/custom_specs/dialog_spec.rb +12 -4
  257. data/spec/slack/web/api/endpoints/custom_specs/groups_spec.rb +2 -0
  258. data/spec/slack/web/api/endpoints/custom_specs/users_spec.rb +6 -1
  259. data/spec/slack/web/api/endpoints/custom_specs/views_spec.rb +95 -0
  260. data/spec/slack/web/api/endpoints/dnd_spec.rb +6 -0
  261. data/spec/slack/web/api/endpoints/emoji_spec.rb +1 -0
  262. data/spec/slack/web/api/endpoints/files_comments_spec.rb +1 -19
  263. data/spec/slack/web/api/endpoints/files_remote_spec.rb +24 -0
  264. data/spec/slack/web/api/endpoints/files_spec.rb +1 -0
  265. data/spec/slack/web/api/endpoints/im_spec.rb +1 -0
  266. data/spec/slack/web/api/endpoints/migration_spec.rb +1 -0
  267. data/spec/slack/web/api/endpoints/mpim_spec.rb +1 -0
  268. data/spec/slack/web/api/endpoints/oauth_spec.rb +1 -0
  269. data/spec/slack/web/api/endpoints/oauth_v2_spec.rb +13 -0
  270. data/spec/slack/web/api/endpoints/pins_spec.rb +5 -1
  271. data/spec/slack/web/api/endpoints/reactions_spec.rb +8 -1
  272. data/spec/slack/web/api/endpoints/reminders_spec.rb +1 -0
  273. data/spec/slack/web/api/endpoints/rtm_spec.rb +1 -0
  274. data/spec/slack/web/api/endpoints/search_spec.rb +1 -0
  275. data/spec/slack/web/api/endpoints/stars_spec.rb +1 -0
  276. data/spec/slack/web/api/endpoints/team_profile_spec.rb +1 -0
  277. data/spec/slack/web/api/endpoints/team_spec.rb +1 -0
  278. data/spec/slack/web/api/endpoints/usergroups_spec.rb +1 -0
  279. data/spec/slack/web/api/endpoints/usergroups_users_spec.rb +1 -0
  280. data/spec/slack/web/api/endpoints/users_admin_spec.rb +1 -0
  281. data/spec/slack/web/api/endpoints/users_prefs_spec.rb +1 -0
  282. data/spec/slack/web/api/endpoints/users_profile_spec.rb +1 -0
  283. data/spec/slack/web/api/endpoints/views_spec.rb +29 -0
  284. data/spec/slack/web/api/error_spec.rb +4 -2
  285. data/spec/slack/web/api/errors/service_unavailable_spec.rb +6 -3
  286. data/spec/slack/web/api/errors/slack_error_spec.rb +26 -2
  287. data/spec/slack/web/api/mixins/channels_spec.rb +17 -7
  288. data/spec/slack/web/api/mixins/groups_spec.rb +17 -7
  289. data/spec/slack/web/api/mixins/users_spec.rb +17 -8
  290. data/spec/slack/web/api/pagination/cursor_spec.rb +40 -10
  291. data/spec/slack/web/client_spec.rb +45 -18
  292. data/spec/slack/web/faraday/response/raise_error_spec.rb +41 -7
  293. data/spec/spec_helper.rb +8 -1
  294. data/spec/support/queue_with_timeout.rb +5 -4
  295. data/spec/support/real_time/concurrency/mock.rb +1 -0
  296. data/spec/support/real_time/connected_client.rb +9 -3
  297. data/spec/support/real_time/event.rb +1 -0
  298. data/spec/support/token.rb +1 -0
  299. data/spec/support/vcr.rb +1 -0
  300. metadata +149 -9
  301. data/screenshots/register-bot.png +0 -0
@@ -1,42 +1,54 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Slack::Messages::Formatting do
4
- subject do
5
- Slack::Messages::Formatting
5
+ subject(:formatting) do
6
+ described_class
6
7
  end
8
+
7
9
  context '#unescape' do
8
10
  it 'plain text' do
9
- expect(subject.unescape('plain text')).to eq 'plain text'
11
+ expect(formatting.unescape('plain text')).to eq 'plain text'
10
12
  end
11
13
  it 'decodes an HTML-encoded message' do
12
- expect(subject.unescape('Hello &amp; &lt;world&gt;')).to eq 'Hello & <world>'
14
+ expect(formatting.unescape('Hello &amp; &lt;world&gt;')).to eq 'Hello & <world>'
13
15
  end
14
16
  it 'unescapes a user reference' do
15
- expect(subject.unescape('Hey <@U024BE7LH|bob>, did you see my file?')).to eq 'Hey @bob, did you see my file?'
17
+ expect(formatting.unescape('Hey <@U024BE7LH|bob>, did you see my file?')).to(
18
+ eq('Hey @bob, did you see my file?')
19
+ )
16
20
  end
17
21
  it 'unescapes a user reference without a name' do
18
- expect(subject.unescape('<@U02BEFY4U> ^^^')).to eq '@U02BEFY4U ^^^'
22
+ expect(formatting.unescape('<@U02BEFY4U> ^^^')).to eq '@U02BEFY4U ^^^'
19
23
  end
20
24
  it 'unescapes a URL without text' do
21
- expect(subject.unescape('This message contains a URL <http://foo.com/>')).to eq 'This message contains a URL http://foo.com/'
25
+ expect(formatting.unescape('This message contains a URL <http://foo.com/>')).to(
26
+ eq('This message contains a URL http://foo.com/')
27
+ )
22
28
  end
23
29
  it 'unescapes a URL with text' do
24
- expect(subject.unescape('So does this one: <http://www.foo.com|www.foo.com>')).to eq 'So does this one: www.foo.com'
30
+ expect(formatting.unescape('So does this one: <http://www.foo.com|www.foo.com>')).to(
31
+ eq('So does this one: www.foo.com')
32
+ )
25
33
  end
26
34
  it 'removes mailto' do
27
- expect(subject.unescape('<mailto:bob@example.com|Bob>')).to eq 'Bob'
35
+ expect(formatting.unescape('<mailto:bob@example.com|Bob>')).to eq 'Bob'
28
36
  end
29
37
  it 'unlinkifies references' do
30
- expect(subject.unescape('Hello <@U123|bob>, say hi to <!everyone> in <#C1234|general>')).to eq 'Hello @bob, say hi to @everyone in #general'
38
+ expect(
39
+ formatting.unescape('Hello <@U123|bob>, say hi to <!everyone> in <#C1234|general>')
40
+ ).to(
41
+ eq('Hello @bob, say hi to @everyone in #general')
42
+ )
31
43
  end
32
44
  it 'can handle a lone &gt;' do
33
- expect(subject.unescape('Hello <@U123|bob> &gt; file.txt')).to eq 'Hello @bob > file.txt'
45
+ expect(formatting.unescape('Hello <@U123|bob> &gt; file.txt')).to eq 'Hello @bob > file.txt'
34
46
  end
35
47
  it 'unescapes a double smart quote' do
36
- expect(subject.unescape('“hello”')).to eq '"hello"'
48
+ expect(formatting.unescape('“hello”')).to eq '"hello"'
37
49
  end
38
50
  it 'unescapes a single smart quote' do
39
- expect(subject.unescape('‘hello’')).to eq "'hello'"
51
+ expect(formatting.unescape('‘hello’')).to eq "'hello'"
40
52
  end
41
53
  end
42
54
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe Slack::RealTime::Client, vcr: { cassette_name: 'web/rtm_start' } do
@@ -7,8 +8,12 @@ RSpec.describe Slack::RealTime::Client, vcr: { cassette_name: 'web/rtm_start' }
7
8
  before do
8
9
  allow(client).to receive(:next_id).and_return(42)
9
10
  end
11
+
10
12
  it 'sends message' do
11
- expect(socket).to receive(:send_data).with({ type: 'message', id: 42, text: 'hello world', channel: 'channel' }.to_json)
13
+ expect(socket).to(
14
+ receive(:send_data)
15
+ .with({ type: 'message', id: 42, text: 'hello world', channel: 'channel' }.to_json)
16
+ )
12
17
  client.message(text: 'hello world', channel: 'channel')
13
18
  end
14
19
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe Slack::RealTime::Client, vcr: { cassette_name: 'web/rtm_start' } do
@@ -7,6 +8,7 @@ RSpec.describe Slack::RealTime::Client, vcr: { cassette_name: 'web/rtm_start' }
7
8
  before do
8
9
  allow(client).to receive(:next_id).and_return(42)
9
10
  end
11
+
10
12
  it 'sends message' do
11
13
  expect(socket).to receive(:send_data).with({ type: 'ping', id: 42 }.to_json)
12
14
  client.ping
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  RSpec.describe Slack::RealTime::Client, vcr: { cassette_name: 'web/rtm_start' } do
@@ -7,8 +8,11 @@ RSpec.describe Slack::RealTime::Client, vcr: { cassette_name: 'web/rtm_start' }
7
8
  before do
8
9
  allow(client).to receive(:next_id).and_return(42)
9
10
  end
11
+
10
12
  it 'sends a typing indicator' do
11
- expect(socket).to receive(:send_data).with({ type: 'typing', id: 42, channel: 'channel' }.to_json)
13
+ expect(socket).to(
14
+ receive(:send_data).with({ type: 'typing', id: 42, channel: 'channel' }.to_json)
15
+ )
12
16
  client.typing(channel: 'channel')
13
17
  end
14
18
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
- RSpec.describe Slack::RealTime::Client do
4
+ RSpec.describe Slack::RealTime::Client do # rubocop:disable Metrics/BlockLength
4
5
  let(:ws) { double(Slack::RealTime::Concurrency::Mock::WebSocket, on: true) }
6
+
5
7
  before do
6
8
  @token = ENV.delete('SLACK_API_TOKEN')
7
9
  Slack::Config.reset
@@ -10,49 +12,78 @@ RSpec.describe Slack::RealTime::Client do
10
12
  config.concurrency = Slack::RealTime::Concurrency::Mock
11
13
  end
12
14
  end
15
+
13
16
  after do
14
17
  ENV['SLACK_API_TOKEN'] = @token if @token
15
18
  end
19
+
16
20
  context 'token' do
17
21
  before do
18
22
  Slack.configure do |config|
19
23
  config.token = 'global default'
20
24
  end
21
25
  end
26
+
22
27
  it 'defaults token to global default' do
23
- client = Slack::RealTime::Client.new
28
+ client = described_class.new
24
29
  expect(client.token).to eq 'global default'
25
30
  expect(client.web_client.token).to eq 'global default'
26
31
  end
27
32
  context 'with real time config' do
28
33
  before do
29
- Slack::RealTime::Client.configure do |config|
34
+ described_class.configure do |config|
30
35
  config.token = 'custom real time token'
31
36
  end
32
37
  end
38
+
33
39
  it 'overrides token to real time config' do
34
- client = Slack::RealTime::Client.new
40
+ client = described_class.new
35
41
  expect(client.token).to eq 'custom real time token'
36
42
  expect(client.web_client.token).to eq 'custom real time token'
37
43
  end
38
44
  it 'overrides token to specific token' do
39
- client = Slack::RealTime::Client.new(token: 'local token')
45
+ client = described_class.new(token: 'local token')
40
46
  expect(client.token).to eq 'local token'
41
47
  expect(client.web_client.token).to eq 'local token'
42
48
  end
43
49
  end
44
50
  end
45
- context 'client with a full store', vcr: { cassette_name: 'web/rtm_start' } do
46
- let(:client) { Slack::RealTime::Client.new(store_class: Slack::RealTime::Stores::Store) }
51
+
52
+ context 'websocket_ping_timer' do
53
+ context 'with defaults' do
54
+ let(:client) { described_class.new }
55
+
56
+ it 'defaults to websocket_ping / 2' do
57
+ expect(client.websocket_ping_timer).to eq 15
58
+ end
59
+ end
60
+
61
+ context 'with websocket_ping value changed' do
62
+ let(:client) { described_class.new(websocket_ping: 22) }
63
+
64
+ it 'defaults to websocket_ping / 2' do
65
+ expect(client.websocket_ping_timer).to eq 11
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'client with a full store',
71
+ vcr: { cassette_name: 'web/rtm_start', allow_playback_repeats: true } do
72
+ let(:client) { described_class.new(store_class: Slack::RealTime::Stores::Store) }
47
73
  let(:url) { 'wss://ms173.slack-msgs.com/websocket/lqcUiAvrKTP-uuid=' }
74
+
48
75
  describe '#start!' do
49
76
  let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
77
+
50
78
  before do
51
- allow(Slack::RealTime::Socket).to receive(:new).with(url, ping: 30, logger: Slack::Logger.default).and_return(socket)
79
+ allow(Slack::RealTime::Socket).to(
80
+ receive(:new).with(url, ping: 30, logger: Slack::Logger.default).and_return(socket)
81
+ )
52
82
  allow(socket).to receive(:connect!)
53
83
  allow(socket).to receive(:start_sync)
54
84
  client.start!
55
85
  end
86
+
56
87
  describe 'properties provided upon connection' do
57
88
  it 'sets url' do
58
89
  expect(client.url).to eq url
@@ -86,7 +117,13 @@ RSpec.describe Slack::RealTime::Client do
86
117
  it 'sets groups' do
87
118
  expect(client.groups.count).to eq 1
88
119
  end
120
+ it 'includes team name in to_s' do
121
+ expect(client.to_s).to eq(
122
+ "id=#{client.team.id}, name=#{client.team.name}, domain=#{client.team.domain}"
123
+ )
124
+ end
89
125
  end
126
+
90
127
  it 'uses web client to fetch url' do
91
128
  expect(client.web_client).to be_a Slack::Web::Client
92
129
  end
@@ -100,9 +137,10 @@ RSpec.describe Slack::RealTime::Client do
100
137
  end
101
138
  describe '#stop!' do
102
139
  before do
103
- expect(socket).to receive(:disconnect!)
140
+ allow(socket).to receive(:disconnect!)
104
141
  client.stop!
105
142
  end
143
+
106
144
  it 'cannot be invoked twice' do
107
145
  client.instance_variable_set('@socket', nil) # caused by a :close callback
108
146
  expect do
@@ -110,12 +148,14 @@ RSpec.describe Slack::RealTime::Client do
110
148
  end.to raise_error Slack::RealTime::Client::ClientNotStartedError
111
149
  end
112
150
  end
151
+
113
152
  describe '#next_id' do
114
153
  it 'increments' do
115
154
  previous_id = client.send(:next_id)
116
155
  expect(client.send(:next_id)).to eq previous_id + 1
117
156
  end
118
157
  end
158
+
119
159
  context 'subclassed' do
120
160
  it 'runs event handlers' do
121
161
  event = Slack::RealTime::Event.new(
@@ -127,18 +167,88 @@ RSpec.describe Slack::RealTime::Client do
127
167
  end
128
168
  end
129
169
  end
170
+
171
+ describe '#start_async' do
172
+ let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
173
+
174
+ before do
175
+ allow(Slack::RealTime::Socket).to(
176
+ receive(:new).with(url, ping: 30, logger: Slack::Logger.default).and_return(socket)
177
+ )
178
+ allow(socket).to receive(:connect!)
179
+ allow(socket).to receive(:start_async)
180
+ client.start_async
181
+ end
182
+
183
+ describe '#run_ping!' do
184
+ it 'sends ping messages when the websocket connection is idle' do
185
+ allow(socket).to receive(:time_since_last_message).and_return(30)
186
+ expect(socket).to receive(:send_data).with('{"type":"ping","id":1}')
187
+ client.run_ping!
188
+ end
189
+ it 'reconnects the websocket if it has been idle for too long' do
190
+ allow(socket).to receive(:time_since_last_message).and_return(75)
191
+ allow(socket).to receive(:connected?).and_return(true)
192
+ expect(socket).to receive(:close)
193
+ expect(socket).to receive(:restart_async)
194
+ client.run_ping!
195
+ end
196
+ [
197
+ EOFError,
198
+ Errno::ECONNRESET,
199
+ Errno::EPIPE,
200
+ Faraday::ClientError,
201
+ Slack::Web::Api::Errors::SlackError
202
+ ].each do |err|
203
+ context "raising #{err}" do
204
+ it 'does not terminate the ping worker' do
205
+ allow(socket).to receive(:time_since_last_message) { raise err }
206
+ expect(socket).not_to receive(:send_data)
207
+ client.run_ping!
208
+ end
209
+ end
210
+ end
211
+ context 'raising Slack::Web::Api::Errors::SlackError' do
212
+ %w[invalid_auth account_inactive].each do |code|
213
+ context code do
214
+ it 'does not terminate the ping worker' do
215
+ allow(socket).to receive(:time_since_last_message) {
216
+ raise Slack::Web::Api::Errors::SlackError, code
217
+ }
218
+ expect(socket).not_to receive(:send_data)
219
+ expect do
220
+ client.run_ping!
221
+ end.to raise_error Slack::Web::Api::Errors::SlackError, code
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ describe 'to_s' do
230
+ it 'defaults to class instance' do
231
+ expect(client.to_s).to match(/^#<Slack::RealTime::Client:0x\h+>$/)
232
+ end
233
+ end
130
234
  end
235
+
131
236
  context 'client with starter store', vcr: { cassette_name: 'web/rtm_connect' } do
132
- let(:client) { Slack::RealTime::Client.new(store_class: Slack::RealTime::Stores::Starter) }
237
+ let(:client) { described_class.new(store_class: Slack::RealTime::Stores::Starter) }
133
238
  let(:url) { 'wss://mpmulti-w5tz.slack-msgs.com/websocket/uid' }
239
+
134
240
  describe '#start!' do
135
241
  let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
242
+
136
243
  before do
137
- allow(Slack::RealTime::Socket).to receive(:new).with(url, ping: 30, logger: Slack::Logger.default).and_return(socket)
244
+ allow(Slack::RealTime::Socket).to(
245
+ receive(:new).with(url, ping: 30, logger: Slack::Logger.default).and_return(socket)
246
+ )
138
247
  allow(socket).to receive(:connect!)
139
248
  allow(socket).to receive(:start_sync)
140
249
  client.start!
141
250
  end
251
+
142
252
  describe 'properties provided upon connection' do
143
253
  it 'sets url' do
144
254
  expect(client.url).to eq url
@@ -167,7 +277,13 @@ RSpec.describe Slack::RealTime::Client do
167
277
  it 'no groups' do
168
278
  expect(client.groups).to be_nil
169
279
  end
280
+ it 'includes team name in to_s' do
281
+ expect(client.to_s).to eq(
282
+ "id=#{client.team.id}, name=#{client.team.name}, domain=#{client.team.domain}"
283
+ )
284
+ end
170
285
  end
286
+
171
287
  it 'uses web client to fetch url' do
172
288
  expect(client.web_client).to be_a Slack::Web::Client
173
289
  end
@@ -181,9 +297,10 @@ RSpec.describe Slack::RealTime::Client do
181
297
  end
182
298
  describe '#stop!' do
183
299
  before do
184
- expect(socket).to receive(:disconnect!)
300
+ allow(socket).to receive(:disconnect!)
185
301
  client.stop!
186
302
  end
303
+
187
304
  it 'cannot be invoked twice' do
188
305
  client.instance_variable_set('@socket', nil) # caused by a :close callback
189
306
  expect do
@@ -191,6 +308,7 @@ RSpec.describe Slack::RealTime::Client do
191
308
  end.to raise_error Slack::RealTime::Client::ClientNotStartedError
192
309
  end
193
310
  end
311
+
194
312
  describe '#next_id' do
195
313
  it 'increments' do
196
314
  previous_id = client.send(:next_id)
@@ -199,9 +317,11 @@ RSpec.describe Slack::RealTime::Client do
199
317
  end
200
318
  end
201
319
  end
320
+
202
321
  context 'client with nil store', vcr: { cassette_name: 'web/rtm_connect' } do
203
- let(:client) { Slack::RealTime::Client.new(store_class: nil) }
322
+ let(:client) { described_class.new(store_class: nil) }
204
323
  let(:url) { 'wss://mpmulti-w5tz.slack-msgs.com/websocket/uid' }
324
+
205
325
  it 'sets store to nil' do
206
326
  expect(client.store).to be nil
207
327
  end
@@ -210,7 +330,7 @@ RSpec.describe Slack::RealTime::Client do
210
330
  'type' => 'team_rename',
211
331
  'name' => 'New Team Name Inc.'
212
332
  )
213
- expect(client).to_not receive(:run_handlers)
333
+ expect(client).not_to receive(:run_handlers)
214
334
  client.send(:dispatch, event)
215
335
  end
216
336
  it 'self' do
@@ -219,9 +339,16 @@ RSpec.describe Slack::RealTime::Client do
219
339
  it 'team' do
220
340
  expect(client.team).to be nil
221
341
  end
342
+ describe 'to_s' do
343
+ it 'defaults to class instance' do
344
+ expect(client.to_s).to match(/^#<Slack::RealTime::Client:0x\h+>$/)
345
+ end
346
+ end
222
347
  end
348
+
223
349
  context 'client with defaults' do
224
- let(:client) { Slack::RealTime::Client.new }
350
+ let(:client) { described_class.new }
351
+
225
352
  describe '#initialize' do
226
353
  it 'sets ping' do
227
354
  expect(client.websocket_ping).to eq 30
@@ -241,38 +368,70 @@ RSpec.describe Slack::RealTime::Client do
241
368
  end
242
369
  end
243
370
  end
371
+
372
+ describe '#run_ping?' do
373
+ it 'returns true when websocket_ping is greater than 0' do
374
+ client.websocket_ping = 30
375
+ expect(client.run_ping?).to be true
376
+ end
377
+ it 'returns false when websocket_ping is less than 1' do
378
+ client.websocket_ping = 0
379
+ expect(client.run_ping?).to be false
380
+ client.websocket_ping = nil
381
+ expect(client.run_ping?).to be false
382
+ end
383
+ end
244
384
  end
385
+
245
386
  context 'with custom settings' do
246
387
  describe '#initialize' do
247
388
  Slack::RealTime::Config::ATTRIBUTES.each do |key|
248
- context key do
249
- let(:client) { Slack::RealTime::Client.new(key => 'custom') }
389
+ context key.to_s do
390
+ let(:client) { described_class.new(key => 'custom') }
391
+
250
392
  it "sets #{key}" do
251
- expect(client.send(key)).to_not eq Slack::RealTime::Config.send(key)
393
+ expect(client.send(key)).not_to eq Slack::RealTime::Config.send(key)
252
394
  expect(client.send(key)).to eq 'custom'
253
395
  end
254
396
  end
255
397
  end
256
398
  end
399
+
400
+ describe 'logger accessor' do
401
+ let(:client) { described_class.new }
402
+
403
+ it 'exposes public logger' do
404
+ expect(client.logger).to be_a(::Logger)
405
+ end
406
+ it 'exposes public logger=' do
407
+ expect { client.logger = nil }.not_to raise_error(NoMethodError)
408
+ end
409
+ end
257
410
  end
411
+
258
412
  context 'global config' do
259
413
  after do
260
- Slack::RealTime::Client.config.reset
414
+ described_class.config.reset
261
415
  end
416
+
262
417
  let(:url) { 'wss://ms173.slack-msgs.com/websocket/lqcUiAvrKTP-uuid=' }
263
- let(:client) { Slack::RealTime::Client.new }
418
+ let(:client) { described_class.new }
419
+
264
420
  context 'ping' do
265
421
  before do
266
- Slack::RealTime::Client.configure do |config|
422
+ described_class.configure do |config|
267
423
  config.websocket_ping = 15
268
424
  end
269
425
  end
426
+
270
427
  describe '#initialize' do
271
428
  it 'sets ping' do
272
429
  expect(client.websocket_ping).to eq 15
273
430
  end
274
431
  it 'creates a connection with custom ping', vcr: { cassette_name: 'web/rtm_start' } do
275
- expect(Slack::RealTime::Concurrency::Mock::WebSocket).to receive(:new).with(url, nil, ping: 15).and_return(ws)
432
+ expect(Slack::RealTime::Concurrency::Mock::WebSocket).to(
433
+ receive(:new).with(url, nil, ping: 15).and_return(ws)
434
+ )
276
435
  client.start!
277
436
  end
278
437
  it 'sets start_options' do
@@ -280,15 +439,17 @@ RSpec.describe Slack::RealTime::Client do
280
439
  end
281
440
  end
282
441
  end
442
+
283
443
  context 'proxy' do
284
444
  before do
285
- Slack::RealTime::Client.configure do |config|
445
+ described_class.configure do |config|
286
446
  config.websocket_proxy = {
287
447
  origin: 'http://username:password@proxy.example.com',
288
448
  headers: { 'User-Agent' => 'ruby' }
289
449
  }
290
450
  end
291
451
  end
452
+
292
453
  describe '#initialize' do
293
454
  it 'sets proxy' do
294
455
  expect(client.websocket_proxy).to eq(
@@ -310,40 +471,48 @@ RSpec.describe Slack::RealTime::Client do
310
471
  end
311
472
  end
312
473
  end
474
+
313
475
  context 'start_options' do
314
476
  before do
315
- Slack::RealTime::Client.configure do |config|
477
+ described_class.configure do |config|
316
478
  config.start_options = { simple_latest: true }
317
479
  end
318
480
  end
481
+
319
482
  describe '#initialize' do
320
483
  it 'sets start_options' do
321
484
  expect(client.start_options).to eq(simple_latest: true)
322
485
  end
323
486
  context 'start!' do
324
487
  let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
488
+
325
489
  before do
326
490
  allow(Slack::RealTime::Socket).to receive(:new).and_return(socket)
327
491
  allow(socket).to receive(:connect!)
328
492
  allow(socket).to receive(:start_sync)
329
493
  end
494
+
330
495
  it 'calls rtm_start with start options', vcr: { cassette_name: 'web/rtm_start' } do
331
- expect(client.web_client).to receive(:rtm_start).with(simple_latest: true).and_call_original
496
+ expect(client.web_client).to(
497
+ receive(:rtm_start).with(simple_latest: true).and_call_original
498
+ )
332
499
  client.start!
333
500
  end
334
501
  end
335
502
  end
336
503
  end
504
+
337
505
  context 'store_class' do
338
506
  context 'starter' do
339
507
  before do
340
- Slack::RealTime::Client.configure do |config|
508
+ described_class.configure do |config|
341
509
  config.store_class = Slack::RealTime::Stores::Starter
342
510
  end
343
511
  end
512
+
344
513
  describe '#initialize' do
345
514
  it 'can be overriden explicitly' do
346
- client = Slack::RealTime::Client.new(store_class: Slack::RealTime::Store)
515
+ client = described_class.new(store_class: Slack::RealTime::Store)
347
516
  expect(client.send(:store_class)).to eq Slack::RealTime::Store
348
517
  end
349
518
  it 'sets store_class' do
@@ -351,11 +520,13 @@ RSpec.describe Slack::RealTime::Client do
351
520
  end
352
521
  context 'start!' do
353
522
  let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
523
+
354
524
  before do
355
525
  allow(Slack::RealTime::Socket).to receive(:new).and_return(socket)
356
526
  allow(socket).to receive(:connect!)
357
527
  allow(socket).to receive(:start_sync)
358
528
  end
529
+
359
530
  it 'instantiates the correct store class', vcr: { cassette_name: 'web/rtm_connect' } do
360
531
  client.start!
361
532
  expect(client.store).to be_a Slack::RealTime::Stores::Starter
@@ -363,20 +534,24 @@ RSpec.describe Slack::RealTime::Client do
363
534
  end
364
535
  end
365
536
  end
537
+
366
538
  context 'store' do
367
539
  before do
368
- Slack::RealTime::Client.configure do |config|
540
+ described_class.configure do |config|
369
541
  config.store_class = Slack::RealTime::Stores::Store
370
542
  end
371
543
  end
544
+
372
545
  describe '#initialize' do
373
546
  context 'start!' do
374
547
  let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
548
+
375
549
  before do
376
550
  allow(Slack::RealTime::Socket).to receive(:new).and_return(socket)
377
551
  allow(socket).to receive(:connect!)
378
552
  allow(socket).to receive(:start_sync)
379
553
  end
554
+
380
555
  it 'calls rtm_start and not rtm_connect', vcr: { cassette_name: 'web/rtm_start' } do
381
556
  expect(client.web_client).to receive(:rtm_start).and_call_original
382
557
  client.start!
@@ -385,18 +560,20 @@ RSpec.describe Slack::RealTime::Client do
385
560
  end
386
561
  end
387
562
  end
563
+
388
564
  context 'start_method' do
389
565
  describe '#initialize' do
390
566
  it 'can be overriden explicitly' do
391
- client = Slack::RealTime::Client.new(start_method: :overriden)
567
+ client = described_class.new(start_method: :overriden)
392
568
  expect(client.send(:start_method)).to eq :overriden
393
569
  end
394
570
  context 'with start_method' do
395
571
  before do
396
- Slack::RealTime::Client.configure do |config|
572
+ described_class.configure do |config|
397
573
  config.start_method = :overriden
398
574
  end
399
575
  end
576
+
400
577
  it 'sets start_method' do
401
578
  expect(client.send(:start_method)).to eq :overriden
402
579
  end
@@ -407,14 +584,18 @@ RSpec.describe Slack::RealTime::Client do
407
584
  end.to raise_error RuntimeError, 'overriden'
408
585
  end
409
586
  end
587
+
410
588
  context 'start!' do
411
589
  let(:socket) { double(Slack::RealTime::Socket, connected?: true) }
590
+
412
591
  before do
413
592
  allow(Slack::RealTime::Socket).to receive(:new).and_return(socket)
414
593
  allow(socket).to receive(:connect!)
415
594
  allow(socket).to receive(:start_sync)
416
595
  end
417
- it 'defaults to :rtm_start when using full store', vcr: { cassette_name: 'web/rtm_start' } do
596
+
597
+ it 'defaults to :rtm_start when using full store',
598
+ vcr: { cassette_name: 'web/rtm_start' } do
418
599
  expect(client.web_client).to receive(:rtm_start).and_call_original
419
600
  client.start!
420
601
  end