openc3-cosmos-cfdp 1.0.2 → 2.0.0

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -1
  3. data/microservices/CFDP/Gemfile +3 -11
  4. data/microservices/CFDP/Gemfile.dev +35 -0
  5. data/microservices/CFDP/Gemfile.prod +29 -0
  6. data/microservices/CFDP/app/controllers/cfdp_controller.rb +8 -0
  7. data/microservices/CFDP/app/models/cfdp_crc_checksum.rb +3 -1
  8. data/microservices/CFDP/app/models/cfdp_mib.rb +2 -1
  9. data/microservices/CFDP/app/models/cfdp_pdu.rb +43 -19
  10. data/microservices/CFDP/app/models/cfdp_receive_transaction.rb +7 -2
  11. data/microservices/CFDP/app/models/cfdp_source_transaction.rb +6 -1
  12. data/microservices/CFDP/app/models/cfdp_transaction.rb +1 -1
  13. data/microservices/CFDP/app/models/cfdp_user.rb +14 -4
  14. data/microservices/CFDP/config/application.rb +0 -11
  15. data/microservices/CFDP/config/environments/test.rb +1 -1
  16. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_file_data.rb +6 -2
  17. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_finished.rb +17 -5
  18. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_metadata.rb +28 -11
  19. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_tlv.rb +5 -2
  20. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_user_ops.rb +16 -9
  21. data/microservices/CFDP/spec/controllers/cfdp_controller_spec.rb +373 -0
  22. data/microservices/CFDP/spec/models/cfdp_crc_checksum_spec.rb +134 -0
  23. data/microservices/CFDP/spec/models/cfdp_mib_spec.rb +389 -0
  24. data/microservices/CFDP/spec/models/cfdp_pdu_metadata_spec.rb +102 -1
  25. data/microservices/CFDP/spec/models/cfdp_pdu_user_ops_spec.rb +562 -0
  26. data/microservices/CFDP/spec/models/cfdp_receive_transaction_spec.rb +598 -0
  27. data/microservices/CFDP/spec/models/cfdp_transaction_spec.rb +331 -0
  28. data/microservices/CFDP/spec/models/cfdp_user_spec.rb +575 -0
  29. metadata +15 -6
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2023 OpenC3, Inc.
3
+ # Copyright 2025 OpenC3, Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # Licensed for Evaluation and Educational Use
@@ -13,6 +13,9 @@
13
13
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
14
  #
15
15
  # The development of this software was funded in-whole or in-part by MethaneSAT LLC.
16
+ #
17
+ # The development of this software was funded in-part by Sandia National Laboratories.
18
+ # See https://github.com/OpenC3/openc3-cosmos-cfdp/pull/12 for details
16
19
 
17
20
  class CfdpPdu < OpenC3::Packet
18
21
  def self.decom_tlv(pdu, pdu_hash, variable_data)
@@ -77,7 +80,7 @@ class CfdpPdu < OpenC3::Packet
77
80
  s = define_message_to_user_tlv()
78
81
  s.buffer = tlv_data
79
82
  tlv["MESSAGE_TO_USER"] = s.read("MESSAGE_TO_USER")
80
- tlv.merge!(pdu.decom_message_to_user(tlv["MESSAGE_TO_USER"]))
83
+ tlv.merge!(pdu.decom_message_to_user(tlv["MESSAGE_TO_USER"], version: pdu_hash["VERSION"]))
81
84
 
82
85
  when "FAULT_HANDLER_OVERRIDE"
83
86
  s = define_fault_handler_override_tlv()
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2023 OpenC3, Inc.
3
+ # Copyright 2025 OpenC3, Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # Licensed for Evaluation and Educational Use
@@ -13,6 +13,9 @@
13
13
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
14
  #
15
15
  # The development of this software was funded in-whole or in-part by MethaneSAT LLC.
16
+ #
17
+ # The development of this software was funded in-part by Sandia National Laboratories.
18
+ # See https://github.com/OpenC3/openc3-cosmos-cfdp/pull/12 for details
16
19
 
17
20
  class CfdpPdu < OpenC3::Packet
18
21
 
@@ -495,19 +498,23 @@ class CfdpPdu < OpenC3::Packet
495
498
  end
496
499
 
497
500
  # Table 6-16
498
- def define_directory_listing_response_message
501
+ def define_directory_listing_response_message(version:)
499
502
  s1 = OpenC3::Packet.new(nil, nil, :BIG_ENDIAN)
500
503
  item = s1.append_item("RESPONSE_CODE", 1, :UINT)
501
- item.states = { "SUCCESSFUL" => 0, "UNSUCCESSFUL" => 1 }
504
+ if version >= 1
505
+ item.states = { "SUCCESSFUL" => 0, "UNSUCCESSFUL" => 1 }
506
+ else
507
+ item.states = { "SUCCESSFUL" => 0, "UNSUCCESSFUL" => 0xFF }
508
+ end
502
509
  s1.append_item("SPARE", 7, :UINT)
503
510
  return s1, define_length_value(), define_length_value()
504
511
  end
505
512
 
506
- def build_directory_listing_response_message(response_code:, directory_name:, directory_file_name:)
513
+ def build_directory_listing_response_message(response_code:, directory_name:, directory_file_name:, version:)
507
514
  s1 = define_reserved_cfdp_message_header()
508
515
  s1.write("MSG_ID", "cfdp")
509
516
  s1.write("MSG_TYPE", "DIRECTORY_LISTING_RESPONSE")
510
- fixed, s2, s3 = define_directory_listing_response_message()
517
+ fixed, s2, s3 = define_directory_listing_response_message(version: version)
511
518
  fixed.write("RESPONSE_CODE", response_code)
512
519
  fixed.write("SPARE", 0)
513
520
  s2.write("LENGTH", directory_name.to_s.length)
@@ -517,10 +524,10 @@ class CfdpPdu < OpenC3::Packet
517
524
  return s1.buffer(false) + fixed.buffer(false) + s2.buffer(false) + s3.buffer(false)
518
525
  end
519
526
 
520
- def decom_directory_listing_response_message(message_to_user)
527
+ def decom_directory_listing_response_message(message_to_user, version:)
521
528
  result = {}
522
529
  message_to_user = message_to_user[5..-1] # Remove header
523
- fixed, s2, s3 = define_directory_listing_response_message()
530
+ fixed, s2, s3 = define_directory_listing_response_message(version: version)
524
531
  fixed.buffer = message_to_user[0..(fixed.defined_length - 1)]
525
532
  result["RESPONSE_CODE"] = fixed.read("RESPONSE_CODE")
526
533
  message_to_user = message_to_user[fixed.defined_length..-1]
@@ -689,7 +696,7 @@ class CfdpPdu < OpenC3::Packet
689
696
  return result
690
697
  end
691
698
 
692
- def decom_message_to_user(message_to_user)
699
+ def decom_message_to_user(message_to_user, version:)
693
700
  s1 = define_reserved_cfdp_message_header()
694
701
  if message_to_user.length >= 5 # Minimum size
695
702
  s1.buffer = message_to_user[0..(s1.defined_length - 1)]
@@ -723,7 +730,7 @@ class CfdpPdu < OpenC3::Packet
723
730
  when "DIRECTORY_LISTING_REQUEST"
724
731
  return decom_directory_listing_request_message(message_to_user)
725
732
  when "DIRECTORY_LISTING_RESPONSE"
726
- return decom_directory_listing_response_message(message_to_user)
733
+ return decom_directory_listing_response_message(message_to_user, version: version)
727
734
  when "REMOTE_STATUS_REPORT_REQUEST"
728
735
  return decom_remote_status_report_request_message(message_to_user)
729
736
  when "REMOTE_STATUS_REPORT_RESPONSE"
@@ -0,0 +1,373 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2025 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # Licensed for Evaluation and Educational Use
7
+ #
8
+ # This file may only be used commercially under the terms of a commercial license
9
+ # purchased from OpenC3, Inc.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ #
15
+ # The development of this software was funded in-whole or in-part by Sandia National Laboratories.
16
+ # See https://github.com/OpenC3/openc3-cosmos-cfdp/pull/12 for details
17
+
18
+ require 'rails_helper'
19
+
20
+ $orig_cfdp_user = $cfdp_user
21
+
22
+ RSpec.describe CfdpController, type: :controller do
23
+ before(:each) do
24
+ @mock_user = double("CfdpUser")
25
+ @mock_transaction = double("CfdpTransaction")
26
+ allow(@mock_transaction).to receive(:id).and_return("1__123")
27
+ $cfdp_user = @mock_user
28
+
29
+ # Setup authorization mock
30
+ allow(controller).to receive(:check_authorization).and_return(true)
31
+ allow(controller).to receive(:authorization).and_return(true)
32
+ end
33
+
34
+ after(:each) do
35
+ $cfdp_user = $orig_cfdp_user
36
+ end
37
+
38
+ describe "PUT requests" do
39
+ it "handles put request successfully" do
40
+ params = {
41
+ destination_entity_id: "2",
42
+ destination_file_name: "dest.txt",
43
+ source_file_name: "source.txt",
44
+ controller: "cfdp",
45
+ action: "put"
46
+ }
47
+
48
+ # Expect CfdpUser to be called with proper parameters
49
+ expect(@mock_user).to receive(:start_source_transaction).with(
50
+ ActionController::Parameters.new(params)
51
+ ).and_return(@mock_transaction)
52
+
53
+ post :put, params: params
54
+
55
+ expect(response).to have_http_status(:success)
56
+ expect(response.body).to eq("1__123")
57
+ end
58
+
59
+ it "requires destination_entity_id" do
60
+ post :put, params: {}
61
+
62
+ expect(response).to have_http_status(:bad_request)
63
+ expect(JSON.parse(response.body)["message"]).to include("destination_entity_id")
64
+ end
65
+
66
+ it "handles errors" do
67
+ allow(@mock_user).to receive(:start_source_transaction).and_raise("Test error")
68
+
69
+ post :put, params: { destination_entity_id: "2" }
70
+
71
+ expect(response).to have_http_status(:internal_server_error)
72
+ expect(JSON.parse(response.body)["message"]).to include("Test error")
73
+ end
74
+ end
75
+
76
+ describe "CANCEL requests" do
77
+ it "handles cancel request successfully" do
78
+ params = { action: "cancel", controller: "cfdp", transaction_id: "1__123" }
79
+
80
+ # Expect CfdpUser to be called with proper parameters
81
+ expect(@mock_user).to receive(:cancel).with(
82
+ ActionController::Parameters.new(params)
83
+ ).and_return(@mock_transaction)
84
+
85
+ post :cancel, params: params
86
+
87
+ expect(response).to have_http_status(:success)
88
+ expect(response.body).to eq("1__123")
89
+ end
90
+
91
+ it "requires transaction_id" do
92
+ post :cancel, params: {}
93
+
94
+ expect(response).to have_http_status(:bad_request)
95
+ end
96
+
97
+ it "handles transaction not found" do
98
+ allow(@mock_user).to receive(:cancel).and_return(nil)
99
+
100
+ post :cancel, params: { transaction_id: "1__123" }
101
+
102
+ expect(response).to have_http_status(:not_found)
103
+ expect(JSON.parse(response.body)["message"]).to include("not found")
104
+ end
105
+
106
+ it "handles errors" do
107
+ allow(@mock_user).to receive(:cancel).and_raise("Test error")
108
+
109
+ post :cancel, params: { transaction_id: "1__123" }
110
+
111
+ expect(response).to have_http_status(:internal_server_error)
112
+ expect(JSON.parse(response.body)["message"]).to include("Test error")
113
+ end
114
+ end
115
+
116
+ describe "SUSPEND requests" do
117
+ it "handles suspend request successfully" do
118
+ params = { transaction_id: "1__123", controller: "cfdp", action: "suspend" }
119
+
120
+ # Expect CfdpUser to be called with proper parameters
121
+ expect(@mock_user).to receive(:suspend).with(
122
+ ActionController::Parameters.new(params)
123
+ ).and_return(@mock_transaction)
124
+
125
+ post :suspend, params: params
126
+
127
+ expect(response).to have_http_status(:success)
128
+ expect(response.body).to eq("1__123")
129
+ end
130
+
131
+ it "requires transaction_id" do
132
+ post :suspend, params: {}
133
+
134
+ expect(response).to have_http_status(:bad_request)
135
+ end
136
+
137
+ it "handles transaction not found" do
138
+ allow(@mock_user).to receive(:suspend).and_return(nil)
139
+
140
+ post :suspend, params: { transaction_id: "1__123" }
141
+
142
+ expect(response).to have_http_status(:not_found)
143
+ expect(JSON.parse(response.body)["message"]).to include("not found")
144
+ end
145
+ end
146
+
147
+ describe "RESUME requests" do
148
+ it "handles resume request successfully" do
149
+ params = { action: "resume", controller: "cfdp", transaction_id: "1__123" }
150
+
151
+ # Expect CfdpUser to be called with proper parameters
152
+ expect(@mock_user).to receive(:resume).with(
153
+ ActionController::Parameters.new(params)
154
+ ).and_return(@mock_transaction)
155
+
156
+ post :resume, params: params
157
+
158
+ expect(response).to have_http_status(:success)
159
+ expect(response.body).to eq("1__123")
160
+ end
161
+
162
+ it "requires transaction_id" do
163
+ post :resume, params: {}
164
+
165
+ expect(response).to have_http_status(:bad_request)
166
+ end
167
+
168
+ it "handles transaction not found" do
169
+ allow(@mock_user).to receive(:resume).and_return(nil)
170
+
171
+ post :resume, params: { transaction_id: "1__123" }
172
+
173
+ expect(response).to have_http_status(:not_found)
174
+ expect(JSON.parse(response.body)["message"]).to include("not found")
175
+ end
176
+ end
177
+
178
+ describe "REPORT requests" do
179
+ it "handles report request successfully" do
180
+ params = { transaction_id: "1__123", controller: "cfdp", action: "report" }
181
+
182
+ # Expect CfdpUser to be called with proper parameters
183
+ expect(@mock_user).to receive(:report).with(
184
+ ActionController::Parameters.new(params)
185
+ ).and_return(@mock_transaction)
186
+
187
+ post :report, params: params
188
+
189
+ expect(response).to have_http_status(:success)
190
+ expect(response.body).to eq("1__123")
191
+ end
192
+
193
+ it "requires transaction_id" do
194
+ post :report, params: {}
195
+
196
+ expect(response).to have_http_status(:bad_request)
197
+ end
198
+
199
+ it "handles transaction not found" do
200
+ allow(@mock_user).to receive(:report).and_return(nil)
201
+
202
+ post :report, params: { transaction_id: "1__123" }
203
+
204
+ expect(response).to have_http_status(:not_found)
205
+ expect(JSON.parse(response.body)["message"]).to include("not found")
206
+ end
207
+ end
208
+
209
+ describe "DIRECTORY_LISTING requests" do
210
+ it "handles directory listing request successfully" do
211
+ params = {
212
+ remote_entity_id: "2",
213
+ directory_name: "test_dir",
214
+ directory_file_name: "result.txt",
215
+ controller: "cfdp",
216
+ action: "directory_listing"
217
+ }
218
+
219
+ # Expect CfdpUser to be called with proper parameters
220
+ expect(@mock_user).to receive(:start_directory_listing).with(
221
+ ActionController::Parameters.new(params)
222
+ ).and_return(@mock_transaction)
223
+
224
+ post :directory_listing, params: params
225
+
226
+ expect(response).to have_http_status(:success)
227
+ expect(response.body).to eq("1__123")
228
+ end
229
+
230
+ it "requires remote_entity_id, directory_name and directory_file_name" do
231
+ post :directory_listing, params: {}
232
+
233
+ expect(response).to have_http_status(:bad_request)
234
+ expect(JSON.parse(response.body)["message"]).to include("missing")
235
+ end
236
+ end
237
+
238
+ describe "SUBSCRIBE requests" do
239
+ before(:each) do
240
+ allow(CfdpTopic).to receive(:subscribe_indications).and_return("0-0")
241
+ end
242
+
243
+ it "handles subscribe request successfully" do
244
+ post :subscribe
245
+
246
+ expect(response).to have_http_status(:success)
247
+ expect(response.body).to eq("0-0")
248
+ end
249
+
250
+ it "handles errors" do
251
+ allow(CfdpTopic).to receive(:subscribe_indications).and_raise("Test error")
252
+
253
+ post :subscribe
254
+
255
+ expect(response).to have_http_status(:internal_server_error)
256
+ expect(JSON.parse(response.body)["message"]).to include("Test error")
257
+ end
258
+ end
259
+
260
+ describe "INDICATIONS requests" do
261
+ before(:each) do
262
+ @indications = {
263
+ continuation: "1-1",
264
+ indications: [
265
+ {
266
+ "time" => Time.now.to_nsec_from_epoch,
267
+ "indication_type" => "Transaction-Finished",
268
+ "transaction_id" => "1__123",
269
+ "condition_code" => "NO_ERROR"
270
+ }
271
+ ]
272
+ }
273
+ allow(CfdpTopic).to receive(:read_indications).and_return(@indications)
274
+ end
275
+
276
+ it "gets indications for all transactions" do
277
+ get :indications
278
+
279
+ expect(response).to have_http_status(:success)
280
+ parsed = JSON.parse(response.body)
281
+ expect(parsed["continuation"]).to eq("1-1")
282
+ expect(parsed["indications"]).to be_an(Array)
283
+ expect(parsed["indications"].length).to eq(1)
284
+ end
285
+
286
+ it "gets indications for a specific transaction" do
287
+ get :indications, params: { transaction_id: "1__123" }
288
+
289
+ expect(response).to have_http_status(:success)
290
+ expect(CfdpTopic).to have_received(:read_indications).with(
291
+ hash_including(transaction_id: "1__123")
292
+ )
293
+ end
294
+
295
+ it "handles errors" do
296
+ allow(CfdpTopic).to receive(:read_indications).and_raise("Test error")
297
+
298
+ get :indications
299
+
300
+ expect(response).to have_http_status(:internal_server_error)
301
+ expect(JSON.parse(response.body)["message"]).to include("Test error")
302
+ end
303
+ end
304
+
305
+ describe "TRANSACTIONS requests" do
306
+ before(:each) do
307
+ @transactions = {
308
+ "1__123" => @mock_transaction,
309
+ "1__124" => @mock_transaction
310
+ }
311
+
312
+ allow(CfdpMib).to receive(:transactions).and_return(@transactions)
313
+ allow(CfdpMib).to receive(:cleanup_old_transactions)
314
+
315
+ allow(@mock_transaction).to receive(:as_json).and_return({"id" => "1__123", "state" => "ACTIVE"})
316
+ allow(@mock_transaction).to receive(:transaction_status).and_return("ACTIVE")
317
+ allow(@mock_transaction).to receive(:id).and_return("1__123")
318
+ end
319
+
320
+ it "gets all transactions" do
321
+ get :transactions
322
+
323
+ expect(response).to have_http_status(:success)
324
+
325
+ parsed = JSON.parse(response.body)
326
+ expect(parsed).to be_an(Array)
327
+ expect(parsed.length).to eq(2)
328
+ end
329
+
330
+ it "gets only active transactions" do
331
+ get :transactions, params: { active: "true" }
332
+
333
+ expect(response).to have_http_status(:success)
334
+
335
+ parsed = JSON.parse(response.body)
336
+ expect(parsed).to be_an(Array)
337
+ expect(parsed.length).to eq(2)
338
+ end
339
+
340
+ it "handles errors" do
341
+ allow(CfdpMib).to receive(:transactions).and_raise("Test error")
342
+
343
+ get :transactions
344
+
345
+ expect(response).to have_http_status(:internal_server_error)
346
+ expect(JSON.parse(response.body)["message"]).to include("Test error")
347
+ end
348
+ end
349
+
350
+ describe "check_authorization" do
351
+ before(:each) do
352
+ # Reset the mock to allow the real method to be called
353
+ allow(controller).to receive(:check_authorization).and_call_original
354
+
355
+ # Setup mocks needed by the method
356
+ @entity = {
357
+ "id" => 1,
358
+ "cmd_info" => ["TARGET", "PACKET", "ITEM"],
359
+ "tlm_info" => []
360
+ }
361
+
362
+ allow(CfdpMib).to receive(:entity).and_return(@entity)
363
+ allow(CfdpMib).to receive(:source_entity_id).and_return(1)
364
+ allow(CfdpMib).to receive(:cleanup_old_transactions)
365
+ end
366
+
367
+ it "authorizes when entity_id is numeric" do
368
+ allow(controller).to receive(:authorization).and_return(true)
369
+
370
+ expect(controller.send(:check_authorization)).to be true
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,134 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2025 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # Licensed for Evaluation and Educational Use
7
+ #
8
+ # This file may only be used commercially under the terms of a commercial license
9
+ # purchased from OpenC3, Inc.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ #
15
+ # The development of this software was funded in-whole or in-part by Sandia National Laboratories.
16
+ # See https://github.com/OpenC3/openc3-cosmos-cfdp/pull/12 for details
17
+
18
+ require 'rails_helper'
19
+ require 'tempfile'
20
+
21
+ RSpec.describe CfdpCrcChecksum do
22
+ before(:each) do
23
+ # Mock OpenC3::Crc32
24
+ @mock_crc32 = double("OpenC3::Crc32")
25
+ allow(@mock_crc32).to receive(:calc).and_return(0x12345678)
26
+ allow(OpenC3::Crc32).to receive(:new).and_return(@mock_crc32)
27
+ end
28
+
29
+ describe "initialize" do
30
+ it "initializes with default values" do
31
+ checksum = CfdpCrcChecksum.new
32
+ expect(checksum.crc).to eq(@mock_crc32)
33
+ expect(checksum.instance_variable_get(:@checksum)).to eq(0)
34
+ end
35
+
36
+ it "initializes with custom values" do
37
+ # These are the defaults from the OpenC3::Crc32 class
38
+ poly = 0x04C11DB7
39
+ seed = 0xFFFFFFFF
40
+ xor = true
41
+ reflect = true
42
+
43
+ # Expect it to be called with these specific parameters
44
+ expect(OpenC3::Crc32).to receive(:new).with(poly, seed, xor, reflect).and_return(@mock_crc32)
45
+
46
+ checksum = CfdpCrcChecksum.new(poly, seed, xor, reflect)
47
+ expect(checksum.crc).to eq(@mock_crc32)
48
+ end
49
+ end
50
+
51
+ describe "add" do
52
+ it "always returns 0 (not supported)" do
53
+ checksum = CfdpCrcChecksum.new
54
+ result = checksum.add(100, "test data")
55
+ expect(result).to eq(0)
56
+ end
57
+ end
58
+
59
+ describe "checksum" do
60
+ it "calculates checksum from file" do
61
+ # Create a tempfile with content
62
+ file = Tempfile.new('crc_test')
63
+ file.write("test data")
64
+ file.rewind
65
+
66
+ # Expect the calc method to be called with the file data
67
+ expect(@mock_crc32).to receive(:calc).with("test data").and_return(0xABCDEF12)
68
+
69
+ checksum = CfdpCrcChecksum.new
70
+ result = checksum.checksum(file, false)
71
+
72
+ # The result should be masked to 32 bits
73
+ expect(result).to eq(0xABCDEF12)
74
+
75
+ file.close
76
+ file.unlink
77
+ end
78
+ end
79
+
80
+ describe "check" do
81
+ it "returns true when checksums match" do
82
+ # Create a tempfile with content
83
+ file = Tempfile.new('crc_test')
84
+ file.write("test data")
85
+ file.rewind
86
+
87
+ checksum = CfdpCrcChecksum.new
88
+
89
+ # First, mock the internal checksum call to return a specific value
90
+ expect(checksum).to receive(:checksum).with(file, false).and_return(0x12345678)
91
+
92
+ # Then check against the same value
93
+ expect(checksum.check(file, 0x12345678, false)).to be true
94
+
95
+ file.close
96
+ file.unlink
97
+ end
98
+
99
+ it "returns false when checksums don't match" do
100
+ # Create a tempfile with content
101
+ file = Tempfile.new('crc_test')
102
+ file.write("test data")
103
+ file.rewind
104
+
105
+ checksum = CfdpCrcChecksum.new
106
+
107
+ # First, mock the internal checksum call to return a specific value
108
+ expect(checksum).to receive(:checksum).with(file, false).and_return(0x12345678)
109
+
110
+ # Then check against a different value
111
+ expect(checksum.check(file, 0x87654321, false)).to be false
112
+
113
+ file.close
114
+ file.unlink
115
+ end
116
+
117
+ it "handles an empty file" do
118
+ # Create an empty tempfile
119
+ file = Tempfile.new('crc_test')
120
+ file.rewind
121
+
122
+ checksum = CfdpCrcChecksum.new
123
+
124
+ # For an empty file, mock the CRC to return a specific value
125
+ expect(checksum).to receive(:checksum).with(file, false).and_return(0)
126
+
127
+ # The check should pass when comparing with the same value
128
+ expect(checksum.check(file, 0, false)).to be true
129
+
130
+ file.close
131
+ file.unlink
132
+ end
133
+ end
134
+ end