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.
- checksums.yaml +4 -4
- data/README.md +10 -1
- data/microservices/CFDP/Gemfile +3 -11
- data/microservices/CFDP/Gemfile.dev +35 -0
- data/microservices/CFDP/Gemfile.prod +29 -0
- data/microservices/CFDP/app/controllers/cfdp_controller.rb +8 -0
- data/microservices/CFDP/app/models/cfdp_crc_checksum.rb +3 -1
- data/microservices/CFDP/app/models/cfdp_mib.rb +2 -1
- data/microservices/CFDP/app/models/cfdp_pdu.rb +43 -19
- data/microservices/CFDP/app/models/cfdp_receive_transaction.rb +7 -2
- data/microservices/CFDP/app/models/cfdp_source_transaction.rb +6 -1
- data/microservices/CFDP/app/models/cfdp_transaction.rb +1 -1
- data/microservices/CFDP/app/models/cfdp_user.rb +14 -4
- data/microservices/CFDP/config/application.rb +0 -11
- data/microservices/CFDP/config/environments/test.rb +1 -1
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_file_data.rb +6 -2
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_finished.rb +17 -5
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_metadata.rb +28 -11
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_tlv.rb +5 -2
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_user_ops.rb +16 -9
- data/microservices/CFDP/spec/controllers/cfdp_controller_spec.rb +373 -0
- data/microservices/CFDP/spec/models/cfdp_crc_checksum_spec.rb +134 -0
- data/microservices/CFDP/spec/models/cfdp_mib_spec.rb +389 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_metadata_spec.rb +102 -1
- data/microservices/CFDP/spec/models/cfdp_pdu_user_ops_spec.rb +562 -0
- data/microservices/CFDP/spec/models/cfdp_receive_transaction_spec.rb +598 -0
- data/microservices/CFDP/spec/models/cfdp_transaction_spec.rb +331 -0
- data/microservices/CFDP/spec/models/cfdp_user_spec.rb +575 -0
- metadata +15 -6
@@ -0,0 +1,575 @@
|
|
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 'openc3/models/microservice_model'
|
20
|
+
require 'openc3/utilities/store_autoload'
|
21
|
+
|
22
|
+
RSpec.describe CfdpUser, type: :model do
|
23
|
+
before(:each) do
|
24
|
+
mock_redis()
|
25
|
+
@source_entity_id = 1
|
26
|
+
@destination_entity_id = 2
|
27
|
+
ENV['OPENC3_MICROSERVICE_NAME'] = 'DEFAULT__API__CFDP'
|
28
|
+
ENV['OPENC3_SCOPE'] = 'DEFAULT'
|
29
|
+
# Create the model that is consumed by CfdpMib.setup
|
30
|
+
model = OpenC3::MicroserviceModel.new(name: ENV['OPENC3_MICROSERVICE_NAME'], scope: "DEFAULT",
|
31
|
+
options: [
|
32
|
+
["source_entity_id", @source_entity_id],
|
33
|
+
["destination_entity_id", @destination_entity_id],
|
34
|
+
["root_path", SPEC_DIR],
|
35
|
+
],
|
36
|
+
)
|
37
|
+
model.create
|
38
|
+
CfdpMib.clear
|
39
|
+
CfdpMib.setup
|
40
|
+
|
41
|
+
# Mock JsonPacket
|
42
|
+
@packet = double("packet")
|
43
|
+
allow(OpenC3::JsonPacket).to receive(:new).and_return(@packet)
|
44
|
+
|
45
|
+
@user = CfdpUser.new
|
46
|
+
end
|
47
|
+
|
48
|
+
after(:each) do
|
49
|
+
@user.stop
|
50
|
+
# Clear MIB state between tests
|
51
|
+
CfdpMib.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "initialize" do
|
55
|
+
it "initializes instance variables" do
|
56
|
+
expect(@user.instance_variable_get(:@thread)).to be_nil
|
57
|
+
expect(@user.instance_variable_get(:@cancel_thread)).to be false
|
58
|
+
expect(@user.instance_variable_get(:@item_name_lookup)).to eq({})
|
59
|
+
expect(@user.instance_variable_get(:@source_transactions)).to eq([])
|
60
|
+
expect(@user.instance_variable_get(:@source_threads)).to eq([])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "receive_packet" do
|
65
|
+
it "receives and decoms a packet" do
|
66
|
+
# Setup mock objects
|
67
|
+
topic = "DEFAULT__DECOM__{CFDP}__PDU"
|
68
|
+
msg_id = "msg123"
|
69
|
+
msg_hash = {"time" => "123456", "stored" => "TRUE", "json_data" => "{\"data\":\"test\"}"}
|
70
|
+
redis = double("redis")
|
71
|
+
|
72
|
+
# Setup expected behavior
|
73
|
+
@user.instance_variable_set(:@item_name_lookup, {topic => "DATA"})
|
74
|
+
pdu_data = "binary_pdu_data"
|
75
|
+
pdu_hash = {"key" => "value"}
|
76
|
+
|
77
|
+
allow(@packet).to receive(:read).with("DATA").and_return(pdu_data)
|
78
|
+
allow(CfdpPdu).to receive(:decom).with(pdu_data).and_return(pdu_hash)
|
79
|
+
|
80
|
+
# Test
|
81
|
+
result = @user.receive_packet(topic, msg_id, msg_hash, redis)
|
82
|
+
expect(result).to eq(pdu_hash)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "start_source_transaction" do
|
87
|
+
it "creates and starts a new source transaction" do
|
88
|
+
# Setup mock thread
|
89
|
+
thread = double("thread")
|
90
|
+
allow(Thread).to receive(:new).and_yield.and_return(thread)
|
91
|
+
allow(thread).to receive(:alive?).and_return(false)
|
92
|
+
|
93
|
+
# Setup transaction
|
94
|
+
transaction = double("transaction")
|
95
|
+
allow(transaction).to receive(:id).and_return("transaction_1")
|
96
|
+
allow(CfdpSourceTransaction).to receive(:new).and_return(transaction)
|
97
|
+
allow(transaction).to receive(:proxy_response_info=)
|
98
|
+
allow(transaction).to receive(:put)
|
99
|
+
allow(transaction).to receive(:abandon)
|
100
|
+
|
101
|
+
# Mock logger
|
102
|
+
allow(OpenC3::Logger).to receive(:info)
|
103
|
+
|
104
|
+
# Call the method
|
105
|
+
params = {
|
106
|
+
destination_entity_id: @destination_entity_id,
|
107
|
+
source_file_name: "source.txt",
|
108
|
+
destination_file_name: "dest.txt"
|
109
|
+
}
|
110
|
+
|
111
|
+
result = @user.start_source_transaction(params)
|
112
|
+
|
113
|
+
# Verify results
|
114
|
+
expect(result).to eq(transaction)
|
115
|
+
expect(@user.instance_variable_get(:@source_transactions)).to include(transaction)
|
116
|
+
expect(@user.instance_variable_get(:@source_threads)).to include(thread)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "creates and starts a new proxy put transaction" do
|
120
|
+
# Setup mock thread
|
121
|
+
thread = double("thread")
|
122
|
+
allow(Thread).to receive(:new).and_yield.and_return(thread)
|
123
|
+
allow(thread).to receive(:alive?).and_return(false)
|
124
|
+
|
125
|
+
# Setup transaction
|
126
|
+
transaction = double("transaction")
|
127
|
+
allow(transaction).to receive(:id).and_return("transaction_1")
|
128
|
+
allow(CfdpSourceTransaction).to receive(:new).and_return(transaction)
|
129
|
+
allow(transaction).to receive(:proxy_response_info=)
|
130
|
+
allow(transaction).to receive(:put)
|
131
|
+
allow(transaction).to receive(:abandon)
|
132
|
+
|
133
|
+
# Mock logger
|
134
|
+
allow(OpenC3::Logger).to receive(:info)
|
135
|
+
|
136
|
+
# Call the method
|
137
|
+
params = {
|
138
|
+
remote_entity_id: 3,
|
139
|
+
destination_entity_id: @destination_entity_id,
|
140
|
+
source_file_name: "source.txt",
|
141
|
+
destination_file_name: "dest.txt"
|
142
|
+
}
|
143
|
+
|
144
|
+
result = @user.start_source_transaction(params)
|
145
|
+
|
146
|
+
# Verify results
|
147
|
+
expect(result).to eq(transaction)
|
148
|
+
expect(@user.instance_variable_get(:@source_transactions)).to include(transaction)
|
149
|
+
expect(@user.instance_variable_get(:@source_threads)).to include(thread)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "proxy_request_setup" do
|
154
|
+
it "sets up a proxy request" do
|
155
|
+
params = {
|
156
|
+
remote_entity_id: @destination_entity_id
|
157
|
+
}
|
158
|
+
|
159
|
+
pdu, entity_id, messages_to_user = @user.proxy_request_setup(params)
|
160
|
+
|
161
|
+
expect(pdu).to be_a(CfdpPdu)
|
162
|
+
expect(entity_id).to eq(@destination_entity_id)
|
163
|
+
expect(messages_to_user).to eq([])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "proxy_request_start" do
|
168
|
+
it "starts a proxy request" do
|
169
|
+
# Setup
|
170
|
+
entity_id = @destination_entity_id
|
171
|
+
messages_to_user = ["message1", "message2"]
|
172
|
+
|
173
|
+
# Mock start_source_transaction
|
174
|
+
expect(@user).to receive(:start_source_transaction).with(
|
175
|
+
{
|
176
|
+
destination_entity_id: entity_id,
|
177
|
+
messages_to_user: messages_to_user
|
178
|
+
}
|
179
|
+
).and_return("transaction")
|
180
|
+
|
181
|
+
# Call and verify
|
182
|
+
result = @user.proxy_request_start(entity_id: entity_id, messages_to_user: messages_to_user)
|
183
|
+
expect(result).to eq("transaction")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "start_directory_listing" do
|
188
|
+
it "starts a directory listing request" do
|
189
|
+
# Setup
|
190
|
+
pdu = instance_double(CfdpPdu)
|
191
|
+
allow(pdu).to receive(:build_directory_listing_request_message).and_return("message")
|
192
|
+
|
193
|
+
# Mock proxy_request_setup
|
194
|
+
expect(@user).to receive(:proxy_request_setup).with(
|
195
|
+
{
|
196
|
+
remote_entity_id: @destination_entity_id,
|
197
|
+
directory_name: "/tmp",
|
198
|
+
directory_file_name: "listing.txt"
|
199
|
+
}
|
200
|
+
).and_return([pdu, @destination_entity_id, []])
|
201
|
+
|
202
|
+
# Mock proxy_request_start
|
203
|
+
expect(@user).to receive(:proxy_request_start).with(
|
204
|
+
entity_id: @destination_entity_id,
|
205
|
+
messages_to_user: ["message"]
|
206
|
+
).and_return("transaction")
|
207
|
+
|
208
|
+
# Call and verify
|
209
|
+
params = {
|
210
|
+
remote_entity_id: @destination_entity_id,
|
211
|
+
directory_name: "/tmp",
|
212
|
+
directory_file_name: "listing.txt"
|
213
|
+
}
|
214
|
+
result = @user.start_directory_listing(params)
|
215
|
+
expect(result).to eq("transaction")
|
216
|
+
end
|
217
|
+
|
218
|
+
it "raises an error if directory_name is missing" do
|
219
|
+
params = {
|
220
|
+
remote_entity_id: @destination_entity_id,
|
221
|
+
directory_file_name: "listing.txt"
|
222
|
+
}
|
223
|
+
|
224
|
+
expect { @user.start_directory_listing(params) }.to raise_error(/directory_name required/)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "raises an error if directory_file_name is missing" do
|
228
|
+
params = {
|
229
|
+
remote_entity_id: @destination_entity_id,
|
230
|
+
directory_name: "/tmp"
|
231
|
+
}
|
232
|
+
|
233
|
+
expect { @user.start_directory_listing(params) }.to raise_error(/directory_file_name required/)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "cancel" do
|
238
|
+
it "cancels a remote transaction" do
|
239
|
+
# Setup
|
240
|
+
pdu = instance_double(CfdpPdu)
|
241
|
+
transaction_id = "1__123"
|
242
|
+
allow(pdu).to receive(:build_proxy_put_cancel_message).and_return("cancel_message")
|
243
|
+
allow(pdu).to receive(:build_originating_transaction_id_message).and_return("id_message")
|
244
|
+
|
245
|
+
# Mock proxy_request_setup
|
246
|
+
expect(@user).to receive(:proxy_request_setup).with(
|
247
|
+
{
|
248
|
+
remote_entity_id: @destination_entity_id,
|
249
|
+
transaction_id: transaction_id
|
250
|
+
}
|
251
|
+
).and_return([pdu, @destination_entity_id, []])
|
252
|
+
|
253
|
+
# Mock proxy_request_start
|
254
|
+
expect(@user).to receive(:proxy_request_start).with(
|
255
|
+
entity_id: @destination_entity_id,
|
256
|
+
messages_to_user: ["cancel_message", "id_message"]
|
257
|
+
).and_return("transaction")
|
258
|
+
|
259
|
+
# Call and verify
|
260
|
+
params = {
|
261
|
+
remote_entity_id: @destination_entity_id,
|
262
|
+
transaction_id: transaction_id
|
263
|
+
}
|
264
|
+
result = @user.cancel(params)
|
265
|
+
expect(result).to eq("transaction")
|
266
|
+
end
|
267
|
+
|
268
|
+
it "cancels a local transaction" do
|
269
|
+
# Setup a transaction in the MIB
|
270
|
+
transaction = double("transaction")
|
271
|
+
transaction_id = "1__123"
|
272
|
+
allow(transaction).to receive(:cancel).and_return(true)
|
273
|
+
CfdpMib.transactions[transaction_id] = transaction
|
274
|
+
|
275
|
+
# Call and verify
|
276
|
+
params = {
|
277
|
+
transaction_id: transaction_id
|
278
|
+
}
|
279
|
+
result = @user.cancel(params)
|
280
|
+
expect(result).to eq(transaction)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "returns nil when transaction not found" do
|
284
|
+
params = {
|
285
|
+
transaction_id: "1__999" # Does not exist
|
286
|
+
}
|
287
|
+
result = @user.cancel(params)
|
288
|
+
expect(result).to be_nil
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "suspend" do
|
293
|
+
it "suspends a remote transaction" do
|
294
|
+
# Setup
|
295
|
+
pdu = instance_double(CfdpPdu)
|
296
|
+
transaction_id = "1__123"
|
297
|
+
allow(pdu).to receive(:build_remote_suspend_request_message).and_return("suspend_message")
|
298
|
+
|
299
|
+
# Mock proxy_request_setup
|
300
|
+
expect(@user).to receive(:proxy_request_setup).with(
|
301
|
+
{
|
302
|
+
remote_entity_id: @destination_entity_id,
|
303
|
+
transaction_id: transaction_id
|
304
|
+
}
|
305
|
+
).and_return([pdu, @destination_entity_id, []])
|
306
|
+
|
307
|
+
# Mock proxy_request_start
|
308
|
+
expect(@user).to receive(:proxy_request_start).with(
|
309
|
+
entity_id: @destination_entity_id,
|
310
|
+
messages_to_user: ["suspend_message"]
|
311
|
+
).and_return("transaction")
|
312
|
+
|
313
|
+
# Call and verify
|
314
|
+
params = {
|
315
|
+
remote_entity_id: @destination_entity_id,
|
316
|
+
transaction_id: transaction_id
|
317
|
+
}
|
318
|
+
result = @user.suspend(params)
|
319
|
+
expect(result).to eq("transaction")
|
320
|
+
end
|
321
|
+
|
322
|
+
it "suspends a local transaction" do
|
323
|
+
# Setup a transaction in the MIB
|
324
|
+
transaction = double("transaction")
|
325
|
+
transaction_id = "1__123"
|
326
|
+
allow(transaction).to receive(:suspend).and_return(true)
|
327
|
+
CfdpMib.transactions[transaction_id] = transaction
|
328
|
+
|
329
|
+
# Call and verify
|
330
|
+
params = {
|
331
|
+
transaction_id: transaction_id
|
332
|
+
}
|
333
|
+
result = @user.suspend(params)
|
334
|
+
expect(result).to eq(transaction)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe "resume" do
|
339
|
+
it "resumes a remote transaction" do
|
340
|
+
# Setup
|
341
|
+
pdu = instance_double(CfdpPdu)
|
342
|
+
transaction_id = "1__123"
|
343
|
+
allow(pdu).to receive(:build_remote_resume_request_message).and_return("resume_message")
|
344
|
+
|
345
|
+
# Mock proxy_request_setup
|
346
|
+
expect(@user).to receive(:proxy_request_setup).with(
|
347
|
+
{
|
348
|
+
remote_entity_id: @destination_entity_id,
|
349
|
+
transaction_id: transaction_id
|
350
|
+
}
|
351
|
+
).and_return([pdu, @destination_entity_id, []])
|
352
|
+
|
353
|
+
# Mock proxy_request_start
|
354
|
+
expect(@user).to receive(:proxy_request_start).with(
|
355
|
+
entity_id: @destination_entity_id,
|
356
|
+
messages_to_user: ["resume_message"]
|
357
|
+
).and_return("transaction")
|
358
|
+
|
359
|
+
# Call and verify
|
360
|
+
params = {
|
361
|
+
remote_entity_id: @destination_entity_id,
|
362
|
+
transaction_id: transaction_id
|
363
|
+
}
|
364
|
+
result = @user.resume(params)
|
365
|
+
expect(result).to eq("transaction")
|
366
|
+
end
|
367
|
+
|
368
|
+
it "resumes a local transaction" do
|
369
|
+
# Setup a transaction in the MIB
|
370
|
+
transaction = double("transaction")
|
371
|
+
transaction_id = "1__123"
|
372
|
+
allow(transaction).to receive(:resume).and_return(true)
|
373
|
+
CfdpMib.transactions[transaction_id] = transaction
|
374
|
+
|
375
|
+
# Call and verify
|
376
|
+
params = {
|
377
|
+
transaction_id: transaction_id
|
378
|
+
}
|
379
|
+
result = @user.resume(params)
|
380
|
+
expect(result).to eq(transaction)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
describe "report" do
|
385
|
+
it "requests a report from a remote transaction" do
|
386
|
+
# Setup
|
387
|
+
pdu = instance_double(CfdpPdu)
|
388
|
+
transaction_id = "1__123"
|
389
|
+
allow(pdu).to receive(:build_remote_status_report_request_message).and_return("report_message")
|
390
|
+
|
391
|
+
# Mock proxy_request_setup
|
392
|
+
expect(@user).to receive(:proxy_request_setup).with(
|
393
|
+
{
|
394
|
+
remote_entity_id: @destination_entity_id,
|
395
|
+
transaction_id: transaction_id,
|
396
|
+
report_file_name: "report.txt"
|
397
|
+
}
|
398
|
+
).and_return([pdu, @destination_entity_id, []])
|
399
|
+
|
400
|
+
# Mock proxy_request_start
|
401
|
+
expect(@user).to receive(:proxy_request_start).with(
|
402
|
+
entity_id: @destination_entity_id,
|
403
|
+
messages_to_user: ["report_message"]
|
404
|
+
).and_return("transaction")
|
405
|
+
|
406
|
+
# Call and verify
|
407
|
+
params = {
|
408
|
+
remote_entity_id: @destination_entity_id,
|
409
|
+
transaction_id: transaction_id,
|
410
|
+
report_file_name: "report.txt"
|
411
|
+
}
|
412
|
+
result = @user.report(params)
|
413
|
+
expect(result).to eq("transaction")
|
414
|
+
end
|
415
|
+
|
416
|
+
it "raises an error if report_file_name is missing for remote reports" do
|
417
|
+
params = {
|
418
|
+
remote_entity_id: @destination_entity_id,
|
419
|
+
transaction_id: "1__123"
|
420
|
+
}
|
421
|
+
|
422
|
+
expect { @user.report(params) }.to raise_error(/report_file_name required/)
|
423
|
+
end
|
424
|
+
|
425
|
+
it "generates a report for a local transaction" do
|
426
|
+
# Setup a transaction in the MIB
|
427
|
+
transaction = double("transaction")
|
428
|
+
transaction_id = "1__123"
|
429
|
+
allow(transaction).to receive(:report).and_return(true)
|
430
|
+
CfdpMib.transactions[transaction_id] = transaction
|
431
|
+
|
432
|
+
# Call and verify
|
433
|
+
params = {
|
434
|
+
transaction_id: transaction_id
|
435
|
+
}
|
436
|
+
result = @user.report(params)
|
437
|
+
expect(result).to eq(transaction)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
describe "handle_messages_to_user" do
|
442
|
+
it "handles proxy put request message" do
|
443
|
+
# Mock start_source_transaction
|
444
|
+
expect(@user).to receive(:start_source_transaction).and_return("transaction")
|
445
|
+
|
446
|
+
# Call the method
|
447
|
+
metadata_pdu_hash = {
|
448
|
+
"SOURCE_ENTITY_ID" => @source_entity_id,
|
449
|
+
"SEQUENCE_NUMBER" => 123
|
450
|
+
}
|
451
|
+
messages_to_user = [
|
452
|
+
{
|
453
|
+
"MSG_TYPE" => "PROXY_PUT_REQUEST",
|
454
|
+
"DESTINATION_ENTITY_ID" => @destination_entity_id,
|
455
|
+
"SOURCE_FILE_NAME" => "source.txt",
|
456
|
+
"DESTINATION_FILE_NAME" => "dest.txt"
|
457
|
+
}
|
458
|
+
]
|
459
|
+
|
460
|
+
@user.handle_messages_to_user(metadata_pdu_hash, messages_to_user)
|
461
|
+
end
|
462
|
+
|
463
|
+
it "handles proxy put response message" do
|
464
|
+
# Expect CfdpTopic.write_indication to be called
|
465
|
+
expect(CfdpTopic).to receive(:write_indication).with('Proxy-Put-Response',
|
466
|
+
transaction_id: "1__123",
|
467
|
+
condition_code: "NO_ERROR",
|
468
|
+
file_status: "FILESTORE_SUCCESS",
|
469
|
+
delivery_code: "DATA_COMPLETE"
|
470
|
+
)
|
471
|
+
|
472
|
+
# Call the method
|
473
|
+
metadata_pdu_hash = {
|
474
|
+
"SOURCE_ENTITY_ID" => @source_entity_id,
|
475
|
+
"SEQUENCE_NUMBER" => 456
|
476
|
+
}
|
477
|
+
messages_to_user = [
|
478
|
+
{
|
479
|
+
"MSG_TYPE" => "ORIGINATING_TRANSACTION_ID",
|
480
|
+
"SOURCE_ENTITY_ID" => 1,
|
481
|
+
"SEQUENCE_NUMBER" => 123
|
482
|
+
},
|
483
|
+
{
|
484
|
+
"MSG_TYPE" => "PROXY_PUT_RESPONSE",
|
485
|
+
"CONDITION_CODE" => "NO_ERROR",
|
486
|
+
"DELIVERY_CODE" => "DATA_COMPLETE",
|
487
|
+
"FILE_STATUS" => "FILESTORE_SUCCESS"
|
488
|
+
}
|
489
|
+
]
|
490
|
+
|
491
|
+
@user.handle_messages_to_user(metadata_pdu_hash, messages_to_user)
|
492
|
+
end
|
493
|
+
|
494
|
+
it "handles directory listing request" do
|
495
|
+
# Mock CfdpMib.directory_listing
|
496
|
+
allow(CfdpMib).to receive(:directory_listing).and_return('{"result": "success"}')
|
497
|
+
|
498
|
+
# Mock start_source_transaction
|
499
|
+
expect(@user).to receive(:start_source_transaction).and_return("transaction")
|
500
|
+
|
501
|
+
# Call the method
|
502
|
+
metadata_pdu_hash = {
|
503
|
+
"SOURCE_ENTITY_ID" => @source_entity_id,
|
504
|
+
"SEQUENCE_NUMBER" => 123
|
505
|
+
}
|
506
|
+
messages_to_user = [
|
507
|
+
{
|
508
|
+
"MSG_TYPE" => "DIRECTORY_LISTING_REQUEST",
|
509
|
+
"DIRECTORY_NAME" => "/tmp",
|
510
|
+
"DIRECTORY_FILE_NAME" => "listing.txt"
|
511
|
+
}
|
512
|
+
]
|
513
|
+
|
514
|
+
@user.handle_messages_to_user(metadata_pdu_hash, messages_to_user)
|
515
|
+
end
|
516
|
+
|
517
|
+
it "handles unknown message types" do
|
518
|
+
# Mock logger
|
519
|
+
allow(OpenC3::Logger).to receive(:warn)
|
520
|
+
|
521
|
+
# Mock is_printable
|
522
|
+
allow(String).to receive(:instance_method).with(:is_printable?).and_return(proc { true })
|
523
|
+
|
524
|
+
# Call the method
|
525
|
+
metadata_pdu_hash = {
|
526
|
+
"SOURCE_ENTITY_ID" => @source_entity_id,
|
527
|
+
"SEQUENCE_NUMBER" => 123
|
528
|
+
}
|
529
|
+
messages_to_user = [
|
530
|
+
{
|
531
|
+
"MSG_TYPE" => "UNKNOWN",
|
532
|
+
"DATA" => "some data",
|
533
|
+
"MSG_TYPE_VALUE" => 99
|
534
|
+
}
|
535
|
+
]
|
536
|
+
|
537
|
+
@user.handle_messages_to_user(metadata_pdu_hash, messages_to_user)
|
538
|
+
|
539
|
+
# Verify logger was called
|
540
|
+
expect(OpenC3::Logger).to have_received(:warn)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
describe "stop" do
|
545
|
+
it "stops the @user thread and kills source threads" do
|
546
|
+
# Setup
|
547
|
+
thread = double("thread")
|
548
|
+
source_thread1 = double("source_thread1")
|
549
|
+
source_thread2 = double("source_thread2")
|
550
|
+
transaction = double("transaction")
|
551
|
+
|
552
|
+
allow(thread).to receive(:join)
|
553
|
+
allow(source_thread1).to receive(:alive?).and_return(true)
|
554
|
+
allow(source_thread1).to receive(:kill)
|
555
|
+
allow(source_thread2).to receive(:alive?).and_return(false)
|
556
|
+
allow(transaction).to receive(:abandon)
|
557
|
+
|
558
|
+
@user.instance_variable_set(:@thread, thread)
|
559
|
+
@user.instance_variable_set(:@source_threads, [source_thread1, source_thread2])
|
560
|
+
@user.instance_variable_set(:@source_transactions, [transaction])
|
561
|
+
|
562
|
+
# Allow sleep
|
563
|
+
allow(@user).to receive(:sleep)
|
564
|
+
|
565
|
+
# Call and verify
|
566
|
+
@user.stop
|
567
|
+
|
568
|
+
expect(@user.instance_variable_get(:@cancel_thread)).to be true
|
569
|
+
expect(transaction).to have_received(:abandon)
|
570
|
+
expect(thread).to have_received(:join)
|
571
|
+
expect(source_thread1).to have_received(:kill)
|
572
|
+
expect(@user.instance_variable_get(:@thread)).to be_nil
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openc3-cosmos-cfdp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Melton
|
8
8
|
- Jason Thomas
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: " This plugin provides COSMOS Support for CFDP\n"
|
15
15
|
email:
|
@@ -25,6 +25,8 @@ files:
|
|
25
25
|
- lib/cfdp.rb
|
26
26
|
- lib/cfdp_api.rb
|
27
27
|
- microservices/CFDP/Gemfile
|
28
|
+
- microservices/CFDP/Gemfile.dev
|
29
|
+
- microservices/CFDP/Gemfile.prod
|
28
30
|
- microservices/CFDP/Rakefile
|
29
31
|
- microservices/CFDP/app/controllers/application_controller.rb
|
30
32
|
- microservices/CFDP/app/controllers/cfdp_controller.rb
|
@@ -74,6 +76,9 @@ files:
|
|
74
76
|
- microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_tlv.rb
|
75
77
|
- microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_user_ops.rb
|
76
78
|
- microservices/CFDP/public/robots.txt
|
79
|
+
- microservices/CFDP/spec/controllers/cfdp_controller_spec.rb
|
80
|
+
- microservices/CFDP/spec/models/cfdp_crc_checksum_spec.rb
|
81
|
+
- microservices/CFDP/spec/models/cfdp_mib_spec.rb
|
77
82
|
- microservices/CFDP/spec/models/cfdp_pdu_ack_spec.rb
|
78
83
|
- microservices/CFDP/spec/models/cfdp_pdu_eof_spec.rb
|
79
84
|
- microservices/CFDP/spec/models/cfdp_pdu_file_data_spec.rb
|
@@ -83,6 +88,10 @@ files:
|
|
83
88
|
- microservices/CFDP/spec/models/cfdp_pdu_nak_spec.rb
|
84
89
|
- microservices/CFDP/spec/models/cfdp_pdu_prompt_spec.rb
|
85
90
|
- microservices/CFDP/spec/models/cfdp_pdu_spec.rb
|
91
|
+
- microservices/CFDP/spec/models/cfdp_pdu_user_ops_spec.rb
|
92
|
+
- microservices/CFDP/spec/models/cfdp_receive_transaction_spec.rb
|
93
|
+
- microservices/CFDP/spec/models/cfdp_transaction_spec.rb
|
94
|
+
- microservices/CFDP/spec/models/cfdp_user_spec.rb
|
86
95
|
- microservices/CFDP/spec/rails_helper.rb
|
87
96
|
- microservices/CFDP/spec/requests/cfdp_spec.rb
|
88
97
|
- microservices/CFDP/spec/spec_helper.rb
|
@@ -95,7 +104,7 @@ homepage: https://github.com/OpenC3/openc3
|
|
95
104
|
licenses:
|
96
105
|
- Nonstandard
|
97
106
|
metadata: {}
|
98
|
-
post_install_message:
|
107
|
+
post_install_message:
|
99
108
|
rdoc_options: []
|
100
109
|
require_paths:
|
101
110
|
- lib
|
@@ -111,8 +120,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
120
|
- !ruby/object:Gem::Version
|
112
121
|
version: '0'
|
113
122
|
requirements: []
|
114
|
-
rubygems_version: 3.
|
115
|
-
signing_key:
|
123
|
+
rubygems_version: 3.5.22
|
124
|
+
signing_key:
|
116
125
|
specification_version: 4
|
117
126
|
summary: OpenC3 COSMOS CFDP
|
118
127
|
test_files: []
|