foreman_rh_cloud 13.2.6 → 13.2.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d11a81329e083956595eae07e1b13a0f337722d2316b73e37eaece51a05ff92
4
- data.tar.gz: 925d50c3713f68be27c6290fe67ac5a68b5a5cd7a3f78578ce5acc574469fcd8
3
+ metadata.gz: 04c6a1daea1675deb081565cf7d9e83e669a3fd01a577a032c9d7d5ba825cbce
4
+ data.tar.gz: 5172a5c764d0dbcb71cf47e19e423f0eae01db27636be594320dece4c6364719
5
5
  SHA512:
6
- metadata.gz: 9f0071fcff2a17f1e8d5ac6dacba1b7383a47822a179e6681a7fdb37469be9d4a0b3f3752d044ae64a34b839ded062ebe70db2a572c8b41368fe8b5e630c7d0b
7
- data.tar.gz: 26ee66163cb1b8e802b5c52c2cbfef4e3c4cdc25f2094b7de5a68c083bede2d046317b9e965d9df2c9cbecdabb7be0638b599928ceb87b9e6678195914d518e4
6
+ metadata.gz: 15b24817679825c5e60bc0e0ca0b33c18eadae7277483155c641f3d3d140bd911c0c7ad7dca15c40e3d63f9a8e354acfcd5789048658fd2888a33fa4b9741d0d
7
+ data.tar.gz: 0cfe77f01b645de55c8e3e41ac4411201c5ec4819e5926d3054bac08b551e12dc923dffa6b1503956cb1075c688a5a3d94e5a63a8c4e332414e10f78054d3954
@@ -21,7 +21,8 @@ module RhCloudHost
21
21
  scoped_search :relation => :inventory_sync_status_object, :on => :status, :rename => :insights_inventory_sync_status,
22
22
  :complete_value => { :disconnect => ::InventorySync::InventoryStatus::DISCONNECT,
23
23
  :sync => ::InventorySync::InventoryStatus::SYNC }
24
- scoped_search :relation => :insights, :on => :uuid, :only_explicit => true, :rename => :insights_uuid
24
+ scoped_search :on => :id, :rename => :insights_uuid, :only_explicit => true,
25
+ :ext_method => :search_by_insights_uuid, :complete_value => false
25
26
 
26
27
  def insights_facet
27
28
  insights
@@ -41,4 +42,33 @@ module RhCloudHost
41
42
  insights_facet.update!(uuid: subscription_facet.uuid)
42
43
  end
43
44
  end
45
+
46
+ module ClassMethods
47
+ def search_by_insights_uuid(_key, operator, value)
48
+ # Determine which facet table to search based on IoP mode
49
+ facet_table = ForemanRhCloud.with_iop_smart_proxy? ? Katello::Host::SubscriptionFacet.table_name : InsightsFacet.table_name
50
+
51
+ # Build SQL condition
52
+ if ['IN', 'NOT IN'].include?(operator)
53
+ # For IN/NOT IN, value may be an array or comma-separated string
54
+ # Convert to array and build placeholders for each value
55
+ values = value.is_a?(Array) ? value : value.to_s.split(',').map(&:strip)
56
+ placeholders = (['?'] * values.size).join(',')
57
+ condition = sanitize_sql_for_conditions(
58
+ ["#{facet_table}.uuid #{operator} (#{placeholders})", *values]
59
+ )
60
+ else
61
+ # For other operators (=, !=, LIKE, etc.), use value_to_sql for proper SQL formatting
62
+ condition = sanitize_sql_for_conditions(
63
+ ["#{facet_table}.uuid #{operator} ?", value_to_sql(operator, value)]
64
+ )
65
+ end
66
+
67
+ # Return search parameters with LEFT JOIN to include hosts without facets
68
+ {
69
+ joins: "LEFT JOIN #{facet_table} ON #{facet_table}.host_id = #{Host::Managed.table_name}.id",
70
+ conditions: condition,
71
+ }
72
+ end
73
+ end
44
74
  end
@@ -7,9 +7,15 @@ module ForemanInventoryUpload
7
7
 
8
8
  def run
9
9
  organization = ::Organization.find(input[:organization_id])
10
- hosts_without_facets = ::ForemanInventoryUpload::Generators::Queries.for_org(organization, hosts_query: 'null? insights_uuid')
10
+ # Find hosts with subscription facets but without insights facets
11
+ # Note: We can't use scoped_search 'null? insights_uuid' because the null? operator
12
+ # doesn't work with ext_methods - it would check hosts.id IS NULL instead of the facet
13
+ hosts_without_facets = ::ForemanInventoryUpload::Generators::Queries.for_org(organization, use_batches: false)
14
+ .left_outer_joins(:insights)
15
+ .where(insights_facets: { id: nil })
16
+
11
17
  facet_count = 0
12
- hosts_without_facets.each do |batch|
18
+ hosts_without_facets.in_batches(of: ForemanInventoryUpload.slice_size) do |batch|
13
19
  facets = batch.pluck(:id, 'katello_subscription_facets.uuid').map do |host_id, uuid|
14
20
  {
15
21
  host_id: host_id,
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '13.2.6'.freeze
2
+ VERSION = '13.2.8'.freeze
3
3
  end
@@ -6,6 +6,8 @@ module InsightsCloud
6
6
  class VmaasReposcanSync < ::Actions::EntryAction
7
7
  include ::ForemanRhCloud::CertAuth
8
8
 
9
+ HTTP_TOO_MANY_REQUESTS = 429
10
+
9
11
  # Subscribe to Katello repository sync hook action, if available
10
12
  def self.subscribe
11
13
  'Actions::Katello::Repository::SyncHook'.constantize
@@ -49,15 +51,9 @@ module InsightsCloud
49
51
 
50
52
  response
51
53
  rescue RestClient::ExceptionWithResponse => e
52
- message = "VMaaS reposcan sync failed: #{e.response&.code} - #{e.response&.body}"
53
- logger.error(message)
54
- output[:message] = message
55
- raise
54
+ handle_rest_client_error(e)
56
55
  rescue StandardError => e
57
- message = "Error triggering VMaaS reposcan sync: #{e.message}, response: #{e.respond_to?(:response) ? e.response : nil}"
58
- logger.error(message)
59
- output[:message] = message
60
- raise
56
+ handle_standard_error(e)
61
57
  end
62
58
 
63
59
  def rescue_strategy_for_self
@@ -70,6 +66,25 @@ module InsightsCloud
70
66
 
71
67
  private
72
68
 
69
+ def handle_rest_client_error(exception)
70
+ if exception.response&.code == HTTP_TOO_MANY_REQUESTS
71
+ message = "VMaaS reposcan sync skipped: another sync already in progress (#{HTTP_TOO_MANY_REQUESTS})"
72
+ logger.warn(message)
73
+ else
74
+ message = "VMaaS reposcan sync failed: #{exception.response&.code} - #{exception.response&.body}"
75
+ logger.error(message)
76
+ end
77
+ output[:message] = message
78
+ # Do NOT raise - let rescue_strategy_for_self Skip handle this
79
+ end
80
+
81
+ def handle_standard_error(exception)
82
+ message = "Error triggering VMaaS reposcan sync: #{exception.message}"
83
+ logger.error(message)
84
+ output[:message] = message
85
+ # Do NOT raise - let rescue_strategy_for_self Skip handle this
86
+ end
87
+
73
88
  def logger
74
89
  action_logger
75
90
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "13.2.6",
3
+ "version": "13.2.7",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,19 +5,17 @@ class VmaasReposcanSyncTest < ActiveSupport::TestCase
5
5
  include ForemanTasks::TestHelpers::WithInThreadExecutor
6
6
 
7
7
  setup do
8
- @root = FactoryBot.build(:katello_root_repository, :fedora_17_x86_64_dev_root)
9
- @root.save(validate: false)
10
- @repo = FactoryBot.create(
11
- :katello_repository,
12
- :with_product,
13
- distribution_family: 'Red Hat',
14
- distribution_version: '7.5',
15
- root: @root
16
- )
8
+ @organization = FactoryBot.create(:organization)
9
+ # Create a simple repository - we only need id and organization_id for the action
10
+ @repo = ::Katello::Repository.new(id: 1)
11
+ @repo.stubs(:organization_id).returns(@organization.id)
12
+ ::Katello::Repository.stubs(:find).with(1).returns(@repo)
13
+
17
14
  @repo_payload = { id: @repo.id }
18
15
  @expected_url = 'https://example.com/api/v1/vmaas/reposcan/sync'
19
16
  InsightsCloud.stubs(:vmaas_reposcan_sync_url).returns(@expected_url)
20
17
  ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
18
+ Organization.stubs(:find).with(@organization.id).returns(@organization)
21
19
  end
22
20
 
23
21
  teardown do
@@ -83,7 +81,7 @@ class VmaasReposcanSyncTest < ActiveSupport::TestCase
83
81
  params[:url] == @expected_url &&
84
82
  params[:headers].is_a?(Hash) &&
85
83
  params[:headers]['Content-Type'] == 'application/json' &&
86
- params[:organization] == @repo.organization
84
+ params[:organization] == @organization
87
85
  end
88
86
  .returns(mock_response)
89
87
 
@@ -116,37 +114,94 @@ class VmaasReposcanSyncTest < ActiveSupport::TestCase
116
114
  .stubs(:execute_cloud_request)
117
115
  .raises(exception)
118
116
 
119
- error = assert_raises(ForemanTasks::TaskError) do
120
- ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
121
- end
117
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
122
118
 
123
- assert_equal 'VMaaS reposcan sync failed: 500 - Server Error', error.task.output[:message]
119
+ assert_equal 'VMaaS reposcan sync failed: 500 - Server Error', task.output[:message]
124
120
  end
125
121
 
126
122
  test 'run sets error message in task output for StandardError exception' do
123
+ mock_logger = mock('logger')
124
+ mock_logger.expects(:error).with('Error triggering VMaaS reposcan sync: Network timeout')
125
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
126
+
127
127
  InsightsCloud::Async::VmaasReposcanSync.any_instance
128
128
  .stubs(:execute_cloud_request)
129
129
  .raises(StandardError.new('Network timeout'))
130
130
 
131
- error = assert_raises(ForemanTasks::TaskError) do
132
- ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
133
- end
131
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
134
132
 
135
- # The task is available via main_action
136
- assert_match(/Error triggering VMaaS reposcan sync: Network timeout, response: /,
137
- error.task.main_action.output[:message])
133
+ assert_equal 'Error triggering VMaaS reposcan sync: Network timeout', task.output[:message]
138
134
  end
139
135
 
140
- test 'run logs and re-raises when cloud request returns error response' do
141
- error_response = mock('error_response', code: 500, body: 'error')
136
+ test 'run logs and handles error response without raising' do
137
+ error_response = mock('error_response')
138
+ error_response.stubs(:code).returns(500)
139
+ error_response.stubs(:body).returns('error')
142
140
  exception = RestClient::ExceptionWithResponse.new(error_response)
143
141
 
144
142
  InsightsCloud::Async::VmaasReposcanSync.any_instance
145
143
  .stubs(:execute_cloud_request)
146
144
  .raises(exception)
147
145
 
148
- assert_raises(ForemanTasks::TaskError) do
149
- ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
150
- end
146
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
147
+
148
+ assert_equal 'VMaaS reposcan sync failed: 500 - error', task.output[:message]
149
+ end
150
+
151
+ test 'run handles 429 error with warning log level' do
152
+ error_response = mock('error_response')
153
+ error_response.stubs(:code).returns(429)
154
+ error_response.stubs(:body).returns('{"msg": "Another task already in progress"}')
155
+ exception = RestClient::ExceptionWithResponse.new(error_response)
156
+
157
+ mock_logger = mock('logger')
158
+ mock_logger.expects(:warn).with('VMaaS reposcan sync skipped: another sync already in progress (429)')
159
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
160
+
161
+ InsightsCloud::Async::VmaasReposcanSync.any_instance
162
+ .stubs(:execute_cloud_request)
163
+ .raises(exception)
164
+
165
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
166
+
167
+ assert_equal 'VMaaS reposcan sync skipped: another sync already in progress (429)',
168
+ task.output[:message]
169
+ end
170
+
171
+ test 'run handles non-429 errors with error log level' do
172
+ error_response = mock('error_response')
173
+ error_response.stubs(:code).returns(500)
174
+ error_response.stubs(:body).returns('Internal Server Error')
175
+ exception = RestClient::ExceptionWithResponse.new(error_response)
176
+
177
+ mock_logger = mock('logger')
178
+ mock_logger.expects(:error).with('VMaaS reposcan sync failed: 500 - Internal Server Error')
179
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
180
+
181
+ InsightsCloud::Async::VmaasReposcanSync.any_instance
182
+ .stubs(:execute_cloud_request)
183
+ .raises(exception)
184
+
185
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
186
+
187
+ assert_equal 'VMaaS reposcan sync failed: 500 - Internal Server Error',
188
+ task.output[:message]
189
+ end
190
+
191
+ test 'run handles RestClient::ExceptionWithResponse with nil response' do
192
+ exception = RestClient::ExceptionWithResponse.new(nil)
193
+
194
+ mock_logger = mock('logger')
195
+ mock_logger.expects(:error).with('VMaaS reposcan sync failed: - ')
196
+ InsightsCloud::Async::VmaasReposcanSync.any_instance.stubs(:logger).returns(mock_logger)
197
+
198
+ InsightsCloud::Async::VmaasReposcanSync.any_instance
199
+ .stubs(:execute_cloud_request)
200
+ .raises(exception)
201
+
202
+ task = ForemanTasks.sync_task(InsightsCloud::Async::VmaasReposcanSync, @repo_payload)
203
+
204
+ refute_nil task.output[:message]
205
+ assert_equal 'VMaaS reposcan sync failed: - ', task.output[:message]
151
206
  end
152
207
  end
@@ -188,4 +188,158 @@ class RhCloudHostTest < ActiveSupport::TestCase
188
188
  assert_equal local_uuid, @host.insights_uuid
189
189
  end
190
190
  end
191
+
192
+ context 'scoped search on insights_uuid' do
193
+ setup do
194
+ @org = FactoryBot.create(:organization)
195
+ end
196
+
197
+ teardown do
198
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
199
+ end
200
+
201
+ test 'searches insights_facet.uuid in non-IoP mode with = operator' do
202
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
203
+ host1 = FactoryBot.create(:host, :managed, organization: @org)
204
+ host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'insights-uuid-123')
205
+ host2 = FactoryBot.create(:host, :managed, organization: @org)
206
+ host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'insights-uuid-456')
207
+
208
+ results = Host::Managed.search_for('insights_uuid = insights-uuid-123')
209
+
210
+ assert_includes results, host1
211
+ assert_not_includes results, host2
212
+ end
213
+
214
+ test 'searches subscription_facet.uuid in IoP mode with = operator' do
215
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
216
+ host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
217
+ host2 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
218
+
219
+ # Even if insights_facet has different UUID, should use subscription_facet UUID
220
+ host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'stale-123')
221
+
222
+ results = Host::Managed.search_for("insights_uuid = #{host1.subscription_facet.uuid}")
223
+
224
+ assert_includes results, host1
225
+ assert_not_includes results, host2
226
+ end
227
+
228
+ test 'searches with ^ operator (IN) in non-IoP mode' do
229
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
230
+ host1 = FactoryBot.create(:host, :managed, organization: @org)
231
+ host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'uuid-1')
232
+ host2 = FactoryBot.create(:host, :managed, organization: @org)
233
+ host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'uuid-2')
234
+ host3 = FactoryBot.create(:host, :managed, organization: @org)
235
+ host3.insights = FactoryBot.create(:insights_facet, host_id: host3.id, uuid: 'uuid-3')
236
+
237
+ results = Host::Managed.search_for('insights_uuid ^ (uuid-1,uuid-2)')
238
+
239
+ assert_includes results, host1
240
+ assert_includes results, host2
241
+ assert_not_includes results, host3
242
+ end
243
+
244
+ test 'searches with ^ operator (IN) in IoP mode - THE BUG FIX' do
245
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
246
+ host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
247
+ host2 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
248
+ host3 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
249
+
250
+ # Create insights facets with stale UUIDs to verify we're using subscription_facet
251
+ host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'stale-1')
252
+ host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'stale-2')
253
+ host3.insights = FactoryBot.create(:insights_facet, host_id: host3.id, uuid: 'stale-3')
254
+
255
+ uuid1 = host1.subscription_facet.uuid
256
+ uuid2 = host2.subscription_facet.uuid
257
+
258
+ # This is the search query that remediation modal creates
259
+ results = Host::Managed.search_for("insights_uuid ^ (#{uuid1},#{uuid2})")
260
+
261
+ # Should find hosts by subscription_facet UUID, not insights_facet UUID
262
+ assert_includes results, host1
263
+ assert_includes results, host2
264
+ assert_not_includes results, host3
265
+ end
266
+
267
+ test 'searches with !^ operator (NOT IN) in non-IoP mode' do
268
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
269
+ host1 = FactoryBot.create(:host, :managed, organization: @org)
270
+ host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'uuid-1')
271
+ host2 = FactoryBot.create(:host, :managed, organization: @org)
272
+ host2.insights = FactoryBot.create(:insights_facet, host_id: host2.id, uuid: 'uuid-2')
273
+ host3 = FactoryBot.create(:host, :managed, organization: @org)
274
+ host3.insights = FactoryBot.create(:insights_facet, host_id: host3.id, uuid: 'uuid-3')
275
+
276
+ results = Host::Managed.search_for('insights_uuid !^ (uuid-1,uuid-2)')
277
+
278
+ assert_not_includes results, host1
279
+ assert_not_includes results, host2
280
+ assert_includes results, host3
281
+ end
282
+
283
+ test 'searches with !^ operator (NOT IN) in IoP mode' do
284
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
285
+ host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
286
+ host2 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
287
+ host3 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
288
+
289
+ uuid1 = host1.subscription_facet.uuid
290
+ uuid2 = host2.subscription_facet.uuid
291
+
292
+ results = Host::Managed.search_for("insights_uuid !^ (#{uuid1},#{uuid2})")
293
+
294
+ assert_not_includes results, host1
295
+ assert_not_includes results, host2
296
+ assert_includes results, host3
297
+ end
298
+
299
+ test 'handles hosts without facets in non-IoP mode' do
300
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
301
+ host_without_facet = FactoryBot.create(:host, :managed, organization: @org)
302
+ host_with_facet = FactoryBot.create(:host, :managed, organization: @org)
303
+ host_with_facet.insights = FactoryBot.create(:insights_facet, host_id: host_with_facet.id, uuid: 'uuid-1')
304
+
305
+ results = Host::Managed.search_for('insights_uuid = uuid-1')
306
+
307
+ assert_includes results, host_with_facet
308
+ assert_not_includes results, host_without_facet
309
+ end
310
+
311
+ test 'handles hosts without subscription_facet in IoP mode' do
312
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
313
+ host_without_sub = FactoryBot.create(:host, :managed, organization: @org)
314
+ host_with_sub = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
315
+
316
+ uuid = host_with_sub.subscription_facet.uuid
317
+
318
+ results = Host::Managed.search_for("insights_uuid = #{uuid}")
319
+
320
+ assert_includes results, host_with_sub
321
+ assert_not_includes results, host_without_sub
322
+ end
323
+
324
+ test 'mode changes are reflected in searches' do
325
+ host1 = FactoryBot.create(:host, :managed, :with_subscription, organization: @org)
326
+ host1.insights = FactoryBot.create(:insights_facet, host_id: host1.id, uuid: 'insights-uuid-abc')
327
+ insights_uuid = 'insights-uuid-abc'
328
+ subscription_uuid = host1.subscription_facet.uuid
329
+
330
+ # Non-IoP mode: should find by insights_facet UUID
331
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
332
+ results = Host::Managed.search_for("insights_uuid = #{insights_uuid}")
333
+ assert_includes results, host1
334
+
335
+ # IoP mode: should find by subscription_facet UUID
336
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
337
+ results = Host::Managed.search_for("insights_uuid = #{subscription_uuid}")
338
+ assert_includes results, host1
339
+
340
+ # Should NOT find by old insights_facet UUID in IoP mode
341
+ results = Host::Managed.search_for("insights_uuid = #{insights_uuid}")
342
+ assert_not_includes results, host1
343
+ end
344
+ end
191
345
  end
@@ -10,7 +10,12 @@ const CVEsHostDetailsTab = ({ systemId }) => {
10
10
  const module = './SystemDetailTable';
11
11
  return (
12
12
  <div className="rh-cloud-insights-vulnerability-host-details-component vulnerability">
13
- <ScalprumComponent scope={scope} module={module} systemId={systemId} />
13
+ <ScalprumComponent
14
+ key={systemId}
15
+ scope={scope}
16
+ module={module}
17
+ systemId={systemId}
18
+ />
14
19
  </div>
15
20
  );
16
21
  };
@@ -11,14 +11,25 @@ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
11
11
  useForemanPermissions: () => new Set(['view_vulnerability']),
12
12
  }));
13
13
 
14
- jest.mock('@scalprum/react-core', () => ({
15
- ScalprumComponent: jest.fn(props => (
16
- <div data-testid="mock-scalprum-component">{JSON.stringify(props)}</div>
17
- )),
18
- ScalprumProvider: jest.fn(({ children }) => <div>{children}</div>),
19
- }));
14
+ const mockUnmountTracker = jest.fn();
15
+ jest.mock('@scalprum/react-core', () => {
16
+ const ReactMock = require('react');
17
+ return {
18
+ ScalprumComponent: jest.fn(props => {
19
+ ReactMock.useEffect(() => mockUnmountTracker, []);
20
+ return (
21
+ <div data-testid="mock-scalprum-component">{JSON.stringify(props)}</div>
22
+ );
23
+ }),
24
+ ScalprumProvider: jest.fn(({ children }) => <div>{children}</div>),
25
+ };
26
+ });
20
27
 
21
28
  describe('CVEsHostDetailsTabWrapper', () => {
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ });
32
+
22
33
  it('renders without crashing', () => {
23
34
  const { container } = render(
24
35
  <CVEsHostDetailsTabWrapper
@@ -31,4 +42,32 @@ describe('CVEsHostDetailsTabWrapper', () => {
31
42
  )
32
43
  ).toBeTruthy();
33
44
  });
45
+
46
+ it('remounts ScalprumComponent when systemId changes', () => {
47
+ const { ScalprumComponent } = require('@scalprum/react-core');
48
+
49
+ const { rerender } = render(
50
+ <CVEsHostDetailsTabWrapper
51
+ response={{ subscription_facet_attributes: { uuid: 'uuid-host-A' } }}
52
+ />
53
+ );
54
+
55
+ expect(mockUnmountTracker).not.toHaveBeenCalled();
56
+ expect(ScalprumComponent).toHaveBeenLastCalledWith(
57
+ expect.objectContaining({ systemId: 'uuid-host-A' }),
58
+ expect.anything()
59
+ );
60
+
61
+ rerender(
62
+ <CVEsHostDetailsTabWrapper
63
+ response={{ subscription_facet_attributes: { uuid: 'uuid-host-B' } }}
64
+ />
65
+ );
66
+
67
+ expect(mockUnmountTracker).toHaveBeenCalledTimes(1);
68
+ expect(ScalprumComponent).toHaveBeenLastCalledWith(
69
+ expect.objectContaining({ systemId: 'uuid-host-B' }),
70
+ expect.anything()
71
+ );
72
+ });
34
73
  });
@@ -128,17 +128,32 @@ NewHostDetailsTab.defaultProps = {
128
128
  const scope = 'advisor';
129
129
  const module = './SystemDetailWrapped';
130
130
 
131
- const IopInsightsTab = props => (
132
- <div className="advisor">
133
- <ScalprumComponent
134
- scope={scope}
135
- module={module}
136
- IopRemediationModal={RemediationModal}
137
- generateRuleUrl={generateRuleUrl}
138
- {...props}
139
- />
140
- </div>
141
- );
131
+ const IopInsightsTab = props => {
132
+ // eslint-disable-next-line camelcase
133
+ const systemId = props.response?.subscription_facet_attributes?.uuid;
134
+ return (
135
+ <div className="advisor">
136
+ <ScalprumComponent
137
+ key={systemId || props.hostName}
138
+ scope={scope}
139
+ module={module}
140
+ IopRemediationModal={RemediationModal}
141
+ generateRuleUrl={generateRuleUrl}
142
+ {...props}
143
+ />
144
+ </div>
145
+ );
146
+ };
147
+
148
+ IopInsightsTab.propTypes = {
149
+ hostName: PropTypes.string,
150
+ response: PropTypes.object,
151
+ };
152
+
153
+ IopInsightsTab.defaultProps = {
154
+ hostName: '',
155
+ response: {},
156
+ };
142
157
 
143
158
  const IopInsightsTabWrapped = props => {
144
159
  const permissions = useInsightsPermissions();
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_rh_cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.2.6
4
+ version: 13.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team