foreman_rh_cloud 13.2.10 → 13.2.11

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/concerns/rh_cloud_host.rb +36 -1
  3. data/app/services/foreman_rh_cloud/url_remediations_retriever.rb +19 -5
  4. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +2 -0
  5. data/lib/foreman_rh_cloud/version.rb +1 -1
  6. data/package.json +1 -1
  7. data/test/jobs/upload_report_direct_job_test.rb +29 -0
  8. data/test/unit/insights_facet_test.rb +5 -0
  9. data/test/unit/rh_cloud_host_test.rb +68 -0
  10. data/test/unit/services/foreman_rh_cloud/url_remediations_retriever_test.rb +82 -5
  11. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/__tests__/EmptyResults.test.js +10 -9
  12. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +13 -9
  13. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/__tests__/ErrorState.test.js +20 -9
  14. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +31 -8
  15. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/ListItemStatus.test.js +26 -10
  16. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +33 -9
  17. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListReducer.test.js +55 -35
  18. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +13 -9
  19. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilter.test.js +12 -15
  20. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +32 -12
  21. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +15 -7
  22. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorButton.test.js +47 -18
  23. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +58 -15
  24. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +23 -9
  25. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButtonSelectors.test.js +19 -17
  26. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +25 -37
  27. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +28 -8
  28. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +25 -8
  29. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +11 -9
  30. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +11 -9
  31. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +4 -4
  32. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +33 -12
  33. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +36 -8
  34. metadata +1 -42
  35. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/__tests__/__snapshots__/EmptyResults.test.js.snap +0 -18
  36. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/__snapshots__/EmptyState.test.js.snap +0 -25
  37. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/__tests__/__snapshots__/ErrorState.test.js.snap +0 -20
  38. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +0 -47
  39. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +0 -59
  40. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListActions.test.js +0 -34
  41. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListIntegration.test.js +0 -14
  42. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListSelectors.test.js +0 -25
  43. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +0 -49
  44. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +0 -86
  45. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +0 -75
  46. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +0 -46
  47. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/__snapshots__/FileDownload.test.js.snap +0 -26
  48. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterActions.test.js +0 -14
  49. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterReducer.test.js +0 -28
  50. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/InventoryFilterSelectors.test.js +0 -21
  51. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilter.test.js.snap +0 -21
  52. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterActions.test.js.snap +0 -17
  53. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterReducer.test.js.snap +0 -19
  54. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilterSelectors.test.js.snap +0 -9
  55. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -43
  56. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/__tests__/AdvancedSettingActions.test.js +0 -9
  57. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/__tests__/__snapshots__/AdvancedSettingActions.test.js.snap +0 -18
  58. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/InventorySettingsActions.test.js +0 -14
  59. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/__snapshots__/InventorySettingsActions.test.js.snap +0 -26
  60. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +0 -68
  61. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorActions.test.js +0 -9
  62. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorActions.test.js.snap +0 -11
  63. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorButton.test.js.snap +0 -59
  64. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/__snapshots__/SettingsWarning.test.js.snap +0 -32
  65. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButton.test.js.snap +0 -15
  66. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButtonSelectors.test.js.snap +0 -3
  67. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +0 -58
  68. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/__snapshots__/ScheduledRun.test.js.snap +0 -23
  69. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/__snapshots__/StatusChart.test.js.snap +0 -74
  70. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/__snapshots__/TabContainer.test.js.snap +0 -18
  71. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/__snapshots__/TabFooter.test.js.snap +0 -12
  72. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/__snapshots__/InventoryAutoUpload.test.js.snap +0 -96
  73. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryUpload.test.js +0 -10
  74. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +0 -5
  75. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryUpload.test.js.snap +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4301f698357e719f6ba5d08677bdeaf3ccefb995835dcf86f3e253cb0c01d599
4
- data.tar.gz: 77f3bc1ef3e85cc1d8988a53f2cd5fdcd65b99a9fb6d6da1cc5f366bbfebc6d3
3
+ metadata.gz: 2139dff816242e974e133f964b393409a65962f4dc5a5de0651b85d1d3c4bcb7
4
+ data.tar.gz: f362df63107b1a8834c6984654bedef584875e6d1d226dcab130c1d8fa11d181
5
5
  SHA512:
6
- metadata.gz: 000fad5efc5a8bd42e03045e33bc10795a5d658aa3ef1098e6f95fa2a0476630cc85fe6b7b40929e817a4ca0d166fd88c8073b4faa972b72a84d0a146eb7aa70
7
- data.tar.gz: 06fe93496e585831f593b8d31223577eb236adf7d1e4d90342cd203ddc9fbf9a80e9b58636b033fa04adefc806885feba8326a4e34dc38844bbaacd083df9c91
6
+ metadata.gz: 1a06fbaad9e79093b83f8ac8454ffcbf0ffa875f1978ef04697b8cfcf58c0a35812a7eee649f08b45df4c938a20e6784bffcba5bbc702c73a5bf224fe8958b47
7
+ data.tar.gz: d5c4b920bc78b449ddca4b080be077a24c3766950de46f30f9611745a85927e37e44c18d7aea6223b91e2faa4e3a6eb39fe9fab139207d9c73a1917c6910b0cd
@@ -10,7 +10,8 @@ module RhCloudHost
10
10
  )
11
11
 
12
12
  has_many :insights_hits, through: :insights, source: :hits
13
- scoped_search :relation => :insights, :on => :hits_count, :only_explicit => true, :rename => :insights_recommendations_count
13
+ scoped_search :on => :id, :rename => :insights_recommendations_count, :only_explicit => true,
14
+ :ext_method => :search_by_insights_recommendations_count, :complete_value => false
14
15
 
15
16
  has_one :insights_client_report_status_object, :class_name => '::InsightsClientReportStatus', :foreign_key => 'host_id'
16
17
  scoped_search :relation => :insights_client_report_status_object, :on => :status, :rename => :insights_client_report_status,
@@ -46,6 +47,40 @@ module RhCloudHost
46
47
  end
47
48
 
48
49
  module ClassMethods
50
+ def search_by_insights_recommendations_count(_key, operator, value)
51
+ if ForemanRhCloud.with_iop_smart_proxy?
52
+ raise ScopedSearch::QueryNotSupported.new(
53
+ _('Searching by recommendations count is not available in IoP mode.')
54
+ )
55
+ end
56
+
57
+ unless ScopedSearch::QueryBuilder::SQL_OPERATORS.value?(operator)
58
+ raise ScopedSearch::QueryNotSupported.new(
59
+ _('Unsupported operator for recommendations count search: %s') % operator
60
+ )
61
+ end
62
+
63
+ facet_table = InsightsFacet.table_name
64
+ hits_count_col = "COALESCE(#{facet_table}.hits_count, 0)"
65
+
66
+ if ['IN', 'NOT IN'].include?(operator)
67
+ values = value.is_a?(Array) ? value : value.to_s.split(',').map(&:strip)
68
+ placeholders = (['?'] * values.size).join(',')
69
+ condition = sanitize_sql_for_conditions(
70
+ ["#{hits_count_col} #{operator} (#{placeholders})", *values]
71
+ )
72
+ else
73
+ condition = sanitize_sql_for_conditions(
74
+ ["#{hits_count_col} #{operator} ?", value_to_sql(operator, value)]
75
+ )
76
+ end
77
+
78
+ {
79
+ joins: "LEFT JOIN #{facet_table} ON #{facet_table}.host_id = #{Host::Managed.table_name}.id",
80
+ conditions: condition,
81
+ }
82
+ end
83
+
49
84
  def search_by_insights_uuid(_key, operator, value)
50
85
  # Determine which facet table to search based on IoP mode
51
86
  facet_table = ForemanRhCloud.with_iop_smart_proxy? ? Katello::Host::SubscriptionFacet.table_name : InsightsFacet.table_name
@@ -1,11 +1,24 @@
1
1
  module ForemanRhCloud
2
2
  class URLRemediationsRetriever < RemediationsRetriever
3
- attr_reader :url, :payload, :headers
3
+ attr_reader :url, :headers
4
4
 
5
5
  def initialize(url:, organization_id:, payload: '', headers: {}, logger: Logger.new(IO::NULL))
6
6
  super(logger: logger)
7
7
 
8
- @url = url
8
+ parsed_url = URI.parse(url)
9
+ query_params = parsed_url.query ? CGI.parse(parsed_url.query) : {}
10
+ hosts_param = query_params.delete('hosts')
11
+
12
+ if hosts_param.present?
13
+ @host_uuids = hosts_param.flat_map { |v| v.split(',') }.map(&:strip).reject(&:blank?)
14
+ @host_uuids = nil if @host_uuids.empty?
15
+ parsed_url.query = query_params.any? ? URI.encode_www_form(query_params) : nil
16
+ @url = parsed_url.to_s
17
+ else
18
+ @host_uuids = nil
19
+ @url = url
20
+ end
21
+
9
22
  @payload = payload
10
23
  @headers = headers
11
24
  @organization_id = organization_id
@@ -14,7 +27,7 @@ module ForemanRhCloud
14
27
  private
15
28
 
16
29
  def query_playbook
17
- logger.debug("Querying playbook at: #{url} with payload: #{payload} and headers: #{headers}")
30
+ logger.debug("Querying playbook via #{method.to_s.upcase} at: #{url} with payload: #{payload} and headers: #{headers}")
18
31
 
19
32
  super
20
33
  end
@@ -28,11 +41,12 @@ module ForemanRhCloud
28
41
  end
29
42
 
30
43
  def payload
31
- @payload.present? ? @payload.to_json : @payload # don't run .to_json if @payload is ''
44
+ return @host_uuids.to_json if @host_uuids.present?
45
+ @payload.present? ? @payload.to_json : @payload
32
46
  end
33
47
 
34
48
  def method
35
- :get
49
+ @host_uuids.present? ? :post : :get
36
50
  end
37
51
 
38
52
  def organization
@@ -59,11 +59,13 @@ module ForemanInventoryUpload
59
59
  def try_execute
60
60
  if content_disconnected?
61
61
  logger.info("Upload canceled: connection to Insights is not enabled. Report location: #{filename}")
62
+ done!
62
63
  return
63
64
  end
64
65
 
65
66
  unless organization.owner_details&.dig('upstreamConsumer', 'idCert')
66
67
  logger.info("Skipping organization '#{organization}', no candlepin certificate defined.")
68
+ done!
67
69
  return
68
70
  end
69
71
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '13.2.10'.freeze
2
+ VERSION = '13.2.11'.freeze
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "13.2.10",
3
+ "version": "13.2.11",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -401,4 +401,33 @@ class UploadReportDirectJobTest < ActiveSupport::TestCase
401
401
  file.close
402
402
  end
403
403
  end
404
+
405
+ test 'mark as done when upload aborted due to missing certificate' do
406
+ # Remove certificate from organization
407
+ Organization.any_instance.stubs(:owner_details).returns({})
408
+
409
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
410
+ action.expects(:action_subject).with(@organization)
411
+ plan_action(action, nil, @organization.id)
412
+
413
+ # Execute the action
414
+ action.send(:try_execute)
415
+
416
+ # Verify it is marked as done
417
+ assert action.done?
418
+ end
419
+
420
+ test 'mark as done when upload aborted due to disconnected mode' do
421
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
422
+
423
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
424
+ action.expects(:action_subject).with(@organization)
425
+ plan_action(action, nil, @organization.id)
426
+
427
+ # Execute the action
428
+ action.send(:try_execute)
429
+
430
+ # Verify it is marked as done
431
+ assert action.done?
432
+ end
404
433
  end
@@ -6,6 +6,10 @@ class InsightsFacetTest < ActiveSupport::TestCase
6
6
  InsightsFacet.reset_counters(@host.insights.id, :hits_count)
7
7
  end
8
8
 
9
+ teardown do
10
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
11
+ end
12
+
9
13
  test 'host with hits can be deleted' do
10
14
  assert_equal 1, @host.insights.hits.count
11
15
 
@@ -16,6 +20,7 @@ class InsightsFacetTest < ActiveSupport::TestCase
16
20
  end
17
21
 
18
22
  test 'search host by recommendations_count' do
23
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
19
24
  FactoryBot.create(:host) # create another host with no recommendations
20
25
 
21
26
  assert_equal 1, Host.search_for('insights_recommendations_count = 1').count
@@ -343,6 +343,74 @@ class RhCloudHostTest < ActiveSupport::TestCase
343
343
  end
344
344
  end
345
345
 
346
+ context 'scoped search on insights_recommendations_count' do
347
+ setup do
348
+ @org = FactoryBot.create(:organization)
349
+ end
350
+
351
+ teardown do
352
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
353
+ end
354
+
355
+ test 'searches insights_facets.hits_count in non-IoP mode' do
356
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
357
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
358
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
359
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
360
+
361
+ results = Host::Managed.search_for('insights_recommendations_count = 1')
362
+
363
+ assert_includes results, host_with_hits
364
+ assert_not_includes results, host_without_hits
365
+ end
366
+
367
+ test 'supports greater-than operator in non-IoP mode' do
368
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
369
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
370
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
371
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
372
+
373
+ results = Host::Managed.search_for('insights_recommendations_count > 0')
374
+
375
+ assert_includes results, host_with_hits
376
+ assert_not_includes results, host_without_hits
377
+ end
378
+
379
+ test 'raises QueryNotSupported in IoP mode' do
380
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
381
+
382
+ error = assert_raises(ScopedSearch::QueryNotSupported) do
383
+ Host::Managed.search_for('insights_recommendations_count = 1')
384
+ end
385
+
386
+ assert_match(/not available in IoP mode/, error.message)
387
+ end
388
+
389
+ test 'supports IN operator in non-IoP mode' do
390
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
391
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
392
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
393
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
394
+
395
+ results = Host::Managed.search_for('insights_recommendations_count ^ (1,2,3)')
396
+
397
+ assert_includes results, host_with_hits
398
+ assert_not_includes results, host_without_hits
399
+ end
400
+
401
+ test 'handles hosts without insights facets in non-IoP mode' do
402
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
403
+ host_without_facet = FactoryBot.create(:host, :managed, organization: @org)
404
+ host_with_facet = FactoryBot.create(:host, :with_insights_hits, organization: @org)
405
+ InsightsFacet.reset_counters(host_with_facet.insights.id, :hits_count)
406
+
407
+ results = Host::Managed.search_for('insights_recommendations_count = 1')
408
+
409
+ assert_includes results, host_with_facet
410
+ assert_not_includes results, host_without_facet
411
+ end
412
+ end
413
+
346
414
  test 'scoped search for user_omitted inventory status works' do
347
415
  host1 = FactoryBot.create(:host, :managed)
348
416
  host2 = FactoryBot.create(:host, :managed)
@@ -1,8 +1,8 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  class URLRemediationsRetrieverTest < ActiveSupport::TestCase
4
- test 'Calls the given url' do
5
- retreiver = ForemanRhCloud::URLRemediationsRetriever.new(
4
+ test 'Calls the given url with GET when no hosts in URL' do
5
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
6
6
  organization_id: FactoryBot.create(:organization).id,
7
7
  url: 'http://test.example.com',
8
8
  payload: 'TEST_PAYLOAD',
@@ -11,19 +11,96 @@ class URLRemediationsRetrieverTest < ActiveSupport::TestCase
11
11
  }
12
12
  )
13
13
 
14
- retreiver.stubs(:cert_auth_available?).returns(true)
14
+ retriever.stubs(:cert_auth_available?).returns(true)
15
15
 
16
16
  response = mock('response')
17
17
  response.stubs(:body).returns('TEST_RESPONSE')
18
- retreiver.expects(:execute_cloud_request).with do |params|
18
+ retriever.expects(:execute_cloud_request).with do |params|
19
19
  params[:method] == :get &&
20
20
  params[:url] == 'http://test.example.com' &&
21
21
  params[:headers][:custom1] == 'TEST_HEADER' &&
22
22
  params[:payload] == "\"TEST_PAYLOAD\""
23
23
  end.returns(response)
24
24
 
25
- actual = retreiver.create_playbook
25
+ actual = retriever.create_playbook
26
26
 
27
27
  assert_equal 'TEST_RESPONSE', actual
28
28
  end
29
+
30
+ test 'Uses POST with hosts in body when URL contains hosts query param' do
31
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
32
+ organization_id: FactoryBot.create(:organization).id,
33
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts=uuid-1,uuid-2,uuid-3'
34
+ )
35
+
36
+ retriever.stubs(:cert_auth_available?).returns(true)
37
+
38
+ response = mock('response')
39
+ response.stubs(:body).returns('TEST_PLAYBOOK')
40
+ retriever.expects(:execute_cloud_request).with do |params|
41
+ params[:method] == :post &&
42
+ params[:url] == 'http://test.example.com/api/remediations/1234/playbook' &&
43
+ JSON.parse(params[:payload]) == ['uuid-1', 'uuid-2', 'uuid-3']
44
+ end.returns(response)
45
+
46
+ actual = retriever.create_playbook
47
+
48
+ assert_equal 'TEST_PLAYBOOK', actual
49
+ end
50
+
51
+ test 'Preserves other query params when extracting hosts' do
52
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
53
+ organization_id: FactoryBot.create(:organization).id,
54
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts=uuid-1,uuid-2&localhost=false'
55
+ )
56
+
57
+ retriever.stubs(:cert_auth_available?).returns(true)
58
+
59
+ response = mock('response')
60
+ response.stubs(:body).returns('TEST_PLAYBOOK')
61
+ retriever.expects(:execute_cloud_request).with do |params|
62
+ params[:method] == :post &&
63
+ params[:url].include?('localhost=false') &&
64
+ !params[:url].include?('hosts=') &&
65
+ JSON.parse(params[:payload]) == ['uuid-1', 'uuid-2']
66
+ end.returns(response)
67
+
68
+ retriever.create_playbook
69
+ end
70
+
71
+ test 'Merges multiple hosts query params into a single array' do
72
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
73
+ organization_id: FactoryBot.create(:organization).id,
74
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts=uuid-1&hosts=uuid-2'
75
+ )
76
+
77
+ retriever.stubs(:cert_auth_available?).returns(true)
78
+
79
+ response = mock('response')
80
+ response.stubs(:body).returns('TEST_PLAYBOOK')
81
+ retriever.expects(:execute_cloud_request).with do |params|
82
+ params[:method] == :post &&
83
+ params[:url] == 'http://test.example.com/api/remediations/1234/playbook' &&
84
+ JSON.parse(params[:payload]) == ['uuid-1', 'uuid-2']
85
+ end.returns(response)
86
+
87
+ retriever.create_playbook
88
+ end
89
+
90
+ test 'Falls back to GET when hosts query param is empty' do
91
+ retriever = ForemanRhCloud::URLRemediationsRetriever.new(
92
+ organization_id: FactoryBot.create(:organization).id,
93
+ url: 'http://test.example.com/api/remediations/1234/playbook?hosts='
94
+ )
95
+
96
+ retriever.stubs(:cert_auth_available?).returns(true)
97
+
98
+ response = mock('response')
99
+ response.stubs(:body).returns('TEST_PLAYBOOK')
100
+ retriever.expects(:execute_cloud_request).with do |params|
101
+ params[:method] == :get
102
+ end.returns(response)
103
+
104
+ retriever.create_playbook
105
+ end
29
106
  end
@@ -1,13 +1,14 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import EmptyResults from '../EmptyResults';
4
4
 
5
- const fixtures = {
6
- 'render without Props': {},
7
- /** fixtures, props for the component */
8
- };
9
-
10
5
  describe('EmptyResults', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(EmptyResults, fixtures));
6
+ it('renders the empty results message', () => {
7
+ render(<EmptyResults />);
8
+ expect(
9
+ screen.getByText(
10
+ "Oops! Couldn't find organization that matches your query"
11
+ )
12
+ ).toBeTruthy();
13
+ });
13
14
  });
@@ -1,13 +1,17 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import EmptyState from '../EmptyState';
4
4
 
5
- const fixtures = {
6
- 'render without Props': {},
7
- /** fixtures, props for the component */
8
- };
9
-
10
5
  describe('EmptyState', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(EmptyState, fixtures));
6
+ it('renders fetching data message', () => {
7
+ render(<EmptyState />);
8
+ expect(
9
+ screen.getByText('Fetching data about your accounts')
10
+ ).toBeTruthy();
11
+ });
12
+
13
+ it('renders loading indicator', () => {
14
+ render(<EmptyState />);
15
+ expect(screen.getByText('Loading...')).toBeTruthy();
16
+ });
13
17
  });
@@ -1,13 +1,24 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import ErrorState from '../ErrorState';
4
4
 
5
- const fixtures = {
6
- 'render without Props': {},
7
- /** fixtures, props for the component */
8
- };
9
-
10
5
  describe('ErrorState', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(ErrorState, fixtures));
6
+ it('renders the error message heading', () => {
7
+ render(<ErrorState error="Something went wrong" />);
8
+ expect(
9
+ screen.getByText(
10
+ 'Encountered an error while trying to access the server:'
11
+ )
12
+ ).toBeTruthy();
13
+ });
14
+
15
+ it('renders the error description', () => {
16
+ render(<ErrorState error="Connection refused" />);
17
+ expect(screen.getByText('Connection refused')).toBeTruthy();
18
+ });
19
+
20
+ it('renders with empty error by default', () => {
21
+ const { container } = render(<ErrorState />);
22
+ expect(container.querySelector('.error_description')).toBeTruthy();
23
+ });
13
24
  });
@@ -1,13 +1,36 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Accordion } from '@patternfly/react-core';
3
4
  import ListItem from '../ListItem';
4
- import { props } from '../ListItem.fixtures';
5
5
 
6
- const fixtures = {
7
- 'render with Props': props,
8
- };
6
+ jest.mock('../../../../Dashboard', () => () => null);
7
+
8
+ const renderListItem = props =>
9
+ render(
10
+ <Accordion>
11
+ <ListItem {...props} />
12
+ </Accordion>
13
+ );
9
14
 
10
15
  describe('ListItem', () => {
11
- describe('rendering', () =>
12
- testComponentSnapshotsWithFixtures(ListItem, fixtures));
16
+ const defaultProps = {
17
+ label: 'test-org',
18
+ account: {
19
+ generated_status: 'unknown',
20
+ uploaded_status: 'unknown',
21
+ id: 1,
22
+ },
23
+ };
24
+
25
+ it('renders the account label', () => {
26
+ renderListItem(defaultProps);
27
+ expect(screen.getByText('test-org')).toBeTruthy();
28
+ });
29
+
30
+ it('renders Generated and Uploaded status labels', () => {
31
+ renderListItem(defaultProps);
32
+ expect(screen.getByText('Generated')).toBeTruthy();
33
+ expect(screen.getByText('Uploaded')).toBeTruthy();
34
+ });
35
+
13
36
  });
@@ -1,14 +1,30 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import ListItemStatus from '../ListItemStatus';
4
- import { props } from '../ListItemStatus.fixtures';
5
-
6
- const fixtures = {
7
- 'render without Props': {},
8
- 'render with Props': props,
9
- };
10
4
 
11
5
  describe('ListItemStatus', () => {
12
- describe('rendering', () =>
13
- testComponentSnapshotsWithFixtures(ListItemStatus, fixtures));
6
+ it('renders Generated and Uploaded labels', () => {
7
+ render(<ListItemStatus />);
8
+ expect(screen.getByText('Generated')).toBeTruthy();
9
+ expect(screen.getByText('Uploaded')).toBeTruthy();
10
+ });
11
+
12
+ it('shows dash placeholders for unknown status', () => {
13
+ render(
14
+ <ListItemStatus
15
+ account={{ generated_status: 'unknown', uploaded_status: 'unknown' }}
16
+ />
17
+ );
18
+ expect(screen.getAllByText('--')).toHaveLength(2);
19
+ });
20
+
21
+ it('renders with provided account statuses', () => {
22
+ render(
23
+ <ListItemStatus
24
+ account={{ generated_status: 'success', uploaded_status: 'running' }}
25
+ />
26
+ );
27
+ expect(screen.getByText('Generated')).toBeTruthy();
28
+ expect(screen.getByText('Uploaded')).toBeTruthy();
29
+ });
14
30
  });
@@ -1,14 +1,38 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
-
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import AccountList from '../AccountList';
4
- import { props } from '../AccountList.fixtures';
4
+ import { accounts } from '../AccountList.fixtures';
5
5
 
6
- const fixtures = {
7
- 'render with props': props,
8
- 'show empty results': { ...props, filterTerm: 'not_matching_term' },
9
- };
6
+ jest.mock('../Components/ListItem', () => ({ label }) => (
7
+ <div>{label}</div>
8
+ ));
10
9
 
11
10
  describe('AccountList', () => {
12
- describe('rendering', () =>
13
- testComponentSnapshotsWithFixtures(AccountList, fixtures));
11
+ it('renders account labels', () => {
12
+ render(<AccountList accounts={accounts} />);
13
+ Object.keys(accounts).forEach(label => {
14
+ expect(screen.getByText(label)).toBeTruthy();
15
+ });
16
+ });
17
+
18
+ it('shows empty results when filterTerm matches nothing', () => {
19
+ render(<AccountList accounts={accounts} filterTerm="not_matching_term" />);
20
+ expect(
21
+ screen.getByText(
22
+ "Oops! Couldn't find organization that matches your query"
23
+ )
24
+ ).toBeTruthy();
25
+ });
26
+
27
+ it('shows empty state when accounts are empty', () => {
28
+ render(<AccountList accounts={{}} />);
29
+ expect(
30
+ screen.getByText('Fetching data about your accounts')
31
+ ).toBeTruthy();
32
+ });
33
+
34
+ it('shows error state when error is present', () => {
35
+ render(<AccountList accounts={{}} error="Server error" />);
36
+ expect(screen.getByText('Server error')).toBeTruthy();
37
+ });
14
38
  });