MuranoCLI 3.2.0.beta.1 → 3.2.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/.trustme.plugin +137 -0
  4. data/.trustme.sh +217 -117
  5. data/.trustme.vim +9 -3
  6. data/Gemfile +9 -3
  7. data/MuranoCLI.gemspec +8 -5
  8. data/Rakefile +1 -0
  9. data/dockers/Dockerfile.2.2.9 +6 -3
  10. data/dockers/Dockerfile.2.3.6 +6 -3
  11. data/dockers/Dockerfile.2.4.3 +6 -3
  12. data/dockers/Dockerfile.2.5.0 +6 -3
  13. data/dockers/Dockerfile.GemRelease +10 -8
  14. data/dockers/Dockerfile.m4 +23 -5
  15. data/dockers/docker-test.sh +65 -28
  16. data/docs/completions/murano_completion-bash +751 -57
  17. data/docs/develop.rst +10 -9
  18. data/lib/MrMurano/AccountBase.rb +95 -6
  19. data/lib/MrMurano/Commander-Entry.rb +9 -4
  20. data/lib/MrMurano/Config-Migrate.rb +2 -0
  21. data/lib/MrMurano/Config.rb +94 -26
  22. data/lib/MrMurano/Content.rb +1 -1
  23. data/lib/MrMurano/Exchange.rb +77 -42
  24. data/lib/MrMurano/Gateway.rb +1 -1
  25. data/lib/MrMurano/HttpAuthed.rb +20 -7
  26. data/lib/MrMurano/Logs.rb +10 -1
  27. data/lib/MrMurano/ProjectFile.rb +1 -1
  28. data/lib/MrMurano/ReCommander.rb +129 -73
  29. data/lib/MrMurano/Solution-ServiceConfig.rb +18 -11
  30. data/lib/MrMurano/Solution-Services.rb +78 -50
  31. data/lib/MrMurano/Solution-Users.rb +1 -1
  32. data/lib/MrMurano/Solution.rb +13 -63
  33. data/lib/MrMurano/SyncUpDown-Core.rb +185 -77
  34. data/lib/MrMurano/SyncUpDown-Item.rb +29 -4
  35. data/lib/MrMurano/SyncUpDown.rb +11 -11
  36. data/lib/MrMurano/Webservice-Cors.rb +1 -1
  37. data/lib/MrMurano/Webservice-Endpoint.rb +28 -17
  38. data/lib/MrMurano/Webservice-File.rb +103 -43
  39. data/lib/MrMurano/commands/domain.rb +1 -0
  40. data/lib/MrMurano/commands/element.rb +585 -0
  41. data/lib/MrMurano/commands/exchange.rb +211 -204
  42. data/lib/MrMurano/commands/gb.rb +1 -0
  43. data/lib/MrMurano/commands/globals.rb +17 -7
  44. data/lib/MrMurano/commands/init.rb +115 -101
  45. data/lib/MrMurano/commands/keystore.rb +1 -1
  46. data/lib/MrMurano/commands/logs.rb +2 -1
  47. data/lib/MrMurano/commands/postgresql.rb +17 -7
  48. data/lib/MrMurano/commands/service.rb +572 -0
  49. data/lib/MrMurano/commands/show.rb +7 -3
  50. data/lib/MrMurano/commands/solution.rb +2 -1
  51. data/lib/MrMurano/commands/solution_picker.rb +31 -15
  52. data/lib/MrMurano/commands/status.rb +205 -169
  53. data/lib/MrMurano/commands/sync.rb +70 -38
  54. data/lib/MrMurano/commands/token.rb +59 -14
  55. data/lib/MrMurano/commands/usage.rb +1 -0
  56. data/lib/MrMurano/commands.rb +2 -0
  57. data/lib/MrMurano/hash.rb +91 -0
  58. data/lib/MrMurano/http.rb +55 -6
  59. data/lib/MrMurano/makePretty.rb +47 -0
  60. data/lib/MrMurano/optparse.rb +60 -45
  61. data/lib/MrMurano/variegated/TruthyFalsey.rb +48 -0
  62. data/lib/MrMurano/variegated/ruby_dig.rb +64 -0
  63. data/lib/MrMurano/verbosing.rb +113 -3
  64. data/lib/MrMurano/version.rb +1 -1
  65. data/spec/Account_spec.rb +34 -20
  66. data/spec/Business_spec.rb +12 -9
  67. data/spec/Config_spec.rb +7 -1
  68. data/spec/Content_spec.rb +17 -1
  69. data/spec/GatewayBase_spec.rb +5 -2
  70. data/spec/GatewayDevice_spec.rb +4 -2
  71. data/spec/GatewayResource_spec.rb +4 -1
  72. data/spec/GatewaySettings_spec.rb +4 -1
  73. data/spec/HttpAuthed_spec.rb +73 -0
  74. data/spec/Http_spec.rb +32 -35
  75. data/spec/ProjectFile_spec.rb +1 -1
  76. data/spec/Solution-ServiceConfig_spec.rb +4 -1
  77. data/spec/Solution-ServiceEventHandler_spec.rb +6 -3
  78. data/spec/Solution-ServiceModules_spec.rb +4 -1
  79. data/spec/Solution-UsersRoles_spec.rb +4 -1
  80. data/spec/Solution_spec.rb +4 -1
  81. data/spec/SyncUpDown_spec.rb +1 -1
  82. data/spec/Webservice-Cors_spec.rb +4 -1
  83. data/spec/Webservice-Endpoint_spec.rb +9 -6
  84. data/spec/Webservice-File_spec.rb +17 -4
  85. data/spec/Webservice-Setting_spec.rb +6 -2
  86. data/spec/_workspace.rb +2 -0
  87. data/spec/cmd_common.rb +42 -13
  88. data/spec/cmd_content_spec.rb +17 -7
  89. data/spec/cmd_device_spec.rb +1 -1
  90. data/spec/cmd_domain_spec.rb +2 -2
  91. data/spec/cmd_element_spec.rb +400 -0
  92. data/spec/cmd_exchange_spec.rb +2 -2
  93. data/spec/cmd_init_spec.rb +59 -25
  94. data/spec/cmd_keystore_spec.rb +6 -3
  95. data/spec/cmd_link_spec.rb +10 -5
  96. data/spec/cmd_logs_spec.rb +1 -1
  97. data/spec/cmd_setting_application_spec.rb +18 -15
  98. data/spec/cmd_setting_product_spec.rb +7 -7
  99. data/spec/cmd_status_spec.rb +27 -17
  100. data/spec/cmd_syncdown_application_spec.rb +30 -3
  101. data/spec/cmd_syncdown_both_spec.rb +72 -18
  102. data/spec/cmd_syncup_spec.rb +71 -5
  103. data/spec/cmd_token_spec.rb +2 -2
  104. data/spec/cmd_usage_spec.rb +2 -2
  105. data/spec/dry_run_formatter.rb +27 -0
  106. data/spec/fixtures/dumped_config +8 -0
  107. data/spec/fixtures/exchange_element/element-show.json +1 -0
  108. data/spec/fixtures/exchange_element/swagger-mur-6407__10k.yaml +282 -0
  109. data/spec/fixtures/exchange_element/swagger-mur-6407__20k.yaml +588 -0
  110. data/spec/variegated_TruthyFalsey_spec.rb +29 -0
  111. metadata +51 -25
data/spec/cmd_common.rb CHANGED
@@ -16,6 +16,7 @@ require 'tmpdir'
16
16
  require 'webmock/rspec'
17
17
 
18
18
  require 'MrMurano/Config'
19
+ require 'MrMurano/HttpAuthed'
19
20
 
20
21
  # Prevent Commander from registering its at_exit hook.
21
22
  # - Also print warning if spec exits, which rspec doesn't see as wrong.
@@ -50,6 +51,9 @@ at_exit do
50
51
  warn('¡Unexpected spec exit killed rspec!')
51
52
  warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
52
53
  # DEVs: If you see this message, the last test exited unexpectedly.
54
+ warn('')
55
+ warn("Died on: #{$exited_abnormally}")
56
+ warn('')
53
57
  end
54
58
  end
55
59
  alias original_at_exit at_exit unless defined?(original_at_exit)
@@ -96,6 +100,8 @@ RSpec.shared_context 'CI_CMD' do
96
100
  args.push '-c', 'fullerror'
97
101
  # The spinner output would make it hard to write expects().
98
102
  args.push '--no-progress'
103
+ # Save the token.
104
+ args.push '--cache'
99
105
 
100
106
  if Gem.win_platform?
101
107
  cmd = args.map do |i|
@@ -159,7 +165,7 @@ RSpec.shared_context 'CI_CMD' do
159
165
  expect(out).to \
160
166
  eq('').or \
161
167
  eq("No solutions found\n").or \
162
- match(/^Deleted [\d]+ solutions/)
168
+ match(/^Deleted [\d]+ solutions?/)
163
169
  expect(strip_color(err)).to eq('').or eq("No solutions found\n")
164
170
  expect(status.exitstatus).to eq(0).or eq(1)
165
171
  end
@@ -221,7 +227,7 @@ RSpec.shared_context 'CI_CMD' do
221
227
  # Utility fcns: Strings.
222
228
 
223
229
  # rname makes a random Murano-acceptable Solution name.
224
- def rname(name)
230
+ def rname(name='')
225
231
  #"#{name}-#{Random.new.rand.hash.abs.to_s(16)}"
226
232
  # MUR-2454: Product name may only contain letters and numbers.
227
233
  #"#{name}#{Random.new.rand.hash.abs.to_s(16)}"
@@ -272,7 +278,7 @@ RSpec.shared_context 'CI_CMD' do
272
278
 
273
279
  def murano_command_runner(cmd, *args, wont_run: false, wont_parse: false)
274
280
  # This is a functional test, so tell WebMock to back off.
275
- # FIXME: Remember the setting and re-enable.
281
+ allow_net_conn = WebMock::Config.instance.allow_net_connect != false
276
282
  WebMock.allow_net_connect!
277
283
 
278
284
  capture_stdio
@@ -288,25 +294,25 @@ RSpec.shared_context 'CI_CMD' do
288
294
  $stdout = tmpout
289
295
  $stderr = tmperr
290
296
 
291
- $exited_abnormally = true
297
+ $exited_abnormally = "#{cmd} #{args.join(' ')}"
298
+
299
+ MrMurano::HttpAuthed.instance.credentials_reset
292
300
 
293
301
  # When Commander is loaded, it sets an at_exit hook, which we monkey
294
302
  # patch in ReCommander. Since Config.validate_cmd is called before
295
303
  # at_exit, it uses runner.command_exit to tell ReCommander's at_exit
296
304
  # monkey patch not to call Commander.run!. Via rspec, we don't use the
297
305
  # at_exit hook, or ReCommander.
298
- if $cfg.nil?
299
- $cfg = MrMurano::Config.new(::Commander::Runner.instance)
300
- $cfg.load
301
- end
306
+ $cfg = MrMurano::Config.new(::Commander::Runner.instance)
307
+ $cfg.load
302
308
  $cfg['tool.no-progress'] = true
303
- $cfg.validate_cmd(cmd)
309
+ $cfg.validate_cmd
304
310
  runner = ::Commander::Runner.instance
305
311
  unless defined?(runner.command_exit) && runner.command_exit
306
- # Commander's at_exit hook calls runner.run! which runs the command
307
- # that was determined with Commander was loaded. I [lb] tried a few
308
- # different ways to reset Runner.instance, but nothing worked; our
309
- # best bet is to just call the command directly.
312
+ # (lb): Commander's at_exit hook calls runner.run! which runs the
313
+ # command that was determined with Commander was loaded. I tried a
314
+ # few different ways to reset Runner.instance, but nothing worked;
315
+ # our best bet is to just call the command directly.
310
316
  the_cmd = command(cmd.to_sym)
311
317
  when_called = the_cmd.peek_when_called.dup
312
318
 
@@ -342,6 +348,8 @@ RSpec.shared_context 'CI_CMD' do
342
348
  # $stdout, $stderr = STDOUT, STDERR
343
349
  restore_stdio
344
350
 
351
+ WebMock.disable_net_connect! unless allow_net_conn
352
+
345
353
  [strip_color(tmpout.string), strip_color(tmperr.string)]
346
354
  end
347
355
 
@@ -376,11 +384,29 @@ RSpec.shared_context 'CI_CMD' do
376
384
  require 'byebug/settings/histfile'
377
385
  _ignored = ::Byebug::HistfileSetting::DEFAULT
378
386
 
387
+ $exited_abnormally = ex.example.to_s
388
+
379
389
  @testdir = Pathname.new(Dir.pwd).realpath
380
390
  Dir.mktmpdir do |hdir|
391
+ saved_home = Dir.home
381
392
  ENV['HOME'] = hdir
382
393
  Dir.chdir(hdir) do
383
394
  Dir.mkdir('.murano')
395
+
396
+ Dir.chdir('.murano') do
397
+ begin
398
+ FileUtils.ln_s(File.join(saved_home, '.murano', 'tokens'), 'tokens')
399
+ rescue NotImplementedError => err
400
+ require 'rbconfig'
401
+ unless OS.windows?
402
+ warn(
403
+ 'Unexpected: ln_s failed on non-Windows machine / ' \
404
+ "host_os: #{RbConfig::CONFIG['host_os']} / err: #{err}"
405
+ )
406
+ end
407
+ end
408
+ end
409
+
384
410
  @tmpdir = File.join(hdir, 'project')
385
411
  Dir.mkdir(@tmpdir)
386
412
  Dir.chdir(@tmpdir) do
@@ -392,7 +418,10 @@ RSpec.shared_context 'CI_CMD' do
392
418
  rm_symlink
393
419
  end
394
420
  end
421
+ ENV['HOME'] = saved_home
395
422
  end
423
+
424
+ $exited_abnormally = false
396
425
  end
397
426
  end
398
427
 
@@ -15,19 +15,24 @@ RSpec.describe 'murano content', :cmd, :needs_password do
15
15
 
16
16
  before(:example) do
17
17
  @product_name = rname('contestTest')
18
- out, err, status = Open3.capture3(capcmd('murano', 'product', 'create', @product_name, '--save'))
18
+ out, err, status = Open3.capture3(
19
+ capcmd('murano', 'product', 'create', @product_name, '--save')
20
+ )
19
21
  expect(err).to eq('')
20
22
  expect(out.chomp).to match(/^[a-zA-Z0-9]+$/)
21
23
  expect(status.exitstatus).to eq(0)
22
24
  end
25
+
23
26
  after(:example) do
24
- out, err, status = Open3.capture3(capcmd('murano', 'solution', 'delete', '--yes', @product_name))
27
+ out, err, status = Open3.capture3(
28
+ capcmd('murano', 'solution', 'delete', '--yes', @product_name)
29
+ )
25
30
  expect(out).to eq('')
26
31
  expect(err).to eq('')
27
32
  expect(status.exitstatus).to eq(0)
28
33
  end
29
34
 
30
- it 'life cycle' do
35
+ it 'life cycle', :club_10s do
31
36
  out, err, status = Open3.capture3(capcmd('murano', 'content', 'list'))
32
37
  #expect(out.lines).to match(
33
38
  # [
@@ -43,7 +48,9 @@ RSpec.describe 'murano content', :cmd, :needs_password do
43
48
  expect(status.exitstatus).to eq(0)
44
49
 
45
50
  FileUtils.copy(File.join(@testdir, 'spec/fixtures/dumped_config'), 'myFile')
46
- out, err, status = Open3.capture3(capcmd('murano', 'content', 'upload', 'myFile', '--tags', 'random=junk'))
51
+ out, err, status = Open3.capture3(
52
+ capcmd('murano', 'content', 'upload', 'myFile', '--tags', 'random=junk')
53
+ )
47
54
  expect(out).to eq('')
48
55
  expect(err).to eq('')
49
56
  expect(status.exitstatus).to eq(0)
@@ -75,7 +82,9 @@ RSpec.describe 'murano content', :cmd, :needs_password do
75
82
  )
76
83
  expect(status.exitstatus).to eq(0)
77
84
 
78
- out, err, status = Open3.capture3(capcmd('murano', 'content', 'download', 'myFile', '-o', 'testDown'))
85
+ out, err, status = Open3.capture3(
86
+ capcmd('murano', 'content', 'download', 'myFile', '-o', 'testDown')
87
+ )
79
88
  expect(out).to eq('')
80
89
  expect(err).to eq('')
81
90
  expect(status.exitstatus).to eq(0)
@@ -84,7 +93,9 @@ RSpec.describe 'murano content', :cmd, :needs_password do
84
93
  tdf = IO.read('testDown')
85
94
  expect(tdf).to eq(dcf)
86
95
 
87
- out, err, status = Open3.capture3(capcmd('murano', 'content', 'delete', 'myFile', '-y'))
96
+ out, err, status = Open3.capture3(
97
+ capcmd('murano', 'content', 'delete', 'myFile', '-y')
98
+ )
88
99
  expect(out).to eq('')
89
100
  expect(err).to eq('')
90
101
  expect(status.exitstatus).to eq(0)
@@ -105,4 +116,3 @@ RSpec.describe 'murano content', :cmd, :needs_password do
105
116
  end
106
117
  end
107
118
 
108
- # vim: set ai et sw=2 ts=2 :
@@ -84,7 +84,7 @@ RSpec.describe 'murano device', :cmd, :needs_password do
84
84
  expect(status.exitstatus).to eq(0)
85
85
  end
86
86
 
87
- it 'writes and reads' do
87
+ it 'writes and reads', :club_10s do
88
88
  FileUtils.mkpath('specs')
89
89
  FileUtils.copy(
90
90
  File.join(@testdir, 'spec/fixtures/product_spec_files/lightbulb.yaml'),
@@ -35,7 +35,7 @@ RSpec.describe 'murano domain', :cmd, :needs_password do
35
35
  expect(status.exitstatus).to eq(0)
36
36
  end
37
37
 
38
- it 'show domain' do
38
+ it 'show domain', :club_20s, :club_10s do
39
39
  out, err, status = Open3.capture3(capcmd('murano', 'domain'))
40
40
  # 2017-05-31: Previously, the project could be named whatever and
41
41
  # the URI would start with the same.
@@ -46,7 +46,7 @@ RSpec.describe 'murano domain', :cmd, :needs_password do
46
46
  #expect(out.chomp).to end_with('m2.exosite.io')
47
47
  out.lines.each do |line|
48
48
  expect(line).to match(
49
- %r{^(Product|Application): domain[Tt]est[a-z0-9]+ <[a-z0-9]+> https://[.a-z0-9]+$}
49
+ %r{^(Product|Application): domain[Tt]est[a-z0-9]+ <[a-z0-9]+> https://[.\-a-z0-9]+$}
50
50
  )
51
51
  end
52
52
  expect(err).to eq('')
@@ -0,0 +1,400 @@
1
+ # Copyright © 2016-2017 Exosite LLC. All Rights Reserved
2
+ # License: PROPRIETARY. See LICENSE.txt.
3
+ # frozen_string_literal: true
4
+
5
+ # vim:tw=0:ts=2:sw=2:et:ai
6
+ # Unauthorized copying of this file is strictly prohibited.
7
+
8
+ # (lb): 2018-05-01: Whoa. Coverage at 303 of 306 lines! 99.02%!!
9
+
10
+ require 'fileutils'
11
+ require 'json'
12
+ require 'open3'
13
+ require 'pathname'
14
+ require 'tty-editor'
15
+ require 'yaml'
16
+
17
+ require 'cmd_common'
18
+ require 'MrMurano/Config'
19
+
20
+ RSpec.describe 'murano element', :cmd, :needs_password do
21
+ include_context 'CI_CMD'
22
+
23
+ context 'without project' do
24
+ it 'usage' do
25
+ cmd_verify_help('element')
26
+ end
27
+ end
28
+
29
+ def expect_exchange_element_table(stdout, stderr, num_cols: nil)
30
+ expect(stderr).to eq('')
31
+ lines = stdout.lines
32
+ # FIXME/2018-04-30: Is this too much detail??
33
+ # What about running test once, dumping output to file,
34
+ # and expecting same output next time?
35
+ # Outline of table. n columns. '+-----+-----+---...----+\n'
36
+ expect(lines[0]).to match(/^(\+-+){#{num_cols}}\+$/)
37
+ # Header. "... key | value ..."
38
+ expect(lines[1]).to match(/^\| key \+| value \+|$/)
39
+ # Separator.
40
+ expect(lines[2]).to match(/^(\+-+){#{num_cols}}\+$/)
41
+ # Content. Starts with nested.hash.key.
42
+ (3..(lines.length - 2)).to_a.each do |line|
43
+ expect(lines[line]).to match(/^\| [\.0-9a-zA-Z]* +\| /)
44
+ end
45
+ expect(lines[-1]).to match(/^(\+-+){#{num_cols}}\+$/)
46
+ end
47
+
48
+ context 'show' do
49
+ it '--help' do
50
+ stdout, stderr = murano_command_wont_parse('element show', '--help')
51
+ expect(stdout).to a_string_starting_with('Usage:')
52
+ expect(stderr).to eq('')
53
+ end
54
+
55
+ it 'using ambiguous name' do
56
+ # MEH/2018-04-30: This test is dependent on the platform having
57
+ # more than one element with the term 'IoT' in its name!
58
+ # 59bc24836b12c505c98c5a51: ‘Twilio SMS Service’
59
+ # 5adedb7cdcd2dd1d04082fba: ‘Public application for the twilio’
60
+ #
61
+ # FIXME/2018-04-30: Make test-specific elements for this test!
62
+ stdout, stderr = murano_command_exits('element show', 'twilio')
63
+ expect(stdout).to eq('')
64
+ expect(stderr).to a_string_starting_with(
65
+ 'Please be more specific: More than one matching element was found:'
66
+ )
67
+ end
68
+
69
+ it 'as default-table' do
70
+ # FIXME/2018-04-30: Make test-specific elements for this test!
71
+ stdout, stderr = murano_command_run('element show', 'MUR-6407')
72
+ expect_exchange_element_table(stdout, stderr, num_cols: 2)
73
+ expect(stderr).to eq('')
74
+ end
75
+
76
+ it 'as table wrap' do
77
+ # FIXME/2018-04-30: Make test-specific elements for this test!
78
+ stdout, stderr = murano_command_run('element show', 'MUR-6407', '--wrap')
79
+ expect_exchange_element_table(stdout, stderr, num_cols: 2)
80
+ expect(stderr).to eq('')
81
+ end
82
+
83
+ it 'as table truncate' do
84
+ # FIXME/2018-04-30: Make test-specific elements for this test!
85
+ stdout, stderr = murano_command_run('element show', 'MUR-6407', '--truncate')
86
+ expect_exchange_element_table(stdout, stderr, num_cols: 2)
87
+ expect(stderr).to eq('')
88
+ end
89
+
90
+ # (lb): NOTE: This does not add any additional coverage to element.rb:
91
+ it 'as json' do
92
+ # FIXME/2018-04-30: Make test-specific elements for this test!
93
+ stdout, stderr = murano_command_run('element show', '--json', 'MUR-6407')
94
+ expect { JSON.parse(stdout) }.to_not raise_error
95
+ expect(stderr).to eq('')
96
+ end
97
+
98
+ # (lb): NOTE: This does not add any additional coverage to element.rb:
99
+ it 'as yaml' do
100
+ # FIXME/2018-04-30: Make test-specific elements for this test!
101
+ stdout, stderr = murano_command_run('element show', 'MUR-6407', '--yaml')
102
+ expect { YAML.parse(stdout) }.to_not raise_error
103
+ expect(stderr).to eq('')
104
+ end
105
+
106
+ # (lb): This adds 1 line of coverage.
107
+ it 'as csv' do
108
+ # FIXME/2018-04-30: Make test-specific elements for this test!
109
+ stdout, stderr = murano_command_run('element show', 'MUR-6407', '--csv')
110
+ expect(stdout).to_not eq('')
111
+ expect(stderr).to eq('')
112
+ end
113
+ end
114
+
115
+ context 'edit' do
116
+ before(:example) do
117
+ @json_input_file = 'element-show.json'
118
+ @spec_swagger_20k = 'swagger-mur-6407__20k.yaml'
119
+ @spec_swagger_10k = 'swagger-mur-6407__10k.yaml'
120
+ [
121
+ @json_input_file,
122
+ @spec_swagger_20k,
123
+ @spec_swagger_10k,
124
+ ].each do |path|
125
+ FileUtils.copy(
126
+ File.join(@testdir, "spec/fixtures/exchange_element/#{path}"), path
127
+ )
128
+ end
129
+ end
130
+
131
+ def element_editor_expect_edit_json_full
132
+ # Mock the $EDITOR session.
133
+ expect(TTY::Editor).to receive(:open) do |path|
134
+ edit_json_make_unique_contact(path)
135
+ end
136
+ end
137
+
138
+ def edit_json_make_unique_contact(path)
139
+ pname = path.is_a?(Pathname) && path || Pathname.new(path)
140
+ pname.write pname.read.gsub(
141
+ /\n "contact": "[a-f0-9]+",\n/,
142
+ %(\n "contact": "#{rname}",\n)
143
+ )
144
+ end
145
+
146
+ def edit_specs_yaml_make_unique_contact(path)
147
+ pname = path.is_a?(Pathname) && path || Pathname.new(path)
148
+ rname7 = rname[0..6]
149
+ pname.write pname.read.gsub(
150
+ /\n name: Exosite Exosite Exosite Exosite\n/,
151
+ %(\n name: #{rname7} #{rname7} #{rname7} #{rname7}\n)
152
+ )
153
+ end
154
+
155
+ def element_editor_expect_edit_plain_contact
156
+ # Mock the $EDITOR session.
157
+ expect(TTY::Editor).to receive(:open) do |path|
158
+ pname = path.is_a?(Pathname) && path || Pathname.new(path)
159
+ pname.write(rname)
160
+ end
161
+ end
162
+
163
+ # DEL:
164
+ def element_editor_expect_edit_json_from_string
165
+ expect(TTY::Editor).to receive(:open) do |path|
166
+ pname = path.is_a?(Pathname) && path || Pathname.new(path)
167
+ pname.write(
168
+ %({"tiers":[],"bizid":"55cop31gs89","access":"private","attachment":{},"contact":"#{rname}","description":"For testing Murano CLI element-edit.\\n\\nTEST2222\\n\\nTEST MORE","image":{"detail":{"color":"#224123","filename":"","url":""},"thumbnail":{"color":"#224655","filename":"xxx","url":""}},"markdown":"AxxxxAA2333BBBCCC\\n","name":"MUR-6407 Test Element","source":{"from":"service","name":"mur6407testelement","url":"https://testtesttest.apps.exosite-dev.io/swagger-mur-6407.yaml"},"specs":"","type":"service","tags":[],"active":true,"approval":"approved","elementId":"5ae3600e313d01708c2a6e0f"})
169
+ )
170
+ end
171
+ end
172
+
173
+ it '--help' do
174
+ stdout, stderr = murano_command_wont_parse('element edit', '--help')
175
+ expect(stdout).to a_string_starting_with('Usage:')
176
+ expect(stderr).to eq('')
177
+ end
178
+
179
+ it 'as static field option' do
180
+ stdout, stderr = murano_command_run(
181
+ 'element edit', '--contact', rname, 'MUR-6407'
182
+ )
183
+ expect(stdout).to eq('')
184
+ expect(stderr).to eq('')
185
+ end
186
+
187
+ it 'as dynamic field option' do
188
+ stdout, stderr = murano_command_run(
189
+ 'element edit', '-e', "contact=#{rname}", 'MUR-6407'
190
+ )
191
+ expect(stdout).to eq('')
192
+ expect(stderr).to eq('')
193
+ end
194
+
195
+ it 'as dynamic edit empty string without update' do
196
+ # I.e., @edit_fields[''] = nil, meaning should bring up editor.
197
+ # FIXME: Requires (lb)'s fixed commander library.
198
+
199
+ # If we don't change the file, Murano CLI will not PUT to BizAPI.
200
+ expect(TTY::Editor).to receive(:open).once
201
+ stdout, stderr = murano_command_exits(
202
+ 'element edit', '-e', '', 'MUR-6407'
203
+ )
204
+ expect(stdout).to eq('')
205
+ expect(stderr).to eq("No new field values specified to update.\n")
206
+ end
207
+
208
+ it 'as dynamic edit empty string with update' do
209
+ element_editor_expect_edit_json_full
210
+ stdout, stderr = murano_command_run(
211
+ 'element edit', '-e', '', 'MUR-6407'
212
+ )
213
+ expect(stdout).to eq('')
214
+ expect(stderr).to eq('')
215
+ end
216
+
217
+ it 'as dynamic edit no field with update' do
218
+ element_editor_expect_edit_json_full
219
+ stdout, stderr = murano_command_run(
220
+ 'element edit', '-e', '--', 'MUR-6407'
221
+ )
222
+ expect(stdout).to eq('')
223
+ expect(stderr).to eq('')
224
+ end
225
+
226
+ it 'as dynamic edit set empty string' do
227
+ # I.e., @edit_fields[''] = ''
228
+ stdout, stderr = murano_command_run(
229
+ #'element edit', '-e', "''=", 'MUR-6407'
230
+ 'element edit', '-e', '=', 'MUR-6407'
231
+ )
232
+ expect(stdout).to eq('')
233
+ expect(stderr).to eq(
234
+ %(Request Failed: 400: [400] child "type" fails because ["type" is required]\n)
235
+ )
236
+ end
237
+
238
+ it 'as edit with too many fields specified' do
239
+ stdout, stderr = murano_command_exits(
240
+ 'element edit', '-e', "contact=#{rname}", '-e', 'markdown', 'MUR-6407'
241
+ )
242
+ expect(stdout).to eq('')
243
+ expect(stderr).to eq(
244
+ %(Please specify at most one field when not specifing all field values.\n)
245
+ )
246
+ end
247
+
248
+ it 'as edit with no action specified' do
249
+ stdout, stderr = murano_command_exits(
250
+ 'element edit', 'MUR-6407'
251
+ )
252
+ expect(stdout).to eq('')
253
+ expect(stderr).to eq(
254
+ %(Please specify one or more -e/--edit options, or an input file.\n)
255
+ )
256
+ end
257
+
258
+ it 'as edit with input file not found' do
259
+ bad_path = "/path/to/nowhere/#{rname}.json"
260
+ stdout, stderr = murano_command_exits(
261
+ 'element edit', 'MUR-6407', bad_path
262
+ )
263
+ expect(stdout).to eq('')
264
+ expect(stderr).to eq(
265
+ %(Input file not found: #{bad_path}\n)
266
+ )
267
+ end
268
+
269
+ it 'as edit with input file found and valid' do
270
+ path = Pathname.new(@json_input_file)
271
+ path.write path.read.gsub(
272
+ /,"contact":"[a-f0-9]+",/,
273
+ %(,"contact":"#{rname}",)
274
+ )
275
+ stdout, stderr = murano_command_run(
276
+ 'element edit', 'MUR-6407', @json_input_file
277
+ )
278
+ expect(stdout).to eq('')
279
+ expect(stderr).to eq('')
280
+ end
281
+
282
+ it 'as edit with input file and too many fields specified' do
283
+ stdout, stderr = murano_command_exits(
284
+ 'element edit', '-e', 'contact', '-e', 'markdown', 'MUR-6407', @spec_swagger_20k
285
+ )
286
+ expect(stdout).to eq('')
287
+ expect(stderr).to eq(
288
+ %(Please specify at most a single field when specifing an input file.\n)
289
+ )
290
+ end
291
+
292
+ it 'as edit with input file and value specified' do
293
+ stdout, stderr = murano_command_exits(
294
+ 'element edit', '-e', 'contact=XYZ', 'MUR-6407', @spec_swagger_20k
295
+ )
296
+ expect(stdout).to eq('')
297
+ expect(stderr).to eq(
298
+ %(Please do not specify a value when specifing an input file.\n)
299
+ )
300
+ end
301
+
302
+ it 'as edit with input file that is too large and unrecognized field' do
303
+ stdout, stderr = murano_command_run(
304
+ 'element edit', '--edit', "spec=@#{@spec_swagger_20k}", 'MUR-6407'
305
+ )
306
+ expect(stdout).to eq('')
307
+ expect(stderr).to eq(
308
+ %(Request Failed: 400: [400] "spec" is not allowed\n)
309
+ )
310
+ end
311
+
312
+ it 'as edit with input file that should not be parsed' do
313
+ stdout, stderr = murano_command_run(
314
+ 'element edit', '--edit', "specs=@#{@spec_swagger_20k}", 'MUR-6407'
315
+ )
316
+ expect(stdout).to eq('')
317
+ expect(stderr).to eq(
318
+ %(Request Failed: 400: [400] child "specs" fails because ["specs" must be a string]\n)
319
+ )
320
+ end
321
+
322
+ # FIXME/2018-05-01 00:10: How does Tilstra circumvent 20k limit?
323
+ it 'as edit with input file that is too large' do
324
+ stdout, stderr = murano_command_run(
325
+ 'element edit', '--edit', 'specs', '--plain', '--', 'MUR-6407', @spec_swagger_20k
326
+ )
327
+ expect(stdout).to eq('')
328
+ expect(stderr).to eq(
329
+ %(Request Failed: 400: [400] child "specs" fails because ["specs" length must be less than or equal to 10000 characters long]\n)
330
+ )
331
+ end
332
+
333
+ it 'as edit with input file that is just right' do
334
+ edit_specs_yaml_make_unique_contact(@spec_swagger_10k)
335
+ stdout, stderr = murano_command_run(
336
+ 'element edit', '--edit', 'specs', '--plain', '--', 'MUR-6407', @spec_swagger_10k
337
+ )
338
+ expect(stdout).to eq('')
339
+ expect(stderr).to eq('')
340
+
341
+ # Send same file again.
342
+ stdout, stderr = murano_command_exits(
343
+ 'element edit', '--edit', 'specs', '--plain', '--', 'MUR-6407', @spec_swagger_10k
344
+ )
345
+ expect(stdout).to eq('')
346
+ expect(stderr).to eq("No new field values specified to update.\n")
347
+ end
348
+
349
+ it 'as dynamic edit single field no value uses EDITOR' do
350
+ element_editor_expect_edit_plain_contact
351
+ stdout, stderr = murano_command_run(
352
+ 'element edit', '-e', 'contact', '--', 'MUR-6407'
353
+ )
354
+ expect(stdout).to eq('')
355
+ expect(stderr).to eq('')
356
+ end
357
+
358
+ it 'as dynamic edit nested field change value' do
359
+ stdout, stderr = murano_command_run(
360
+ 'element edit', '-e', "image.detail.color=0x#{rname[0..5]}", 'MUR-6407'
361
+ )
362
+ expect(stdout).to eq('')
363
+ expect(stderr).to eq('')
364
+ end
365
+
366
+ # (lb): Get 2 more lines of coverage.
367
+ it 'as dynamic edit no field with update from string' do
368
+ stdout, stderr = murano_command_run(
369
+ 'element edit',
370
+ '-e',
371
+ %(={"bizid":"55cop31gs89","access":"private","attachment":{},"contact":"#{rname}","description":"For testing Murano CLI element-edit.\\n\\nTEST2222\\n\\nTEST MORE","image":{"detail":{"color":"#224123","filename":"","url":""},"thumbnail":{"color":"#224655","filename":"xxx","url":""}},"markdown":"AxxxxAA2333BBBCCC\\n","name":"MUR-6407 Test Element","source":{"from":"service","name":"mur6407testelement","url":"https://testtesttest.apps.exosite-dev.io/swagger-mur-6407.yaml"},"specs":"","type":"service","tags":[],"active":true,"elementId":"5ae3600e313d01708c2a6e0f"}),
372
+ 'MUR-6407'
373
+ )
374
+ expect(stdout).to eq('')
375
+ expect(stderr).to eq('')
376
+ end
377
+
378
+ # (lb): Get 2 more lines of coverage.
379
+ it 'as dynamic edit no field with update from string not valid Hash' do
380
+ stdout, stderr = murano_command_exits(
381
+ 'element edit', '-e', %(={), 'MUR-6407'
382
+ )
383
+ expect(stdout).to eq('')
384
+ expect(stderr).to eq %(The document object is not a Hash: {\n765: unexpected token at '{'\n)
385
+ end
386
+
387
+ # (lb): No additional coverage, other than integration.
388
+ it 'as dynamic edit no field with update from string fields not allowed' do
389
+ stdout, stderr = murano_command_run(
390
+ 'element edit',
391
+ '-e',
392
+ %(={"tiers":[],"bizid":"55cop31gs89","access":"private","attachment":{},"contact":"#{rname}","description":"For testing Murano CLI element-edit.\\n\\nTEST2222\\n\\nTEST MORE","image":{"detail":{"color":"#224123","filename":"","url":""},"thumbnail":{"color":"#224655","filename":"xxx","url":""}},"markdown":"AxxxxAA2333BBBCCC\\n","name":"MUR-6407 Test Element","source":{"from":"service","name":"mur6407testelement","url":"https://testtesttest.apps.exosite-dev.io/swagger-mur-6407.yaml"},"specs":"","type":"service","tags":[],"active":true,"approval":"approved","elementId":"5ae3600e313d01708c2a6e0f"}),
393
+ 'MUR-6407'
394
+ )
395
+ expect(stdout).to eq('')
396
+ expect(stderr).to eq %(Request Failed: 400: [400] "tiers" is not allowed. "approval" is not allowed\n)
397
+ end
398
+ end
399
+ end
400
+
@@ -39,7 +39,7 @@ RSpec.describe 'murano exchange', :cmd, :needs_password do
39
39
  expect(lines[3]).to match(/^(\+-+){#{num_cols}}\+$/)
40
40
  # Content. Starts with elementId.
41
41
  (4..(lines.length - 2)).to_a.each do |line|
42
- expect(lines[line]).to match(/^\| [0-9a-f]+ \| /)
42
+ expect(lines[line]).to match(/^\| [0-9a-f]+ +\| /)
43
43
  end
44
44
  expect(lines[-1]).to match(/^(\+-+){#{num_cols}}\+$/)
45
45
  end
@@ -51,7 +51,7 @@ RSpec.describe 'murano exchange', :cmd, :needs_password do
51
51
  expect_exchange_element_table(stdout, stderr, num_cols: 4)
52
52
  end
53
53
 
54
- it 'only ids' do
54
+ it 'only ids', :club_20s, :club_10s do
55
55
  stdout, stderr = murano_command_run('exchange list', '--idonly')
56
56
  expect(stderr).to eq('')
57
57
  stdout.lines.each do |line|