hubot 5.0.4 → 5.0.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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/robot.js +0 -11
  3. package/.editorconfig +0 -14
  4. package/.github/stale.yml +0 -23
  5. package/.github/workflows/nodejs-macos.yml +0 -26
  6. package/.github/workflows/nodejs-ubuntu.yml +0 -28
  7. package/.github/workflows/nodejs-windows.yml +0 -26
  8. package/.github/workflows/release.yml +0 -37
  9. package/bin/e2e-test.sh +0 -47
  10. package/docs/adapters/campfire.md +0 -79
  11. package/docs/adapters/development.md +0 -125
  12. package/docs/adapters/shell.md +0 -24
  13. package/docs/adapters.md +0 -27
  14. package/docs/deploying/azure.md +0 -97
  15. package/docs/deploying/bluemix.md +0 -111
  16. package/docs/deploying/heroku.md +0 -66
  17. package/docs/deploying/unix.md +0 -72
  18. package/docs/deploying/windows.md +0 -66
  19. package/docs/deploying.md +0 -11
  20. package/docs/implementation.md +0 -55
  21. package/docs/index.md +0 -125
  22. package/docs/patterns.md +0 -265
  23. package/docs/scripting.md +0 -1051
  24. package/examples/hubot-start.ps1 +0 -12
  25. package/examples/hubot.service +0 -27
  26. package/script/bootstrap +0 -3
  27. package/script/release +0 -44
  28. package/script/server +0 -3
  29. package/script/smoke-test +0 -3
  30. package/script/test +0 -3
  31. package/test/adapter_test.js +0 -97
  32. package/test/brain_test.js +0 -336
  33. package/test/datastore_test.js +0 -154
  34. package/test/es2015_test.js +0 -199
  35. package/test/fixtures/MockAdapter.coffee +0 -10
  36. package/test/fixtures/MockAdapter.mjs +0 -43
  37. package/test/fixtures/TestScript.coffee +0 -9
  38. package/test/fixtures/TestScript.js +0 -13
  39. package/test/fixtures/mock-adapter.js +0 -35
  40. package/test/listener_test.js +0 -379
  41. package/test/message_test.js +0 -46
  42. package/test/middleware_test.js +0 -507
  43. package/test/robot_test.js +0 -1153
  44. package/test/shell_test.js +0 -73
  45. package/test/user_test.js +0 -29
@@ -1,199 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it */
4
- /* eslint-disable no-unused-expressions */
5
-
6
- // Assertions and Stubbing
7
- const chai = require('chai')
8
- const sinon = require('sinon')
9
- chai.use(require('sinon-chai'))
10
- const mockery = require('mockery')
11
-
12
- const expect = chai.expect
13
-
14
- // Hubot classes
15
- const Hubot = require('../es2015')
16
- const User = Hubot.User
17
- const Brain = Hubot.Brain
18
- const Robot = Hubot.Robot
19
- const Adapter = Hubot.Adapter
20
- const Response = Hubot.Response
21
- const Listener = Hubot.Listener
22
- const TextListener = Hubot.TextListener
23
- const Message = Hubot.Message
24
- const TextMessage = Hubot.TextMessage
25
- const EnterMessage = Hubot.EnterMessage
26
- const LeaveMessage = Hubot.LeaveMessage
27
- const TopicMessage = Hubot.TopicMessage
28
- const CatchAllMessage = Hubot.CatchAllMessage
29
- const loadBot = Hubot.loadBot
30
-
31
- describe('hubot/es2015', function () {
32
- it('exports User class', function () {
33
- class MyUser extends User {}
34
- const user = new MyUser('id123', { foo: 'bar' })
35
-
36
- expect(user).to.be.an.instanceof(User)
37
- expect(user.id).to.equal('id123')
38
- expect(user.foo).to.equal('bar')
39
- })
40
-
41
- it('exports Brain class', function () {
42
- class MyBrain extends Brain {}
43
- const robotMock = {
44
- on: sinon.spy()
45
- }
46
- const brain = new MyBrain(robotMock)
47
-
48
- expect(brain).to.be.an.instanceof(Brain)
49
- expect(robotMock.on).to.have.been.called
50
-
51
- brain.set('foo', 'bar')
52
- expect(brain.get('foo')).to.equal('bar')
53
- })
54
-
55
- it('exports Robot class', async function () {
56
- mockery.enable({
57
- warnOnReplace: false,
58
- warnOnUnregistered: false
59
- })
60
- mockery.registerMock('hubot-mock-adapter', require('./fixtures/mock-adapter.js'))
61
-
62
- class MyRobot extends Robot {}
63
- const robot = new MyRobot('mock-adapter', false, 'TestHubot')
64
- await robot.loadAdapter()
65
- expect(robot).to.be.an.instanceof(Robot)
66
- expect(robot.name).to.equal('TestHubot')
67
-
68
- mockery.disable()
69
- })
70
-
71
- it('exports Adapter class', function () {
72
- class MyAdapter extends Adapter {}
73
- const adapter = new MyAdapter('myrobot')
74
-
75
- expect(adapter).to.be.an.instanceof(Adapter)
76
- expect(adapter.robot).to.equal('myrobot')
77
- })
78
-
79
- it('exports Response class', function () {
80
- class MyResponse extends Response {}
81
- const robotMock = 'robotMock'
82
- const messageMock = {
83
- room: 'room',
84
- user: 'user'
85
- }
86
- const matchMock = 'matchMock'
87
- const response = new MyResponse(robotMock, messageMock, matchMock)
88
-
89
- expect(response).to.be.an.instanceof(Response)
90
- expect(response.message).to.equal(messageMock)
91
- expect(response.match).to.equal(matchMock)
92
- })
93
-
94
- it('exports Listener class', function () {
95
- class MyListener extends Listener {}
96
- const robotMock = 'robotMock'
97
- const matcherMock = 'matchMock'
98
- const callback = sinon.spy()
99
- const listener = new MyListener(robotMock, matcherMock, callback)
100
-
101
- expect(listener).to.be.an.instanceof(Listener)
102
- expect(listener.robot).to.equal(robotMock)
103
- expect(listener.matcher).to.equal(matcherMock)
104
- expect(listener.options).to.deep.include({
105
- id: null
106
- })
107
- expect(listener.callback).to.equal(callback)
108
- })
109
-
110
- it('exports TextListener class', function () {
111
- class MyTextListener extends TextListener {}
112
- const robotMock = 'robotMock'
113
- const regex = /regex/
114
- const callback = sinon.spy()
115
- const textListener = new MyTextListener(robotMock, regex, callback)
116
-
117
- expect(textListener).to.be.an.instanceof(TextListener)
118
- expect(textListener.regex).to.equal(regex)
119
- })
120
-
121
- it('exports Message class', function () {
122
- class MyMessage extends Message {}
123
- const userMock = {
124
- room: 'room'
125
- }
126
- const message = new MyMessage(userMock)
127
-
128
- expect(message).to.be.an.instanceof(Message)
129
- expect(message.user).to.equal(userMock)
130
- })
131
-
132
- it('exports TextMessage class', function () {
133
- class MyTextMessage extends TextMessage {}
134
- const userMock = {
135
- room: 'room'
136
- }
137
- const textMessage = new MyTextMessage(userMock, 'bla blah')
138
-
139
- expect(textMessage).to.be.an.instanceof(TextMessage)
140
- expect(textMessage).to.be.an.instanceof(Message)
141
- expect(textMessage.text).to.equal('bla blah')
142
- })
143
-
144
- it('exports EnterMessage class', function () {
145
- class MyEnterMessage extends EnterMessage {}
146
- const userMock = {
147
- room: 'room'
148
- }
149
- const enterMessage = new MyEnterMessage(userMock)
150
-
151
- expect(enterMessage).to.be.an.instanceof(EnterMessage)
152
- expect(enterMessage).to.be.an.instanceof(Message)
153
- })
154
-
155
- it('exports LeaveMessage class', function () {
156
- class MyLeaveMessage extends LeaveMessage {}
157
- const userMock = {
158
- room: 'room'
159
- }
160
- const leaveMessage = new MyLeaveMessage(userMock)
161
-
162
- expect(leaveMessage).to.be.an.instanceof(LeaveMessage)
163
- expect(leaveMessage).to.be.an.instanceof(Message)
164
- })
165
-
166
- it('exports TopicMessage class', function () {
167
- class MyTopicMessage extends TopicMessage {}
168
- const userMock = {
169
- room: 'room'
170
- }
171
- const topicMessage = new MyTopicMessage(userMock)
172
-
173
- expect(topicMessage).to.be.an.instanceof(TopicMessage)
174
- expect(topicMessage).to.be.an.instanceof(Message)
175
- })
176
-
177
- it('exports CatchAllMessage class', function () {
178
- class MyCatchAllMessage extends CatchAllMessage {}
179
- const messageMock = {
180
- user: {
181
- room: 'room'
182
- }
183
- }
184
- const catchAllMessage = new MyCatchAllMessage(messageMock)
185
-
186
- expect(catchAllMessage).to.be.an.instanceof(CatchAllMessage)
187
- expect(catchAllMessage).to.be.an.instanceof(Message)
188
- expect(catchAllMessage.message).to.equal(messageMock)
189
- expect(catchAllMessage.user).to.equal(messageMock.user)
190
- })
191
-
192
- it('exports loadBot function', function () {
193
- sinon.stub(Hubot, 'Robot')
194
-
195
- expect(loadBot).to.be.a('function')
196
- Hubot.loadBot(null, 'adapter', 'enableHttpd', 'botName', 'botAlias')
197
- expect(Hubot.Robot).to.be.called.calledWith(null, 'adapter', 'enableHttpd', 'botName', 'botAlias')
198
- })
199
- })
@@ -1,10 +0,0 @@
1
- { Adapter } = require "../../index.js"
2
- class MockAdapter extends Adapter
3
- constructor: (robot, @options) ->
4
- super robot
5
- @name = "MockAdapter"
6
- run: ->
7
- @emit "connected"
8
-
9
- module.exports.use = (robot) ->
10
- new MockAdapter robot
@@ -1,43 +0,0 @@
1
- 'use strict'
2
-
3
- import { Adapter } from '../../es2015.js' // eslint-disable-line import/no-unresolved
4
-
5
- class MockAdapter extends Adapter {
6
- constructor (robot) {
7
- super(robot)
8
- this.name = 'MockAdapter'
9
- }
10
-
11
- send (envelope, ...strings) {
12
- this.emit('send', envelope, strings)
13
- }
14
-
15
- reply (envelope, ...strings) {
16
- this.emit('reply', envelope, strings)
17
- }
18
-
19
- topic (envelope, ...strings) {
20
- this.emit('topic', envelope, strings)
21
- }
22
-
23
- play (envelope, ...strings) {
24
- this.emit('play', envelope, strings)
25
- }
26
-
27
- run () {
28
- // This is required to get the scripts loaded
29
- this.emit('connected')
30
- }
31
-
32
- close () {
33
- this.emit('closed')
34
- }
35
- }
36
- export {
37
- MockAdapter
38
- }
39
- export default {
40
- use (robot) {
41
- return new MockAdapter(robot)
42
- }
43
- }
@@ -1,9 +0,0 @@
1
- # Description: A test script for the robot to load
2
- #
3
- # Commands:
4
- # hubot test - Responds with a test response
5
- #
6
-
7
- module.exports = (robot) ->
8
- robot.respond 'test', (res) ->
9
- res.send 'test response from coffeescript'
@@ -1,13 +0,0 @@
1
- 'use strict'
2
-
3
- // Description: A test script for the robot to load
4
- //
5
- // Commands:
6
- // hubot test - Responds with a test response
7
- //
8
-
9
- module.exports = robot => {
10
- robot.respond('test', res => {
11
- res.send('test response')
12
- })
13
- }
@@ -1,35 +0,0 @@
1
- 'use strict'
2
-
3
- const Adapter = require('../..').Adapter
4
-
5
- class MockAdapter extends Adapter {
6
- send (envelope/* , ...strings */) {
7
- const strings = [].slice.call(arguments, 1)
8
- this.emit('send', envelope, strings)
9
- }
10
-
11
- reply (envelope/* , ...strings */) {
12
- const strings = [].slice.call(arguments, 1)
13
- this.emit('reply', envelope, strings)
14
- }
15
-
16
- topic (envelope/* , ...strings */) {
17
- const strings = [].slice.call(arguments, 1)
18
- this.emit('topic', envelope, strings)
19
- }
20
-
21
- play (envelope/* , ...strings */) {
22
- const strings = [].slice.call(arguments, 1)
23
- this.emit('play', envelope, strings)
24
- }
25
-
26
- run () {
27
- this.emit('connected')
28
- }
29
-
30
- close () {
31
- this.emit('closed')
32
- }
33
- }
34
-
35
- module.exports.use = robot => new MockAdapter(robot)
@@ -1,379 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, beforeEach, it */
4
- /* eslint-disable no-unused-expressions */
5
-
6
- // Assertions and Stubbing
7
- const chai = require('chai')
8
- const sinon = require('sinon')
9
- chai.use(require('sinon-chai'))
10
-
11
- const expect = chai.expect
12
-
13
- // Hubot classes
14
- const EnterMessage = require('../src/message').EnterMessage
15
- const TextMessage = require('../src/message').TextMessage
16
- const Listener = require('../src/listener').Listener
17
- const TextListener = require('../src/listener').TextListener
18
- const Response = require('../src/response')
19
- const User = require('../src/user')
20
-
21
- describe('Listener', function () {
22
- beforeEach(function () {
23
- // Dummy robot
24
- this.robot = {
25
- // Re-throw AssertionErrors for clearer test failures
26
- emit (name, err, response) {
27
- if (err.constructor.name === 'AssertionError') {
28
- return process.nextTick(function () {
29
- throw err
30
- })
31
- }
32
- },
33
- // Ignore log messages
34
- logger: {
35
- debug () {}
36
- },
37
- // Why is this part of the Robot object??
38
- Response
39
- }
40
-
41
- // Test user
42
- this.user = new User({
43
- id: 1,
44
- name: 'hubottester',
45
- room: '#mocha'
46
- })
47
- })
48
-
49
- describe('Unit Tests', function () {
50
- describe('#call', function () {
51
- it('calls the matcher', function (done) {
52
- const callback = sinon.spy()
53
- const testMatcher = sinon.spy()
54
- const testMessage = {}
55
-
56
- const testListener = new Listener(this.robot, testMatcher, callback)
57
- testListener.call(testMessage, function (_) {
58
- expect(testMatcher).to.have.been.calledWith(testMessage)
59
- done()
60
- })
61
- })
62
-
63
- it('passes the matcher result on to the listener callback', function (done) {
64
- const matcherResult = {}
65
- const testMatcher = sinon.stub().returns(matcherResult)
66
- const testMessage = {}
67
- const listenerCallback = response => expect(response.match).to.be.equal(matcherResult)
68
-
69
- // sanity check; matcherResult must be truthy
70
- expect(matcherResult).to.be.ok
71
-
72
- const testListener = new Listener(this.robot, testMatcher, listenerCallback)
73
- testListener.call(testMessage, function (result) {
74
- // sanity check; message should have been processed
75
- expect(testMatcher).to.have.been.called
76
- expect(result).to.be.ok
77
-
78
- done()
79
- })
80
- })
81
-
82
- describe('if the matcher returns true', function () {
83
- beforeEach(function () {
84
- this.createListener = function (cb) {
85
- return new Listener(this.robot, sinon.stub().returns(true), cb)
86
- }
87
- })
88
-
89
- it('executes the listener callback', function (done) {
90
- const listenerCallback = sinon.spy()
91
- const testMessage = {}
92
-
93
- const testListener = this.createListener(listenerCallback)
94
- testListener.call(testMessage, function (_) {
95
- expect(listenerCallback).to.have.been.called
96
- done()
97
- })
98
- })
99
-
100
- it('returns true', function () {
101
- const testMessage = {}
102
-
103
- const testListener = this.createListener(function () {})
104
- const result = testListener.call(testMessage)
105
- expect(result).to.be.ok
106
- })
107
-
108
- it('calls the provided callback with true', function (done) {
109
- const testMessage = {}
110
-
111
- const testListener = this.createListener(function () {})
112
- testListener.call(testMessage, function (result) {
113
- expect(result).to.be.ok
114
- done()
115
- })
116
- })
117
-
118
- it('calls the provided callback after the function returns', function (done) {
119
- const testMessage = {}
120
-
121
- const testListener = this.createListener(function () {})
122
- let finished = false
123
- testListener.call(testMessage, function (result) {
124
- expect(finished).to.be.ok
125
- done()
126
- })
127
- finished = true
128
- })
129
-
130
- it('handles uncaught errors from the listener callback', function (done) {
131
- const testMessage = {}
132
- const theError = new Error()
133
-
134
- const listenerCallback = function (response) {
135
- throw theError
136
- }
137
-
138
- this.robot.emit = function (name, err, response) {
139
- expect(name).to.equal('error')
140
- expect(err).to.equal(theError)
141
- expect(response.message).to.equal(testMessage)
142
- done()
143
- }
144
-
145
- const testListener = this.createListener(listenerCallback)
146
- testListener.call(testMessage, sinon.spy())
147
- })
148
-
149
- it('calls the provided callback with true if there is an error thrown by the listener callback', function (done) {
150
- const testMessage = {}
151
- const theError = new Error()
152
-
153
- const listenerCallback = function (response) {
154
- throw theError
155
- }
156
-
157
- const testListener = this.createListener(listenerCallback)
158
- testListener.call(testMessage, function (result) {
159
- expect(result).to.be.ok
160
- done()
161
- })
162
- })
163
-
164
- it('calls the listener callback with a Response that wraps the Message', function (done) {
165
- const testMessage = {}
166
-
167
- const listenerCallback = function (response) {
168
- expect(response.message).to.equal(testMessage)
169
- done()
170
- }
171
-
172
- const testListener = this.createListener(listenerCallback)
173
-
174
- testListener.call(testMessage, sinon.spy())
175
- })
176
-
177
- it('passes through the provided middleware stack', function (testDone) {
178
- const testMessage = {}
179
-
180
- const testListener = this.createListener(function () {})
181
- const testMiddleware = {
182
- execute (context, next, done) {
183
- expect(context.listener).to.be.equal(testListener)
184
- expect(context.response).to.be.instanceof(Response)
185
- expect(context.response.message).to.be.equal(testMessage)
186
- expect(next).to.be.a('function')
187
- expect(done).to.be.a('function')
188
- testDone()
189
- }
190
- }
191
-
192
- testListener.call(testMessage, testMiddleware, sinon.spy())
193
- })
194
-
195
- it('executes the listener callback if middleware succeeds', function (testDone) {
196
- const listenerCallback = sinon.spy()
197
- const testMessage = {}
198
-
199
- const testListener = this.createListener(listenerCallback)
200
-
201
- testListener.call(testMessage, function (result) {
202
- expect(listenerCallback).to.have.been.called
203
- // Matcher matched, so we true
204
- expect(result).to.be.ok
205
- testDone()
206
- })
207
- })
208
-
209
- it('does not execute the listener callback if middleware fails', function (testDone) {
210
- const listenerCallback = sinon.spy()
211
- const testMessage = {}
212
-
213
- const testListener = this.createListener(listenerCallback)
214
- const testMiddleware = {
215
- execute (context, next, done) {
216
- // Middleware fails
217
- done()
218
- }
219
- }
220
-
221
- testListener.call(testMessage, testMiddleware, function (result) {
222
- expect(listenerCallback).to.not.have.been.called
223
- // Matcher still matched, so we true
224
- expect(result).to.be.ok
225
- testDone()
226
- })
227
- })
228
-
229
- it('unwinds the middleware stack if there is an error in the listener callback', function (testDone) {
230
- const listenerCallback = sinon.stub().throws(new Error())
231
- const testMessage = {}
232
- let extraDoneFunc = null
233
-
234
- const testListener = this.createListener(listenerCallback)
235
- const testMiddleware = {
236
- execute (context, next, done) {
237
- extraDoneFunc = sinon.spy(done)
238
- next(context, extraDoneFunc)
239
- }
240
- }
241
-
242
- testListener.call(testMessage, testMiddleware, function (result) {
243
- // Listener callback was called (and failed)
244
- expect(listenerCallback).to.have.been.called
245
- // Middleware stack was unwound correctly
246
- expect(extraDoneFunc).to.have.been.called
247
- // Matcher still matched, so we true
248
- expect(result).to.be.ok
249
- testDone()
250
- })
251
- })
252
- })
253
-
254
- describe('if the matcher returns false', function () {
255
- beforeEach(function () {
256
- this.createListener = function (cb) {
257
- return new Listener(this.robot, sinon.stub().returns(false), cb)
258
- }
259
- })
260
-
261
- it('does not execute the listener callback', function (done) {
262
- const listenerCallback = sinon.spy()
263
- const testMessage = {}
264
-
265
- const testListener = this.createListener(listenerCallback)
266
- testListener.call(testMessage, function (_) {
267
- expect(listenerCallback).to.not.have.been.called
268
- done()
269
- })
270
- })
271
-
272
- it('returns false', function () {
273
- const testMessage = {}
274
-
275
- const testListener = this.createListener(function () {})
276
- const result = testListener.call(testMessage)
277
- expect(result).to.not.be.ok
278
- })
279
-
280
- it('calls the provided callback with false', function (done) {
281
- const testMessage = {}
282
-
283
- const testListener = this.createListener(function () {})
284
- testListener.call(testMessage, function (result) {
285
- expect(result).to.not.be.ok
286
- done()
287
- })
288
- })
289
-
290
- it('calls the provided callback after the function returns', function (done) {
291
- const testMessage = {}
292
-
293
- const testListener = this.createListener(function () {})
294
- let finished = false
295
- testListener.call(testMessage, function (result) {
296
- expect(finished).to.be.ok
297
- done()
298
- })
299
- finished = true
300
- })
301
-
302
- it('does not execute any middleware', function (done) {
303
- const testMessage = {}
304
-
305
- const testListener = this.createListener(function () {})
306
- const testMiddleware = { execute: sinon.spy() }
307
-
308
- testListener.call(testMessage, result => {
309
- expect(testMiddleware.execute).to.not.have.been.called
310
- done()
311
- })
312
- })
313
- })
314
- })
315
-
316
- describe('#constructor', function () {
317
- it('requires a matcher', () => expect(function () {
318
- return new Listener(this.robot, undefined, {}, sinon.spy())
319
- }).to.throw(Error))
320
-
321
- it('requires a callback', function () {
322
- // No options
323
- expect(function () {
324
- return new Listener(this.robot, sinon.spy())
325
- }).to.throw(Error)
326
- // With options
327
- expect(function () {
328
- return new Listener(this.robot, sinon.spy(), {})
329
- }).to.throw(Error)
330
- })
331
-
332
- it('gracefully handles missing options', function () {
333
- const testMatcher = sinon.spy()
334
- const listenerCallback = sinon.spy()
335
- const testListener = new Listener(this.robot, testMatcher, listenerCallback)
336
- // slightly brittle because we are testing for the default options Object
337
- expect(testListener.options).to.deep.equal({ id: null })
338
- expect(testListener.callback).to.be.equal(listenerCallback)
339
- })
340
-
341
- it('gracefully handles a missing ID (set to null)', function () {
342
- const testMatcher = sinon.spy()
343
- const listenerCallback = sinon.spy()
344
- const testListener = new Listener(this.robot, testMatcher, {}, listenerCallback)
345
- expect(testListener.options.id).to.be.null
346
- })
347
- })
348
-
349
- describe('TextListener', () =>
350
- describe('#matcher', function () {
351
- it('matches TextMessages', function () {
352
- const callback = sinon.spy()
353
- const testMessage = new TextMessage(this.user, 'test')
354
- testMessage.match = sinon.stub().returns(true)
355
- const testRegex = /test/
356
-
357
- const testListener = new TextListener(this.robot, testRegex, callback)
358
- const result = testListener.matcher(testMessage)
359
-
360
- expect(result).to.be.ok
361
- expect(testMessage.match).to.have.been.calledWith(testRegex)
362
- })
363
-
364
- it('does not match EnterMessages', function () {
365
- const callback = sinon.spy()
366
- const testMessage = new EnterMessage(this.user)
367
- testMessage.match = sinon.stub().returns(true)
368
- const testRegex = /test/
369
-
370
- const testListener = new TextListener(this.robot, testRegex, callback)
371
- const result = testListener.matcher(testMessage)
372
-
373
- expect(result).to.not.be.ok
374
- expect(testMessage.match).to.not.have.been.called
375
- })
376
- })
377
- )
378
- })
379
- })