1 | _ = require('lodash')
|
2 | sinon = require('sinon')
|
3 | chai = require('chai')
|
4 | chai.use(require('sinon-chai'))
|
5 | expect = chai.expect
|
6 | Command = require('../lib/command')
|
7 | Option = require('../lib/option')
|
8 | Signature = require('../lib/signature')
|
9 | settings = require('../lib/settings')
|
10 | state = require('../lib/state')
|
11 | utils = require('../lib/utils')
|
12 |
|
13 | describe 'Command:', ->
|
14 |
|
15 | describe '#constructor()', ->
|
16 |
|
17 | describe 'signature option', ->
|
18 |
|
19 | it 'should throw an error if no signature', ->
|
20 | expect ->
|
21 | new Command(action: _.noop)
|
22 | .to.throw(Error)
|
23 |
|
24 | it 'should throw an error if signature is not a string', ->
|
25 | expect ->
|
26 | new Command
|
27 | signature: new Signature([ 1, 2, 3 ])
|
28 | .to.throw(Error)
|
29 |
|
30 | describe 'action option', ->
|
31 |
|
32 | it 'should throw an error if no action', ->
|
33 | expect ->
|
34 | new Command
|
35 | signature: new Signature('foo')
|
36 | .to.throw(Error)
|
37 |
|
38 | it 'should throw an error if action is not a function', ->
|
39 | expect ->
|
40 | new Command
|
41 | signature: new Signature('foo')
|
42 | action: 'bar'
|
43 | .to.throw(Error)
|
44 |
|
45 | describe 'options option', ->
|
46 |
|
47 | it 'should throw an error if not array', ->
|
48 | expect ->
|
49 | new Command
|
50 | signature: new Signature('hello')
|
51 | action: _.noop
|
52 | options: { hello: boolean: true }
|
53 | .to.throw(Error)
|
54 |
|
55 | it 'should default to an empty array', ->
|
56 | command = new Command
|
57 | signature: new Signature('hello')
|
58 | action: _.noop
|
59 |
|
60 | expect(command.options).to.deep.equal([])
|
61 |
|
62 | it 'should parse each option', ->
|
63 | command = new Command
|
64 | signature: new Signature('hello')
|
65 | action: _.noop
|
66 | options: [
|
67 | new Option
|
68 | signature: new Signature('quiet')
|
69 | boolean: true
|
70 | new Option
|
71 | signature: new Signature('config')
|
72 | parameter: 'name'
|
73 | ]
|
74 |
|
75 | expect(command.options).to.be.an.instanceof(Array)
|
76 | for option in command.options
|
77 | expect(option).to.be.an.instanceof(Option)
|
78 |
|
79 | describe '#option()', ->
|
80 |
|
81 | it 'should throw an error if option is not an instance of Option', ->
|
82 | command = new Command
|
83 | signature: new Signature('hello')
|
84 | action: _.noop
|
85 |
|
86 | expect ->
|
87 | command.option({ option: 'world' })
|
88 | .to.throw(Error)
|
89 |
|
90 | it 'should add an option', ->
|
91 | command = new Command
|
92 | signature: new Signature('hello')
|
93 | action: _.noop
|
94 |
|
95 | expect(command.options).to.have.length(0)
|
96 | command.option new Option
|
97 | signature: new Signature('quiet')
|
98 | boolean: true
|
99 | expect(command.options).to.have.length(1)
|
100 | expect(command.options[0].signature.toString()).to.equal('quiet')
|
101 |
|
102 | it 'should not add duplicated options', ->
|
103 | command = new Command
|
104 | signature: new Signature('hello')
|
105 | action: _.noop
|
106 |
|
107 | option = new Option
|
108 | signature: new Signature('quiet')
|
109 | boolean: true
|
110 |
|
111 | expect(command.options).to.have.length(0)
|
112 | command.option(option)
|
113 | expect(command.options).to.have.length(1)
|
114 | command.option(option)
|
115 | expect(command.options).to.have.length(1)
|
116 |
|
117 | describe '#applyPermissions()', ->
|
118 |
|
119 | beforeEach ->
|
120 | state.permissions = {}
|
121 |
|
122 | describe 'given a command without permissions', ->
|
123 |
|
124 | beforeEach ->
|
125 | @command = new Command
|
126 | signature: new Signature('hello')
|
127 | action: _.noop
|
128 |
|
129 | it 'should call the callback without errors', ->
|
130 | spy = sinon.spy()
|
131 | @command.applyPermissions(spy)
|
132 | expect(spy).to.have.been.calledOnce
|
133 | expect(spy.firstCall.args).to.deep.equal([])
|
134 |
|
135 | describe 'given a command with permissions', ->
|
136 |
|
137 | beforeEach ->
|
138 | @command = new Command
|
139 | signature: new Signature('hello')
|
140 | action: _.noop
|
141 | permission: 'user'
|
142 |
|
143 | it 'should continue if permission is found and does not return an error', ->
|
144 | state.permissions.user = (done) -> done()
|
145 | spy = sinon.spy()
|
146 | @command.applyPermissions(spy)
|
147 | expect(spy).to.have.been.calledOnce
|
148 | expect(spy.firstCall.args).to.deep.equal([])
|
149 |
|
150 | it 'should return an error if permission was not found', ->
|
151 | spy = sinon.spy()
|
152 | @command.applyPermissions(spy)
|
153 | expect(spy).to.have.been.calledOnce
|
154 | args = spy.firstCall.args
|
155 | expect(args[0]).to.be.an.instanceof(Error)
|
156 | expect(args[0].message).to.equal('Permission not found: user')
|
157 |
|
158 | it 'should return an error if permission is found and returns an error', ->
|
159 | state.permissions.user = (done) ->
|
160 | error = new Error('You are not a user!')
|
161 | done(error)
|
162 | spy = sinon.spy()
|
163 | @command.applyPermissions(spy)
|
164 | expect(spy).to.have.been.calledOnce
|
165 | args = spy.firstCall.args
|
166 | expect(args[0]).to.be.an.instanceof(Error)
|
167 | expect(args[0].message).to.equal('You are not a user!')
|
168 |
|
169 | describe '#execute()', ->
|
170 |
|
171 | beforeEach ->
|
172 | state.globalOptions = []
|
173 |
|
174 | it 'should execute the action', (done) ->
|
175 | spy = sinon.spy()
|
176 |
|
177 | command = new Command
|
178 | signature: new Signature('foo <bar>')
|
179 | action: spy
|
180 |
|
181 | command.execute command: 'foo hello', (error) ->
|
182 | expect(error).to.not.exist
|
183 | expect(spy).to.have.been.calledOnce
|
184 | expect(spy).to.have.been.calledWith(bar: 'hello')
|
185 | done()
|
186 |
|
187 | it 'should call action within the context of command', (done) ->
|
188 | spy = sinon.spy()
|
189 |
|
190 | command = new Command
|
191 | signature: new Signature('foo')
|
192 | action: spy
|
193 |
|
194 | command.execute command: 'foo', (error) ->
|
195 | expect(error).to.not.exist
|
196 | expect(spy).to.have.been.calledOn(command)
|
197 | done()
|
198 |
|
199 | it 'should pass empty objects if nullary command', (done) ->
|
200 | spy = sinon.spy()
|
201 |
|
202 | command = new Command
|
203 | signature: new Signature('foo')
|
204 | action: spy
|
205 |
|
206 | command.execute command: 'foo', (error) ->
|
207 | expect(error).to.not.exist
|
208 | expect(spy).to.have.been.calledWith({}, {})
|
209 | done()
|
210 |
|
211 | it 'should pass an empty object as the second argument if no options', (done) ->
|
212 | spy = sinon.spy()
|
213 |
|
214 | command = new Command
|
215 | signature: new Signature('foo <bar>')
|
216 | action: spy
|
217 |
|
218 | expect(state.globalOptions).to.deep.equal([])
|
219 | command.execute command: 'foo baz', (error) ->
|
220 | expect(error).to.not.exist
|
221 | expect(spy).to.have.been.calledWith(bar: 'baz', {})
|
222 | done()
|
223 |
|
224 | it 'should parse global options', (done) ->
|
225 | spy = sinon.spy()
|
226 |
|
227 | command = new Command
|
228 | signature: new Signature('foo <bar>')
|
229 | action: spy
|
230 |
|
231 | state.globalOptions.push new Option
|
232 | signature: new Signature('quiet')
|
233 | boolean: true
|
234 |
|
235 | command.execute {
|
236 | command: 'foo baz'
|
237 | options:
|
238 | quiet: true
|
239 | }, (error) ->
|
240 | expect(error).to.not.exist
|
241 | expect(spy).to.have.been.calledWith {
|
242 | bar: 'baz'
|
243 | }, {
|
244 | quiet: true
|
245 | }
|
246 | done()
|
247 |
|
248 | it 'should parse command options', (done) ->
|
249 | spy = sinon.spy()
|
250 |
|
251 | command = new Command
|
252 | signature: new Signature('foo <bar>')
|
253 | action: spy
|
254 | options: [
|
255 | new Option
|
256 | signature: new Signature('quiet')
|
257 | boolean: true
|
258 | ]
|
259 |
|
260 | command.execute {
|
261 | command: 'foo baz'
|
262 | options:
|
263 | quiet: true
|
264 | }, (error) ->
|
265 | expect(error).to.not.exist
|
266 | expect(spy).to.have.been.calledWith {
|
267 | bar: 'baz'
|
268 | }, {
|
269 | quiet: true
|
270 | }
|
271 | done()
|
272 |
|
273 | it 'should return an error if lacking a required option', (done) ->
|
274 | command = new Command
|
275 | signature: new Signature('foo <bar>')
|
276 | action: _.noop
|
277 | options: [
|
278 | new Option
|
279 | signature: new Signature('quiet')
|
280 | boolean: true
|
281 | required: 'You have to pass this option'
|
282 | ]
|
283 |
|
284 | command.execute {
|
285 | command: 'foo baz'
|
286 | }, (error) ->
|
287 | expect(error).to.be.an.instanceof(Error)
|
288 | expect(error.message).to.equal('You have to pass this option')
|
289 | done()
|
290 |
|
291 | it 'should parse global and command options', (done) ->
|
292 | spy = sinon.spy()
|
293 |
|
294 | state.globalOptions.push new Option
|
295 | signature: new Signature('config')
|
296 | parameter: 'name'
|
297 | boolean: false
|
298 | alias: 'c'
|
299 |
|
300 | command = new Command
|
301 | signature: new Signature('foo <bar>')
|
302 | action: spy
|
303 | options: [
|
304 | new Option
|
305 | signature: new Signature('quiet')
|
306 | boolean: true
|
307 | ]
|
308 |
|
309 | command.execute {
|
310 | command: 'foo baz'
|
311 | options:
|
312 | quiet: true
|
313 | c: 'hello.conf'
|
314 | }, (error) ->
|
315 | expect(error).to.not.exist
|
316 | expect(spy).to.have.been.calledWith {
|
317 | bar: 'baz'
|
318 | }, {
|
319 | quiet: true
|
320 | config: 'hello.conf'
|
321 | }
|
322 | done()
|
323 |
|
324 | it 'should give precedence to command options', (done) ->
|
325 | spy = sinon.spy()
|
326 |
|
327 | state.globalOptions.push new Option
|
328 | signature: new Signature('config')
|
329 | parameter: 'name'
|
330 |
|
331 | command = new Command
|
332 | signature: new Signature('foo <bar>')
|
333 | action: spy
|
334 | options: [
|
335 | new Option
|
336 | signature: new Signature('config')
|
337 | boolean: true
|
338 | ]
|
339 |
|
340 | command.execute {
|
341 | command: 'foo baz'
|
342 | options:
|
343 | config: true
|
344 | }, (error) ->
|
345 | expect(error).to.not.exist
|
346 | expect(spy).to.have.been.calledWith {
|
347 | bar: 'baz'
|
348 | }, {
|
349 | config: true
|
350 | }
|
351 | done()
|
352 |
|
353 | it 'should be able to call the done callback manually', (done) ->
|
354 | command = new Command
|
355 | signature: new Signature('foo <bar>')
|
356 | action: (params, options, callback) ->
|
357 | return callback(null, 123)
|
358 |
|
359 | command.execute command: 'foo bar', (error, data) ->
|
360 | expect(error).to.not.exist
|
361 | expect(data).to.equal(123)
|
362 | done()
|
363 |
|
364 | it 'should be able to call the done callback with an error', (done) ->
|
365 | cliError = new Error('Test error')
|
366 |
|
367 | command = new Command
|
368 | signature: new Signature('foo <bar>')
|
369 | action: (params, options, callback) ->
|
370 | return callback(cliError)
|
371 |
|
372 | command.execute command: 'foo bar', (error) ->
|
373 | expect(error).to.deep.equal(cliError)
|
374 | done()
|
375 |
|
376 | it 'should call the action if no callback', (done) ->
|
377 | spy = sinon.spy()
|
378 |
|
379 | command = new Command
|
380 | signature: new Signature('foo <bar>')
|
381 | action: spy
|
382 |
|
383 | command.execute command: 'foo bar', (error) ->
|
384 | expect(error).to.not.exist
|
385 | expect(spy).to.have.been.calledOnce
|
386 | done()
|
387 |
|
388 | describe 'given an action that throws an error', ->
|
389 |
|
390 | beforeEach ->
|
391 | @command = new Command
|
392 | signature: new Signature('hello')
|
393 | action: ->
|
394 | throw new Error('Command Error')
|
395 |
|
396 | it 'should catch the error and send it to the callback', (done) ->
|
397 | @command.execute command: 'hello', (error) ->
|
398 | expect(error).to.be.an.instanceof(Error)
|
399 | expect(error.message).to.equal('Command Error')
|
400 | done()
|
401 |
|
402 | describe 'given a command with the root property', ->
|
403 |
|
404 | beforeEach ->
|
405 | @actionSpy = sinon.spy()
|
406 | @command = new Command
|
407 | signature: new Signature('foo')
|
408 | action: @actionSpy
|
409 | root: true
|
410 |
|
411 | describe 'given the user is root', ->
|
412 |
|
413 | beforeEach ->
|
414 | @utilsIsElevatedStub = sinon.stub(utils, 'isElevated')
|
415 | @utilsIsElevatedStub.yields(null, true)
|
416 |
|
417 | afterEach ->
|
418 | @utilsIsElevatedStub.restore()
|
419 |
|
420 | it 'should execute the action normally', (done) ->
|
421 | @command.execute command: 'foo', (error) =>
|
422 | expect(error).to.not.exist
|
423 | expect(@actionSpy).to.have.been.called
|
424 | done()
|
425 |
|
426 | describe 'given the user is not root', ->
|
427 |
|
428 | beforeEach ->
|
429 | @utilsIsElevatedStub = sinon.stub(utils, 'isElevated')
|
430 | @utilsIsElevatedStub.yields(null, false)
|
431 |
|
432 | afterEach ->
|
433 | @utilsIsElevatedStub.restore()
|
434 |
|
435 | it 'should not execute the action', (done) ->
|
436 | @command.execute command: 'foo', =>
|
437 | expect(@actionSpy).to.not.have.been.called
|
438 | done()
|
439 |
|
440 | it 'should return an error', (done) ->
|
441 | @command.execute command: 'foo', (error) ->
|
442 | expect(error).to.be.an.instanceof(Error)
|
443 | expect(error.message).to.equal('You need admin privileges to run this command')
|
444 | expect(error.code).to.equal('EACCES')
|
445 | done()
|
446 |
|
447 | describe 'given a command with permissions', ->
|
448 |
|
449 | beforeEach ->
|
450 | state.permissions = {}
|
451 |
|
452 | @actionSpy = sinon.spy()
|
453 | @command = new Command
|
454 | signature: new Signature('foo')
|
455 | action: @actionSpy
|
456 | permission: 'user'
|
457 |
|
458 | it 'should call the action if permission is found and does not return an error', (done) ->
|
459 | state.permissions.user = (done) -> done()
|
460 | @command.execute command: 'foo', (error) =>
|
461 | expect(error).to.not.exist
|
462 | expect(@actionSpy).to.have.been.called
|
463 | done()
|
464 |
|
465 | it 'should not call the action if permission is found and returns an error', (done) ->
|
466 | state.permissions.user = (done) ->
|
467 | error = new Error('You are not a user!')
|
468 | done(error)
|
469 |
|
470 | @command.execute command: 'foo', (error) =>
|
471 | expect(error).to.be.an.instanceof(Error)
|
472 | expect(error.message).to.equal('You are not a user!')
|
473 | expect(@actionSpy).to.not.have.been.called
|
474 | done()
|
475 |
|
476 | it 'should return an error if permission is not found', (done) ->
|
477 | @command.execute command: 'foo', (error) =>
|
478 | expect(error).to.be.an.instanceof(Error)
|
479 | expect(error.message).to.equal('Permission not found: user')
|
480 | expect(@actionSpy).to.not.have.been.called
|
481 | done()
|
482 |
|
483 | describe '#isWildcard()', ->
|
484 |
|
485 | it 'should return true if is wildcard', ->
|
486 | command = new Command
|
487 | signature: new Signature(settings.signatures.wildcard)
|
488 | action: _.noop
|
489 |
|
490 | expect(command.isWildcard()).to.be.true
|
491 |
|
492 | it 'should return false if not wildcard', ->
|
493 | command = new Command
|
494 | signature: new Signature('foo bar')
|
495 | action: _.noop
|
496 |
|
497 | expect(command.isWildcard()).to.be.false
|