foreman_rh_cloud 13.0.8 → 13.0.10

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/inventory_upload/report_actions.rb +8 -1
  3. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +82 -7
  4. data/app/controllers/foreman_inventory_upload/api/tasks_controller.rb +110 -0
  5. data/app/controllers/foreman_inventory_upload/reports_controller.rb +41 -17
  6. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -9
  7. data/config/routes.rb +4 -2
  8. data/db/migrate/20251209163012_drop_task_output_tables.foreman_rh_cloud.rb +24 -0
  9. data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +1 -1
  10. data/lib/foreman_inventory_upload/async/host_inventory_report_job.rb +39 -0
  11. data/lib/foreman_inventory_upload/async/queue_for_upload_job.rb +1 -23
  12. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +171 -0
  13. data/lib/foreman_inventory_upload.rb +0 -4
  14. data/lib/foreman_rh_cloud/plugin.rb +1 -0
  15. data/lib/foreman_rh_cloud/version.rb +1 -1
  16. data/lib/inventory_sync/async/inventory_hosts_sync.rb +0 -2
  17. data/lib/tasks/rh_cloud_inventory.rake +4 -2
  18. data/package.json +1 -1
  19. data/test/controllers/accounts_controller_test.rb +10 -11
  20. data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +1 -2
  21. data/test/jobs/host_inventory_report_job_test.rb +161 -97
  22. data/test/jobs/queue_for_upload_job_test.rb +1 -12
  23. data/test/jobs/single_host_report_job_test.rb +36 -54
  24. data/test/jobs/upload_report_direct_job_test.rb +399 -0
  25. data/test/unit/rh_cloud_permissions_test.rb +2 -0
  26. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +6 -6
  27. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +49 -34
  28. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListActions.js +2 -2
  29. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +4 -5
  30. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +15 -7
  31. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +11 -11
  32. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.fixtures.js +2 -2
  33. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.js +10 -14
  34. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatusHelper.js +9 -4
  35. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +4 -4
  36. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +15 -9
  37. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +7 -7
  38. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +6 -6
  39. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +12 -12
  40. data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +37 -130
  41. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/Dashboard.test.js +60 -17
  42. data/webpack/ForemanInventoryUpload/Components/Dashboard/index.js +1 -34
  43. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -1
  44. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +1 -26
  45. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +24 -17
  46. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +178 -8
  47. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +2 -2
  48. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +3 -1
  49. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +69 -51
  50. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +22 -9
  51. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +67 -4
  52. data/webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js +140 -0
  53. data/webpack/ForemanInventoryUpload/Components/TaskHistory/index.js +1 -0
  54. data/webpack/ForemanInventoryUpload/Components/TaskHistory/taskHistory.scss +40 -0
  55. data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +340 -0
  56. data/webpack/ForemanInventoryUpload/Components/TaskProgress/index.js +1 -0
  57. data/webpack/ForemanInventoryUpload/Components/TaskProgress/taskProgress.scss +8 -0
  58. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +2 -2
  59. data/webpack/ForemanInventoryUpload/ForemanInventoryUploadReducers.js +0 -2
  60. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +1 -1
  61. data/webpack/ForemanRhCloudPages.js +0 -1
  62. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +11 -19
  63. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +1 -2
  64. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -4
  65. data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +77 -22
  66. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +14 -0
  67. data/webpack/__tests__/ForemanRhCloudHelpers.test.js +5 -1
  68. metadata +13 -68
  69. data/app/models/task_output_line.rb +0 -2
  70. data/app/models/task_output_status.rb +0 -2
  71. data/lib/foreman_inventory_upload/async/generate_report_job.rb +0 -61
  72. data/lib/foreman_inventory_upload/async/progress_output.rb +0 -38
  73. data/lib/foreman_inventory_upload/async/shell_process.rb +0 -77
  74. data/lib/foreman_inventory_upload/async/upload_report_job.rb +0 -97
  75. data/lib/foreman_inventory_upload/scripts/uploader.sh.erb +0 -55
  76. data/test/controllers/reports_controller_test.rb +0 -21
  77. data/test/controllers/uploads_controller_test.rb +0 -21
  78. data/test/jobs/generate_report_job_test.rb +0 -146
  79. data/test/jobs/upload_report_job_test.rb +0 -38
  80. data/test/unit/shell_process_job_test.rb +0 -29
  81. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardActions.js +0 -88
  82. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardConstants.js +0 -9
  83. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardReducer.js +0 -68
  84. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardSelectors.js +0 -17
  85. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardActions.test.js +0 -51
  86. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardIntegration.test.js +0 -17
  87. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardReducer.test.js +0 -64
  88. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardSelectors.test.js +0 -46
  89. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/Dashboard.test.js.snap +0 -36
  90. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardActions.test.js.snap +0 -76
  91. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardReducer.test.js.snap +0 -44
  92. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardSelectors.test.js.snap +0 -42
  93. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.fixtures.js +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.js +0 -55
  95. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModalHelper.js +0 -0
  96. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/FullScreenModal.test.js +0 -13
  97. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/__snapshots__/FullScreenModal.test.js.snap +0 -65
  98. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/fullScreenModal.scss +0 -20
  99. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/index.js +0 -1
  100. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap +0 -36
  101. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.fixtures.js +0 -18
  102. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.js +0 -65
  103. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerateHelper.js +0 -0
  104. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/ReportGenerate.test.js +0 -14
  105. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/__snapshots__/ReportGenerate.test.js.snap +0 -47
  106. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/index.js +0 -1
  107. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/reportGenerate.scss +0 -0
  108. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.fixtures.js +0 -18
  109. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.js +0 -46
  110. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUploadHelper.js +0 -0
  111. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/ReportUpload.test.js +0 -14
  112. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/__snapshots__/ReportUpload.test.js.snap +0 -47
  113. data/webpack/ForemanInventoryUpload/Components/ReportUpload/index.js +0 -1
  114. data/webpack/ForemanInventoryUpload/Components/ReportUpload/reportUpload.scss +0 -0
  115. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.fixtures.js +0 -0
  116. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.js +0 -31
  117. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBodyHelper.js +0 -0
  118. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/TabBody.test.js +0 -13
  119. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/__snapshots__/TabBody.test.js.snap +0 -19
  120. data/webpack/ForemanInventoryUpload/Components/TabBody/index.js +0 -1
  121. data/webpack/ForemanInventoryUpload/Components/TabBody/tabBody.scss +0 -5
  122. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.fixtures.js +0 -10
  123. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.js +0 -110
  124. data/webpack/ForemanInventoryUpload/Components/Terminal/TerminalHelper.js +0 -6
  125. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/Terminal.test.js +0 -34
  126. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/__snapshots__/Terminal.test.js.snap +0 -98
  127. data/webpack/ForemanInventoryUpload/Components/Terminal/index.js +0 -1
  128. data/webpack/ForemanInventoryUpload/Components/Terminal/terminal.scss +0 -32
  129. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +0 -112
  130. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +0 -3
@@ -0,0 +1,399 @@
1
+ require 'test_plugin_helper'
2
+ require 'foreman_tasks/test_helpers'
3
+
4
+ class UploadReportDirectJobTest < ActiveSupport::TestCase
5
+ include Dynflow::Testing::Factories
6
+ include Dynflow::Testing::Assertions
7
+
8
+ setup do
9
+ @organization = FactoryBot.create(:organization)
10
+ @uploads_folder = ForemanInventoryUpload.uploads_folder
11
+ @filename = File.join(@uploads_folder, 'test_report.tar.xz')
12
+
13
+ # Stub settings
14
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(true)
15
+ Setting.stubs(:[]).with(:ssl_certificate).returns('/fake/cert.pem')
16
+ Setting.stubs(:[]).with(:ssl_priv_key).returns('/fake/key.pem')
17
+
18
+ # Stub ForemanRhCloud methods
19
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
20
+
21
+ # Stub organization owner details with certificate
22
+ @cert_data = {
23
+ 'upstreamConsumer' => {
24
+ 'idCert' => {
25
+ 'cert' => 'FAKE CERTIFICATE',
26
+ 'key' => 'FAKE KEY',
27
+ },
28
+ },
29
+ }
30
+ Organization.any_instance.stubs(:owner_details).returns(@cert_data)
31
+ end
32
+
33
+ test 'plan sets input correctly' do
34
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
35
+ action.expects(:action_subject).with(@organization)
36
+ plan_action(action, @filename, @organization.id)
37
+
38
+ assert_equal @filename, action.input[:filename]
39
+ assert_equal @organization.id, action.input[:organization_id]
40
+ end
41
+
42
+ test 'output_label generates correct label' do
43
+ label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(@organization.id)
44
+ assert_equal "upload_for_#{@organization.id}", label
45
+ end
46
+
47
+ test 'uses manifest certificate in regular mode' do
48
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
49
+
50
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
51
+ action.expects(:action_subject).with(@organization)
52
+ plan_action(action, @filename, @organization.id)
53
+
54
+ cert = action.send(:certificate)
55
+ assert_equal 'FAKE CERTIFICATE', cert[:cert]
56
+ assert_equal 'FAKE KEY', cert[:key]
57
+ end
58
+
59
+ test 'manifest_certificate extracts from organization owner_details' do
60
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
61
+ action.expects(:action_subject).with(@organization)
62
+ plan_action(action, @filename, @organization.id)
63
+
64
+ cert = action.send(:manifest_certificate)
65
+ assert_equal 'FAKE CERTIFICATE', cert[:cert]
66
+ assert_equal 'FAKE KEY', cert[:key]
67
+ end
68
+
69
+ test 'uses foreman certificate in IoP mode' do
70
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
71
+ File.stubs(:readable?).with('/fake/cert.pem').returns(true)
72
+ File.stubs(:readable?).with('/fake/key.pem').returns(true)
73
+ File.stubs(:read).with('/fake/cert.pem').returns('FOREMAN CERTIFICATE')
74
+ File.stubs(:read).with('/fake/key.pem').returns('FOREMAN KEY')
75
+
76
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
77
+ action.expects(:action_subject).with(@organization)
78
+ plan_action(action, @filename, @organization.id)
79
+
80
+ cert = action.send(:foreman_certificate)
81
+ assert_equal 'FOREMAN CERTIFICATE', cert[:cert]
82
+ assert_equal 'FOREMAN KEY', cert[:key]
83
+ end
84
+
85
+ test 'certificate method returns manifest cert in regular mode' do
86
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
87
+
88
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
89
+ action.expects(:action_subject).with(@organization)
90
+ plan_action(action, @filename, @organization.id)
91
+
92
+ cert = action.send(:certificate)
93
+ assert_equal 'FAKE CERTIFICATE', cert[:cert]
94
+ assert_equal 'FAKE KEY', cert[:key]
95
+ end
96
+
97
+ test 'certificate method returns foreman cert in IoP mode' do
98
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
99
+ Setting.stubs(:[]).with(:ssl_certificate).returns('/fake/cert.pem')
100
+ Setting.stubs(:[]).with(:ssl_priv_key).returns('/fake/key.pem')
101
+ File.stubs(:readable?).with('/fake/cert.pem').returns(true)
102
+ File.stubs(:readable?).with('/fake/key.pem').returns(true)
103
+ File.stubs(:read).with('/fake/cert.pem').returns('FOREMAN CERTIFICATE')
104
+ File.stubs(:read).with('/fake/key.pem').returns('FOREMAN KEY')
105
+
106
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
107
+ action.expects(:action_subject).with(@organization)
108
+ plan_action(action, @filename, @organization.id)
109
+
110
+ cert = action.send(:certificate)
111
+ assert_equal 'FOREMAN CERTIFICATE', cert[:cert]
112
+ assert_equal 'FOREMAN KEY', cert[:key]
113
+ end
114
+
115
+ test 'foreman_certificate raises error when certificate file is missing' do
116
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
117
+ Setting.stubs(:[]).with(:ssl_certificate).returns('/nonexistent/cert.pem')
118
+ Setting.stubs(:[]).with(:ssl_priv_key).returns('/fake/key.pem')
119
+ File.stubs(:readable?).with('/nonexistent/cert.pem').returns(false)
120
+
121
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
122
+ action.expects(:action_subject).with(@organization)
123
+ plan_action(action, @filename, @organization.id)
124
+
125
+ error = assert_raises(RuntimeError) do
126
+ action.send(:foreman_certificate)
127
+ end
128
+ assert_match(/SSL certificate file not found or not readable/, error.message)
129
+ end
130
+
131
+ test 'foreman_certificate raises error when private key file is missing' do
132
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
133
+ Setting.stubs(:[]).with(:ssl_certificate).returns('/fake/cert.pem')
134
+ Setting.stubs(:[]).with(:ssl_priv_key).returns('/nonexistent/key.pem')
135
+ File.stubs(:readable?).with('/fake/cert.pem').returns(true)
136
+ File.stubs(:readable?).with('/nonexistent/key.pem').returns(false)
137
+
138
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
139
+ action.expects(:action_subject).with(@organization)
140
+ plan_action(action, @filename, @organization.id)
141
+
142
+ error = assert_raises(RuntimeError) do
143
+ action.send(:foreman_certificate)
144
+ end
145
+ assert_match(/SSL private key file not found or not readable/, error.message)
146
+ end
147
+
148
+ test 'filename returns input filename' do
149
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
150
+ action.expects(:action_subject).with(@organization)
151
+ plan_action(action, @filename, @organization.id)
152
+
153
+ assert_equal @filename, action.send(:filename)
154
+ end
155
+
156
+ test 'organization returns Organization from input organization_id' do
157
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
158
+ action.expects(:action_subject).with(@organization)
159
+ plan_action(action, @filename, @organization.id)
160
+
161
+ org = action.send(:organization)
162
+ assert_equal @organization.id, org.id
163
+ end
164
+
165
+ test 'content_disconnected? returns true when subscription_connection_enabled is false' do
166
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
167
+
168
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
169
+ action.expects(:action_subject).with(@organization)
170
+ plan_action(action, @filename, @organization.id)
171
+
172
+ assert action.send(:content_disconnected?)
173
+ end
174
+
175
+ test 'content_disconnected? returns false when subscription_connection_enabled is true' do
176
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(true)
177
+
178
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
179
+ action.expects(:action_subject).with(@organization)
180
+ plan_action(action, @filename, @organization.id)
181
+
182
+ refute action.send(:content_disconnected?)
183
+ end
184
+
185
+ test 'content_disconnected? returns false when IoP mode is enabled even if subscription_connection_enabled is false' do
186
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
187
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
188
+
189
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
190
+ action.expects(:action_subject).with(@organization)
191
+ plan_action(action, @filename, @organization.id)
192
+
193
+ refute action.send(:content_disconnected?)
194
+ end
195
+
196
+ test 'rescue_strategy_for_self returns Fail strategy' do
197
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
198
+ action.expects(:action_subject).with(@organization)
199
+ plan_action(action, @filename, @organization.id)
200
+
201
+ assert_equal Dynflow::Action::Rescue::Fail, action.send(:rescue_strategy_for_self)
202
+ end
203
+
204
+ test 'handles RestClient server error gracefully' do
205
+ # Create mock response for RestClient exception
206
+ response = mock('response')
207
+ response.stubs(:code).returns(500)
208
+ response.stubs(:body).returns('Server error')
209
+
210
+ # Stub upload_file to raise server error
211
+ ForemanInventoryUpload::Async::UploadReportDirectJob.any_instance.stubs(:upload_file)
212
+ .raises(RestClient::InternalServerError.new(response))
213
+
214
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
215
+ action.expects(:action_subject).with(@organization)
216
+ plan_action(action, @filename, @organization.id)
217
+
218
+ # Should raise the error (handled by Dynflow retry mechanism)
219
+ assert_raises(RestClient::InternalServerError) do
220
+ action.send(:try_execute)
221
+ end
222
+ end
223
+
224
+ test 'handles RestClient timeout gracefully' do
225
+ # Stub upload_file to raise timeout (Timeout exception doesn't need response object)
226
+ ForemanInventoryUpload::Async::UploadReportDirectJob.any_instance.stubs(:upload_file)
227
+ .raises(RestClient::Exceptions::Timeout.new)
228
+
229
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
230
+ action.expects(:action_subject).with(@organization)
231
+ plan_action(action, @filename, @organization.id)
232
+
233
+ # Should raise the error (handled by Dynflow retry mechanism via ExponentialBackoff)
234
+ assert_raises(RestClient::Exceptions::Timeout) do
235
+ action.send(:try_execute)
236
+ end
237
+ end
238
+
239
+ test 'uses proxy configuration from ForemanRhCloud' do
240
+ proxy_url = 'http://proxy.example.com:8080'
241
+ ForemanRhCloud.stubs(:transformed_http_proxy_string).returns(proxy_url)
242
+
243
+ # Create test file
244
+ FileUtils.mkdir_p(File.dirname(@filename))
245
+ FileUtils.touch(@filename)
246
+
247
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
248
+ action.expects(:action_subject).with(@organization)
249
+ plan_action(action, @filename, @organization.id)
250
+
251
+ # Mock response
252
+ response = mock('response')
253
+ response.stubs(:code).returns(200)
254
+
255
+ # Verify execute_cloud_request is called (which handles proxy)
256
+ # We can't test the actual proxy parameters because CloudRequest concern
257
+ # merges them internally, but we can verify the method is called
258
+ action.expects(:execute_cloud_request).returns(response)
259
+
260
+ # Stub upload_file to call execute_cloud_request (avoiding SSL cert creation issues)
261
+ action.stubs(:upload_file).returns(nil)
262
+
263
+ # Manually call upload_file expectations in the test
264
+ # This simulates what would happen during actual execution
265
+ action.send(:execute_cloud_request,
266
+ method: :post,
267
+ url: ForemanInventoryUpload.upload_url,
268
+ payload: { multipart: true },
269
+ headers: { 'X-Org-Id' => @organization.label })
270
+ end
271
+
272
+ test 'file cleanup when upload aborted due to missing certificate' do
273
+ # Remove certificate from organization
274
+ Organization.any_instance.stubs(:owner_details).returns({})
275
+
276
+ # Create a real test file to verify it's not moved
277
+ FileUtils.mkdir_p(@uploads_folder)
278
+ test_file = File.join(@uploads_folder, 'test_file_for_cleanup.tar.xz')
279
+ FileUtils.mkdir_p(@uploads_folder)
280
+ FileUtils.touch(test_file)
281
+
282
+ begin
283
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
284
+ action.expects(:action_subject).with(@organization)
285
+ plan_action(action, test_file, @organization.id)
286
+
287
+ # Execute the action
288
+ action.send(:try_execute)
289
+
290
+ # Verify file still exists (not moved or deleted)
291
+ assert File.exist?(test_file), "File should remain when upload is aborted"
292
+ ensure
293
+ FileUtils.rm_f(test_file) if File.exist?(test_file)
294
+ end
295
+ end
296
+
297
+ test 'file cleanup when upload aborted due to disconnected mode' do
298
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
299
+
300
+ # Create a real test file
301
+ FileUtils.mkdir_p(@uploads_folder)
302
+ test_file = File.join(@uploads_folder, 'test_file_disconnected.tar.xz')
303
+ FileUtils.mkdir_p(@uploads_folder)
304
+ FileUtils.touch(test_file)
305
+
306
+ begin
307
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
308
+ action.expects(:action_subject).with(@organization)
309
+ plan_action(action, test_file, @organization.id)
310
+
311
+ # Execute the action
312
+ action.send(:try_execute)
313
+
314
+ # Verify file still exists
315
+ assert File.exist?(test_file), "File should remain when connection is disabled"
316
+ ensure
317
+ FileUtils.rm_f(test_file) if File.exist?(test_file)
318
+ end
319
+ end
320
+
321
+ test 'overwrites existing done file and logs warning' do
322
+ # Prepare new report file in uploads folder
323
+ FileUtils.mkdir_p(@uploads_folder)
324
+ source_file = File.join(@uploads_folder, 'overwrite_done_file.tar.xz')
325
+ new_report_content = 'new report content'
326
+ File.write(source_file, new_report_content)
327
+
328
+ # Pre-create done file with old content
329
+ done_file_path = ForemanInventoryUpload.done_file_path(File.basename(source_file))
330
+ FileUtils.mkdir_p(File.dirname(done_file_path))
331
+ old_report_content = 'old report content'
332
+ File.write(done_file_path, old_report_content)
333
+
334
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
335
+ action.expects(:action_subject).with(@organization)
336
+
337
+ # Stub upload_file to avoid actual HTTP upload
338
+ action.stubs(:upload_file).returns(nil)
339
+
340
+ # Expect a warning when overwriting the existing done file
341
+ action.expects(:logger).at_least_once.returns(mock_logger = mock('logger'))
342
+ mock_logger.expects(:warn).with(regexp_matches(/already exists.*overwriting/i))
343
+ mock_logger.stubs(:debug)
344
+
345
+ plan_action(action, source_file, @organization.id)
346
+
347
+ # Execute the action (which will call move_to_done_folder)
348
+ action.send(:try_execute)
349
+
350
+ # Done file should now contain the new report content
351
+ assert_equal new_report_content, File.read(done_file_path)
352
+ ensure
353
+ FileUtils.rm_f(source_file) if defined?(source_file) && source_file
354
+ FileUtils.rm_f(done_file_path) if defined?(done_file_path) && done_file_path
355
+ end
356
+
357
+ test 'FileUpload wrapper delegates to file object' do
358
+ # Create test file
359
+ FileUtils.mkdir_p(File.dirname(@filename))
360
+ FileUtils.touch(@filename)
361
+
362
+ file = File.open(@filename, 'rb')
363
+ begin
364
+ wrapped = ForemanInventoryUpload::Async::UploadReportDirectJob::FileUpload.new(
365
+ file,
366
+ content_type: 'application/test'
367
+ )
368
+
369
+ assert_equal 'application/test', wrapped.content_type
370
+ assert_equal file.path, wrapped.path
371
+ assert_respond_to wrapped, :read
372
+ assert_respond_to wrapped, :close
373
+ ensure
374
+ file.close
375
+ end
376
+ end
377
+
378
+ test 'FileUpload wrapper provides content_type for RestClient' do
379
+ # Create test file
380
+ FileUtils.mkdir_p(File.dirname(@filename))
381
+ FileUtils.touch(@filename)
382
+
383
+ file = File.open(@filename, 'rb')
384
+ begin
385
+ wrapped = ForemanInventoryUpload::Async::UploadReportDirectJob::FileUpload.new(
386
+ file,
387
+ content_type: 'application/vnd.redhat.qpc.tar+tgz'
388
+ )
389
+
390
+ # RestClient checks for these methods
391
+ assert_respond_to wrapped, :read
392
+ assert_respond_to wrapped, :path
393
+ assert_respond_to wrapped, :content_type
394
+ assert_equal 'application/vnd.redhat.qpc.tar+tgz', wrapped.content_type
395
+ ensure
396
+ file.close
397
+ end
398
+ end
399
+ end
@@ -9,6 +9,8 @@ class RhCloudPermissionsTest < ActiveSupport::TestCase
9
9
  [
10
10
  'insights_cloud/api/machine_telemetries/forward_request',
11
11
  'insights_cloud/api/machine_telemetries/branch_info',
12
+ 'api/v2/rh_cloud/foreman_inventory_upload/api/tasks/current',
13
+ 'api/v2/rh_cloud/foreman_inventory_upload/api/tasks/history',
12
14
  ],
13
15
  skip_patterns: [/^(?!foreman_inventory_upload|insights_cloud|.*rh_cloud).*/]
14
16
  ) # include only plugin paths
@@ -3,18 +3,18 @@ import { noop } from 'foremanReact/common/helpers';
3
3
  export const accounts = {
4
4
  Account1: {
5
5
  id: 1,
6
- upload_report_status: 'running',
7
- generate_report_status: 'running',
6
+ uploaded_status: 'running',
7
+ generated_status: 'running',
8
8
  },
9
9
  Account2: {
10
10
  id: 2,
11
- upload_report_status: 'unknown',
12
- generate_report_status: 'failure',
11
+ uploaded_status: 'unknown',
12
+ generated_status: 'failure',
13
13
  },
14
14
  Account3: {
15
15
  id: 3,
16
- upload_report_status: 'success',
17
- generate_report_status: 'running',
16
+ uploaded_status: 'success',
17
+ generated_status: 'running',
18
18
  },
19
19
  };
20
20
 
@@ -1,4 +1,4 @@
1
- import React, { Component } from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import { isEmpty } from 'lodash';
3
3
  import { noop } from 'foremanReact/common/helpers';
4
4
  import { Accordion } from '@patternfly/react-core';
@@ -10,42 +10,57 @@ import EmptyResults from './Components/EmptyResults';
10
10
  import { filterAccounts } from './AccountListHelper';
11
11
  import './accountList.scss';
12
12
 
13
- class AccountList extends Component {
14
- componentDidMount() {
15
- const { fetchAccountsStatus, startAccountStatusPolling } = this.props;
13
+ const AccountList = ({
14
+ accounts,
15
+ error,
16
+ filterTerm,
17
+ fetchAccountsStatus,
18
+ startAccountStatusPolling,
19
+ stopAccountStatusPolling,
20
+ pollingProcessID,
21
+ }) => {
22
+ useEffect(() => {
16
23
  fetchAccountsStatus();
17
- const pollingProcessID = setInterval(fetchAccountsStatus, 5000);
18
- startAccountStatusPolling(pollingProcessID);
19
- }
20
-
21
- componentWillUnmount() {
22
- const { stopAccountStatusPolling, pollingProcessID } = this.props;
23
- stopAccountStatusPolling(pollingProcessID);
24
- }
24
+ const pollingID = setInterval(fetchAccountsStatus, 2000);
25
+ startAccountStatusPolling(pollingID);
25
26
 
26
- render() {
27
- const { accounts, error, filterTerm } = this.props;
28
- const filteredAccount = filterAccounts(accounts, filterTerm);
27
+ return () => {
28
+ stopAccountStatusPolling(pollingID);
29
+ };
30
+ }, [
31
+ fetchAccountsStatus,
32
+ startAccountStatusPolling,
33
+ stopAccountStatusPolling,
34
+ ]);
29
35
 
30
- if (error) {
31
- return <ErrorState error={error} />;
32
- }
36
+ const filteredAccount = filterAccounts(accounts, filterTerm);
33
37
 
34
- if (isEmpty(accounts)) {
35
- return <EmptyState />;
36
- }
38
+ if (error) {
39
+ return <ErrorState error={error} />;
40
+ }
37
41
 
38
- if (isEmpty(filteredAccount)) {
39
- return <EmptyResults />;
40
- }
42
+ if (isEmpty(accounts)) {
43
+ return <EmptyState />;
44
+ }
41
45
 
42
- const items = Object.keys(filteredAccount).map((label, index) => {
43
- const account = accounts[label];
44
- return <ListItem key={index} label={label} account={account} />;
45
- });
46
- return <Accordion className="account-list">{items}</Accordion>;
46
+ if (isEmpty(filteredAccount)) {
47
+ return <EmptyResults />;
47
48
  }
48
- }
49
+
50
+ const items = Object.keys(filteredAccount).map((label, index) => {
51
+ const account = accounts[label];
52
+ return (
53
+ <ListItem
54
+ key={label}
55
+ label={label}
56
+ account={account}
57
+ defaultExpanded={index === 0}
58
+ onTaskStart={fetchAccountsStatus}
59
+ />
60
+ );
61
+ });
62
+ return <Accordion className="account-list">{items}</Accordion>;
63
+ };
49
64
 
50
65
  AccountList.propTypes = {
51
66
  fetchAccountsStatus: PropTypes.func,
@@ -53,8 +68,8 @@ AccountList.propTypes = {
53
68
  stopAccountStatusPolling: PropTypes.func,
54
69
  pollingProcessID: PropTypes.number,
55
70
  account: PropTypes.shape({
56
- generate_report_status: PropTypes.string,
57
- upload_report_status: PropTypes.string,
71
+ generated_status: PropTypes.string,
72
+ uploaded_status: PropTypes.string,
58
73
  }),
59
74
  accounts: PropTypes.object,
60
75
  error: PropTypes.string,
@@ -67,8 +82,8 @@ AccountList.defaultProps = {
67
82
  stopAccountStatusPolling: noop,
68
83
  pollingProcessID: 0,
69
84
  account: {
70
- generate_report_status: 'unknown',
71
- upload_report_status: 'unknown',
85
+ generated_status: 'unknown',
86
+ uploaded_status: 'unknown',
72
87
  },
73
88
  accounts: {},
74
89
  error: '',
@@ -46,10 +46,10 @@ export const restartProcess = (accountID, activeTab) => async dispatch => {
46
46
 
47
47
  if (activeTab === 'uploading') {
48
48
  processController = 'uploads';
49
- processStatusName = 'upload_report_status';
49
+ processStatusName = 'uploaded_status';
50
50
  } else {
51
51
  processController = 'reports';
52
- processStatusName = 'generate_report_status';
52
+ processStatusName = 'generated_status';
53
53
  }
54
54
 
55
55
  try {
@@ -1,10 +1,9 @@
1
1
  export const props = {
2
2
  label: 'test',
3
3
  account: {
4
- test: {
5
- generate_report_status: 'unknown',
6
- upload_report_status: 'unknown',
7
- id: 1,
8
- },
4
+ generated_status: 'unknown',
5
+ uploaded_status: 'unknown',
6
+ id: 1,
7
+ report_file_paths: [],
9
8
  },
10
9
  };
@@ -10,8 +10,8 @@ import PropTypes from 'prop-types';
10
10
  import ListItemStatus from '../ListItemStatus';
11
11
  import Dashboard from '../../../Dashboard';
12
12
 
13
- const ListItem = ({ label, account }) => {
14
- const [isExpanded, setIsExpanded] = useState(false);
13
+ const ListItem = ({ label, account, defaultExpanded, onTaskStart }) => {
14
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
15
15
  return (
16
16
  <AccordionItem>
17
17
  <AccordionToggle
@@ -30,7 +30,11 @@ const ListItem = ({ label, account }) => {
30
30
  <ListItemStatus key={`${label}_status`} account={account} />
31
31
  </AccordionToggle>
32
32
  <AccordionContent isHidden={!isExpanded}>
33
- <Dashboard accountID={account.id} account={account} />
33
+ <Dashboard
34
+ accountID={account.id}
35
+ account={account}
36
+ onTaskStart={onTaskStart}
37
+ />
34
38
  </AccordionContent>
35
39
  </AccordionItem>
36
40
  );
@@ -39,18 +43,22 @@ const ListItem = ({ label, account }) => {
39
43
  ListItem.propTypes = {
40
44
  label: PropTypes.string.isRequired,
41
45
  account: PropTypes.shape({
42
- generate_report_status: PropTypes.string,
43
- upload_report_status: PropTypes.string,
46
+ generated_status: PropTypes.string,
47
+ uploaded_status: PropTypes.string,
44
48
  id: PropTypes.number,
45
49
  }),
50
+ defaultExpanded: PropTypes.bool,
51
+ onTaskStart: PropTypes.func,
46
52
  };
47
53
 
48
54
  ListItem.defaultProps = {
49
55
  account: {
50
- generate_report_status: 'unknown',
51
- upload_report_status: 'unknown',
56
+ generated_status: 'unknown',
57
+ uploaded_status: 'unknown',
52
58
  id: 0,
53
59
  },
60
+ defaultExpanded: false,
61
+ onTaskStart: null,
54
62
  };
55
63
 
56
64
  export default ListItem;
@@ -18,11 +18,10 @@ exports[`ListItem rendering render with Props 1`] = `
18
18
  <ListItemStatus
19
19
  account={
20
20
  Object {
21
- "test": Object {
22
- "generate_report_status": "unknown",
23
- "id": 1,
24
- "upload_report_status": "unknown",
25
- },
21
+ "generated_status": "unknown",
22
+ "id": 1,
23
+ "report_file_paths": Array [],
24
+ "uploaded_status": "unknown",
26
25
  }
27
26
  }
28
27
  key="test_status"
@@ -31,16 +30,17 @@ exports[`ListItem rendering render with Props 1`] = `
31
30
  <AccordionContent
32
31
  isHidden={true}
33
32
  >
34
- <Connect(Dashboard)
33
+ <Dashboard
35
34
  account={
36
35
  Object {
37
- "test": Object {
38
- "generate_report_status": "unknown",
39
- "id": 1,
40
- "upload_report_status": "unknown",
41
- },
36
+ "generated_status": "unknown",
37
+ "id": 1,
38
+ "report_file_paths": Array [],
39
+ "uploaded_status": "unknown",
42
40
  }
43
41
  }
42
+ accountID={1}
43
+ onTaskStart={null}
44
44
  />
45
45
  </AccordionContent>
46
46
  </AccordionItem>
@@ -1,7 +1,7 @@
1
1
  export const props = {
2
2
  account: {
3
- generate_report_status: 'Exit Code: pid 22191 exit 0',
4
- upload_report_status: 'Exit Code: Running in pid 22376',
3
+ generated_status: 'success',
4
+ uploaded_status: 'running',
5
5
  label: 'Default Org',
6
6
  },
7
7
  };