moose-inventory 2.0 → 2.1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +2 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +21 -0
  5. data/BACKLOG.md +630 -8
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +1 -1
  8. data/README.md +315 -39
  9. data/Rakefile +2 -0
  10. data/bin/moose-inventory +2 -1
  11. data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
  12. data/docs/compatibility/cli-output-compatibility.md +76 -0
  13. data/docs/governance/approval-register.md +37 -0
  14. data/docs/maintenance/database-backup-restore-guidance.md +162 -0
  15. data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
  16. data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
  17. data/docs/product/product-brief.md +161 -0
  18. data/docs/product/requirements-baseline.md +477 -0
  19. data/docs/qa/qa-documentation-and-release-gates.md +283 -0
  20. data/docs/release/package-provenance-hardening.md +126 -0
  21. data/docs/release/publishing.md +11 -3
  22. data/docs/release/release-environment-protection.md +70 -0
  23. data/docs/release/release-readiness.md +23 -4
  24. data/docs/security/accepted-risk-register.md +84 -0
  25. data/docs/security/security-privacy-process.md +287 -0
  26. data/docs/security-audit-2026-05-26-rerun.md +2 -2
  27. data/docs/ux/cli-workflow-notes.md +287 -0
  28. data/examples/ansible/ansible.cfg +3 -0
  29. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  30. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  31. data/examples/ci/README.md +16 -0
  32. data/examples/ci/github-actions/inventory-review.yml +38 -0
  33. data/examples/ci/inventory/example-snapshot.yml +19 -0
  34. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  35. data/lib/moose_inventory/cli/application.rb +133 -5
  36. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  37. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  38. data/lib/moose_inventory/cli/audit.rb +62 -0
  39. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  40. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  41. data/lib/moose_inventory/cli/console.rb +135 -0
  42. data/lib/moose_inventory/cli/db.rb +64 -0
  43. data/lib/moose_inventory/cli/factory.rb +28 -0
  44. data/lib/moose_inventory/cli/formatter.rb +8 -12
  45. data/lib/moose_inventory/cli/group.rb +5 -2
  46. data/lib/moose_inventory/cli/group_add.rb +11 -9
  47. data/lib/moose_inventory/cli/group_addchild.rb +23 -65
  48. data/lib/moose_inventory/cli/group_addhost.rb +16 -67
  49. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  50. data/lib/moose_inventory/cli/group_get.rb +8 -42
  51. data/lib/moose_inventory/cli/group_list.rb +7 -40
  52. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  53. data/lib/moose_inventory/cli/group_rm.rb +12 -10
  54. data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
  55. data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
  56. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  57. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  58. data/lib/moose_inventory/cli/helpers.rb +68 -1
  59. data/lib/moose_inventory/cli/host.rb +6 -3
  60. data/lib/moose_inventory/cli/host_add.rb +69 -29
  61. data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
  62. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  63. data/lib/moose_inventory/cli/host_get.rb +9 -37
  64. data/lib/moose_inventory/cli/host_list.rb +24 -21
  65. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  66. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  67. data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
  68. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  69. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  70. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  71. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  72. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  73. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  74. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  75. data/lib/moose_inventory/config/config.rb +185 -108
  76. data/lib/moose_inventory/db/db.rb +170 -195
  77. data/lib/moose_inventory/db/exceptions.rb +6 -3
  78. data/lib/moose_inventory/db/models.rb +16 -0
  79. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  80. data/lib/moose_inventory/inventory_context.rb +68 -2
  81. data/lib/moose_inventory/operations/add_associations.rb +20 -16
  82. data/lib/moose_inventory/operations/add_groups.rb +21 -13
  83. data/lib/moose_inventory/operations/add_hosts.rb +30 -17
  84. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  85. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  86. data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
  87. data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
  88. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  89. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  90. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  91. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  92. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
  94. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  95. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  96. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  97. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  98. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  99. data/lib/moose_inventory/operations/remove_associations.rb +30 -18
  100. data/lib/moose_inventory/operations/remove_groups.rb +12 -12
  101. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  102. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  103. data/lib/moose_inventory/runtime_options.rb +31 -0
  104. data/lib/moose_inventory/version.rb +3 -1
  105. data/lib/moose_inventory.rb +10 -7
  106. data/moose-inventory.gemspec +19 -35
  107. data/scripts/check.sh +1 -0
  108. data/scripts/ci/check_generated_artifacts.sh +41 -0
  109. data/scripts/ci/check_permissions.sh +2 -0
  110. data/scripts/ci/check_rubocop.sh +30 -25
  111. data/scripts/files.rb +5 -4
  112. data/spec/examples/ci_examples_spec.rb +37 -0
  113. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  114. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  115. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
  116. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  117. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  118. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  119. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  120. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  121. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  122. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  123. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  124. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  125. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  126. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  127. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  128. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  129. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
  130. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
  131. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  132. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  133. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  134. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  135. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  136. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  137. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  138. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  139. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  140. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  141. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  142. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  143. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  144. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  145. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  146. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  147. data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
  148. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  149. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  150. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  151. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  152. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
  153. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
  154. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
  155. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  156. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
  157. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
  158. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  159. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  160. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  161. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  162. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
  163. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
  164. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  165. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  166. data/spec/shared/shared_config_setup.rb +4 -3
  167. data/spec/spec_helper.rb +50 -40
  168. data/spec/support/cli_harness.rb +33 -0
  169. metadata +80 -41
@@ -1,48 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
1
4
  require 'spec_helper'
2
5
 
3
- # TODO: the usual respond_to? method doesn't seem to work on Thor objects.
4
6
  # Why not? For now, we'll check against instance_methods.
5
7
 
6
8
  RSpec.describe Moose::Inventory::Cli::Group do
7
9
  before(:all) do
8
- # Set up the configuration object
9
- @mockarg_parts = {
10
- config: File.join(spec_root, 'config/config.yml'),
11
- format: 'yaml',
12
- env: 'test',
13
- }
14
-
15
- @mockargs = []
16
- @mockarg_parts.each do |key, val|
17
- @mockargs << "--#{key}"
18
- @mockargs << val
19
- end
20
-
21
- @config = Moose::Inventory::Config
22
- @config.init(@mockargs)
23
-
24
- @db = Moose::Inventory::DB
25
- @db.init if @db.db.nil?
26
-
27
- @console = Moose::Inventory::Cli::Formatter
28
- @group = Moose::Inventory::Cli::Group
29
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Group, command_ivar: :@group)
30
11
  end
31
12
 
32
13
  before(:each) do
33
- @db.reset
14
+ reset_cli_harness
34
15
  end
35
16
 
36
17
  describe 'rmvar' do
37
18
  it 'should be responsive' do
38
- result = @group.instance_methods(false).include?(:rmvar)
19
+ result = @group.method_defined?(:rmvar, false)
39
20
  expect(result).to eq(true)
40
21
  end
41
22
 
42
23
  #-----------------
43
24
  it '<missing args> ... should abort with an error' do
44
25
  actual = runner do
45
- @app.start(%w(group rmvar))
26
+ @app.start(%w[group rmvar])
46
27
  end
47
28
 
48
29
  # Check output
@@ -56,16 +37,16 @@ RSpec.describe Moose::Inventory::Cli::Group do
56
37
  group_name = 'does-not-exist'
57
38
  var_name = 'foo=bar'
58
39
  actual = runner do
59
- @app.start(%W(group rmvar #{group_name} #{var_name}))
40
+ @app.start(%W[group rmvar #{group_name} #{var_name} --yes])
60
41
  end
61
42
 
62
43
  # Check output
63
44
  desired = { aborted: true }
64
45
  desired[:STDOUT] =
65
- "Remove variable(s) '#{var_name}' from group '#{group_name}':\n"\
66
- " - retrieve group '#{group_name}'...\n"
46
+ "Remove variable(s) '#{var_name}' from group '#{group_name}':\n " \
47
+ "- retrieve group '#{group_name}'...\n"
67
48
  desired[:STDERR] =
68
- "An error occurred during a transaction, any changes have been rolled back.\n"\
49
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
69
50
  "ERROR: The group '#{group_name}' does not exist.\n"
70
51
  expected(actual, desired)
71
52
  end
@@ -77,44 +58,57 @@ RSpec.describe Moose::Inventory::Cli::Group do
77
58
 
78
59
  group_name = 'test1'
79
60
  @db.models[:group].create(name: group_name)
80
-
81
- var = { name: 'foo', value: 'bar' }
82
- cases = %w(
61
+ cases = %w[
83
62
  =bar
84
63
  foo=bar=
85
64
  =foo=bar
86
65
  foo=bar=extra
87
- )
66
+ ]
88
67
 
89
68
  cases.each do |args|
90
69
  actual = runner do
91
- @app.start(%W(group rmvar #{group_name} #{args}))
70
+ @app.start(%W[group rmvar #{group_name} #{args} --yes])
92
71
  end
93
72
  # @console.out(actual,'p')
94
73
 
95
74
  desired = { aborted: true }
96
75
  desired[:STDOUT] =
97
- "Remove variable(s) '#{args}' from group '#{group_name}':\n"\
98
- " - retrieve group '#{group_name}'...\n"\
99
- " - OK\n"\
100
- " - remove variable '#{args}'...\n"
76
+ "Remove variable(s) '#{args}' from group '#{group_name}':\n " \
77
+ "- retrieve group '#{group_name}'...\n " \
78
+ "- OK\n " \
79
+ "- remove variable '#{args}'...\n"
101
80
  desired[:STDERR] =
102
- "An error occurred during a transaction, any changes have been rolled back.\n"\
81
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
103
82
  "ERROR: Incorrect format in {#{args}}. Expected 'key' or 'key=value'.\n"
104
83
 
105
84
  expected(actual, desired)
106
85
  end
107
86
  end
108
87
 
88
+ #------------------------
89
+ it 'GROUP key --dry-run should not remove the group variable' do
90
+ group_name = 'group_test'
91
+ @db.models[:group].create(name: group_name)
92
+ runner { @app.start(%W[group addvar #{group_name} var1=val1]) }
93
+
94
+ actual = runner { @app.start(%W[group rmvar #{group_name} var1 --dry-run]) }
95
+
96
+ expect(actual[:unexpected]).to eq(false)
97
+ expect(actual[:aborted]).to eq(false)
98
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
99
+ group = @db.models[:group].find(name: group_name)
100
+ expect(group.groupvars_dataset[name: 'var1']).not_to be_nil
101
+ end
102
+
109
103
  #------------------------
110
104
  it 'GROUP <valid args> ... should remove the group variable' do
111
105
  group_name = 'group_test'
112
106
  var = { name: 'foo', value: 'bar' }
113
- cases = %W(
107
+ cases = %W[
114
108
  #{var[:name]}
115
109
  #{var[:name]}=
116
110
  #{var[:name]}=#{var[:value]}
117
- )
111
+ ]
118
112
  cases.each do |example|
119
113
  # reset the db
120
114
  @db.reset
@@ -122,24 +116,24 @@ RSpec.describe Moose::Inventory::Cli::Group do
122
116
  # Add an initial group and groupvar
123
117
  @db.models[:group].create(name: group_name)
124
118
  runner do
125
- @app.start(%W(group addvar #{group_name} #{var[:name]}=#{var[:value]}))
119
+ @app.start(%W[group addvar #{group_name} #{var[:name]}=#{var[:value]}])
126
120
  end
127
121
 
128
122
  # Try to remove the groupvar using the case example valid args
129
123
  actual = runner do
130
- @app.start(%W(group rmvar #{group_name} #{example}))
124
+ @app.start(%W[group rmvar #{group_name} #{example} --yes])
131
125
  end
132
126
  # @console.out(actual,'p')
133
127
 
134
128
  # Check the output
135
129
  desired = { aborted: false }
136
130
  desired[:STDOUT] =
137
- "Remove variable(s) '#{example}' from group '#{group_name}':\n"\
138
- " - retrieve group '#{group_name}'...\n"\
139
- " - OK\n"\
140
- " - remove variable '#{example}'...\n"\
141
- " - OK\n"\
142
- " - all OK\n"\
131
+ "Remove variable(s) '#{example}' from group '#{group_name}':\n " \
132
+ "- retrieve group '#{group_name}'...\n " \
133
+ "- OK\n " \
134
+ "- remove variable '#{example}'...\n " \
135
+ "- OK\n " \
136
+ "- all OK\n" \
143
137
  "Succeeded.\n"
144
138
 
145
139
  # @console.out(desired,'p')
@@ -160,36 +154,35 @@ RSpec.describe Moose::Inventory::Cli::Group do
160
154
  group_name = 'test_group'
161
155
  varsarray = [
162
156
  { name: 'var1', value: 'val1' },
163
- { name: 'var2', value: 'val2' },
157
+ { name: 'var2', value: 'val2' }
164
158
  ]
165
159
 
166
- vars = []
167
- varsarray.each do |var|
168
- vars << "#{var[:name]}=#{var[:value]}"
160
+ vars = varsarray.map do |var|
161
+ "#{var[:name]}=#{var[:value]}"
169
162
  end
170
163
 
171
164
  @db.models[:group].create(name: group_name)
172
- actual = runner do
173
- @app.start(%W(group addvar #{group_name}) + vars)
165
+ runner do
166
+ @app.start(%W[group addvar #{group_name}] + vars)
174
167
  end
175
168
 
176
169
  actual = runner do
177
- @app.start(%W(group rmvar #{group_name}) + vars)
170
+ @app.start(%W[group rmvar #{group_name} --yes] + vars)
178
171
  end
179
172
  # @console.out(actual,'y')
180
173
 
181
174
  desired = { aborted: false }
182
175
  desired[:STDOUT] =
183
- "Remove variable(s) '#{vars.join(',')}' from group '#{group_name}':\n"\
184
- " - retrieve group '#{group_name}'...\n"\
185
- " - OK\n"
176
+ "Remove variable(s) '#{vars.join(',')}' from group '#{group_name}':\n " \
177
+ "- retrieve group '#{group_name}'...\n " \
178
+ "- OK\n"
186
179
  vars.each do |var|
187
180
  desired[:STDOUT] = desired[:STDOUT] +
188
- " - remove variable '#{var}'...\n"\
189
- " - OK\n"
181
+ " - remove variable '#{var}'...\n " \
182
+ "- OK\n"
190
183
  end
191
184
  desired[:STDOUT] = desired[:STDOUT] +
192
- " - all OK\n"\
185
+ " - all OK\n" \
193
186
  "Succeeded.\n"
194
187
  expected(actual, desired)
195
188
 
@@ -200,3 +193,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
200
193
  end
201
194
  end
202
195
  end
196
+ # rubocop:enable Metrics/BlockLength
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  require_relative 'group_add_spec'
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ # rubocop:disable Metrics/BlockLength
6
+ RSpec.describe Moose::Inventory::Cli::Helpers do
7
+ subject(:helper) { helper_class.new }
8
+
9
+ let(:helper_class) do
10
+ Class.new do
11
+ include Moose::Inventory::Cli::Helpers
12
+ end
13
+ end
14
+
15
+ let(:inventory_context) { instance_double(Moose::Inventory::InventoryContext) }
16
+ let(:automatic_group) { instance_double('AutomaticGroup') }
17
+
18
+ before do
19
+ helper.instance_variable_set(:@inventory_context, inventory_context)
20
+ allow(inventory_context).to receive(:automatic_group).and_return(automatic_group)
21
+ end
22
+
23
+ describe 'small helper methods' do
24
+ it 'checks whether an association dataset contains a named record' do
25
+ dataset = instance_double('Dataset')
26
+
27
+ expect(helper.send(:association_exists?, nil, 'alpha')).to eq(false)
28
+ allow(dataset).to receive(:[]).with(name: 'alpha').and_return(nil)
29
+ expect(helper.send(:association_exists?, dataset, 'alpha')).to eq(false)
30
+ allow(dataset).to receive(:[]).with(name: 'beta').and_return({ name: 'beta' })
31
+ expect(helper.send(:association_exists?, dataset, 'beta')).to eq(true)
32
+ end
33
+
34
+ it 'converts exceptions to strings' do
35
+ expect(helper.send(:exception_to_s, RuntimeError.new('boom'))).to eq('boom')
36
+ end
37
+
38
+ it 'returns the automatic group through the inventory context' do
39
+ expect(helper.send(:automatic_group)).to eq(automatic_group)
40
+ end
41
+ end
42
+
43
+ describe 'run_group_relation_transaction' do
44
+ it 'prints the heading and success marker for a successful transaction' do
45
+ allow(Moose::Inventory::DB).to receive(:transaction).and_yield
46
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, '- all OK')
47
+
48
+ result = nil
49
+ actual = runner do
50
+ result = helper.send(:run_group_relation_transaction, heading: 'Heading') { :done }
51
+ end
52
+
53
+ expect(result).to eq(:done)
54
+ expected(actual, aborted: false, STDOUT: "Heading\n", STDERR: '')
55
+ end
56
+
57
+ it 'aborts with the Moose exception message when no custom handler is supplied' do
58
+ error = Moose::Inventory::DB::MooseDBException.new('boom')
59
+ allow(Moose::Inventory::DB).to receive(:transaction).and_raise(error)
60
+ allow(Moose::Inventory::DB).to receive(:exceptions).and_return({ moose: Moose::Inventory::DB::MooseDBException })
61
+
62
+ actual = runner do
63
+ helper.send(:run_group_relation_transaction, heading: 'Heading') { :done }
64
+ end
65
+
66
+ expected(actual, aborted: true, STDOUT: '', STDERR: "ERROR: boom\n")
67
+ end
68
+
69
+ it 'uses the custom Moose exception handler when provided' do
70
+ error = Moose::Inventory::DB::MooseDBException.new('boom')
71
+ allow(Moose::Inventory::DB).to receive(:transaction).and_raise(error)
72
+ allow(Moose::Inventory::DB).to receive(:exceptions).and_return({ moose: Moose::Inventory::DB::MooseDBException })
73
+
74
+ actual = runner do
75
+ helper.send(
76
+ :run_group_relation_transaction,
77
+ heading: 'Heading',
78
+ on_error: ->(e) { "wrapped #{e.message}" }
79
+ ) { :done }
80
+ end
81
+
82
+ expected(actual, aborted: true, STDOUT: '', STDERR: "ERROR: wrapped boom\n")
83
+ end
84
+ end
85
+
86
+ describe 'automatic-group helpers' do
87
+ it 'removes the automatic group from a host when present' do
88
+ host = instance_double('Host')
89
+ dataset = instance_double('Dataset')
90
+ ungrouped = instance_double('Group')
91
+ allow(host).to receive(:groups_dataset).and_return(dataset)
92
+ allow(dataset).to receive(:[]).with(name: 'ungrouped').and_return(ungrouped)
93
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, 'remove auto')
94
+ expect(host).to receive(:remove_group).with(ungrouped)
95
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(4, '- OK')
96
+
97
+ helper.send(:remove_automatic_group_from_host, host, indent: 2, message: 'remove auto')
98
+ end
99
+
100
+ it 'does nothing when the automatic group is not present' do
101
+ host = instance_double('Host')
102
+ dataset = instance_double('Dataset')
103
+ allow(host).to receive(:groups_dataset).and_return(dataset)
104
+ allow(dataset).to receive(:[]).with(name: 'ungrouped').and_return(nil)
105
+
106
+ expect(Moose::Inventory::Cli::Formatter).not_to receive(:puts)
107
+ expect(host).not_to receive(:remove_group)
108
+
109
+ helper.send(:remove_automatic_group_from_host, host, indent: 2, message: 'remove auto')
110
+ end
111
+
112
+ it 'adds the automatic group when the last non-automatic group is removed' do
113
+ host = instance_double('Host')
114
+ dataset = instance_double('Dataset', count: 1)
115
+ allow(host).to receive(:groups_dataset).and_return(dataset)
116
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, 'add auto')
117
+ expect(host).to receive(:add_group).with(automatic_group)
118
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(4, '- OK')
119
+
120
+ helper.send(:add_automatic_group_to_host_if_last_group, host, indent: 2, message: 'add auto')
121
+ end
122
+
123
+ it 'adds the automatic group when a host has no groups' do
124
+ host = instance_double('Host')
125
+ dataset = instance_double('Dataset', count: 0)
126
+ allow(host).to receive(:groups_dataset).and_return(dataset)
127
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(2, 'add auto')
128
+ expect(host).to receive(:add_group).with(automatic_group)
129
+ expect(Moose::Inventory::Cli::Formatter).to receive(:puts).with(4, '- OK')
130
+
131
+ helper.send(:add_automatic_group_to_host_if_no_groups, host, indent: 2, message: 'add auto')
132
+ end
133
+
134
+ it 'does nothing when the group count does not match the requested threshold' do
135
+ host = instance_double('Host')
136
+ dataset = instance_double('Dataset', count: 2)
137
+ allow(host).to receive(:groups_dataset).and_return(dataset)
138
+
139
+ expect(Moose::Inventory::Cli::Formatter).not_to receive(:puts)
140
+ expect(host).not_to receive(:add_group)
141
+
142
+ helper.send(:add_automatic_group_to_host_if_group_count, host, 1, indent: 2, message: 'add auto')
143
+ end
144
+ end
145
+ end
146
+ # rubocop:enable Metrics/BlockLength