1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2021 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "PrecompiledHeadersUnitTests.h"
35 #include <gtest/gtest.h>
36
37 #include "../../OrthancFramework/Sources/Compatibility.h"
38 #include "../../OrthancFramework/Sources/FileStorage/MemoryStorageArea.h"
39 #include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h"
40 #include "../../OrthancFramework/Sources/Logging.h"
41 #include "../../OrthancFramework/Sources/SerializationToolbox.h"
42
43 #include "../Sources/Database/SQLiteDatabaseWrapper.h"
44 #include "../Sources/ServerContext.h"
45 #include "../Sources/ServerJobs/LuaJobManager.h"
46 #include "../Sources/ServerJobs/OrthancJobUnserializer.h"
47
48 #include "../Sources/ServerJobs/Operations/DeleteResourceOperation.h"
49 #include "../Sources/ServerJobs/Operations/DicomInstanceOperationValue.h"
50 #include "../Sources/ServerJobs/Operations/ModifyInstanceOperation.h"
51 #include "../Sources/ServerJobs/Operations/StorePeerOperation.h"
52 #include "../Sources/ServerJobs/Operations/StoreScuOperation.h"
53 #include "../Sources/ServerJobs/Operations/SystemCallOperation.h"
54
55 #include "../Sources/ServerJobs/ArchiveJob.h"
56 #include "../Sources/ServerJobs/DicomModalityStoreJob.h"
57 #include "../Sources/ServerJobs/DicomMoveScuJob.h"
58 #include "../Sources/ServerJobs/MergeStudyJob.h"
59 #include "../Sources/ServerJobs/OrthancPeerStoreJob.h"
60 #include "../Sources/ServerJobs/ResourceModificationJob.h"
61 #include "../Sources/ServerJobs/SplitStudyJob.h"
62
63
64 using namespace Orthanc;
65
66 namespace
67 {
68 class DummyJob : public IJob
69 {
70 private:
71 bool fails_;
72 unsigned int count_;
73 unsigned int steps_;
74
75 public:
DummyJob()76 DummyJob() :
77 fails_(false),
78 count_(0),
79 steps_(4)
80 {
81 }
82
DummyJob(bool fails)83 explicit DummyJob(bool fails) :
84 fails_(fails),
85 count_(0),
86 steps_(4)
87 {
88 }
89
Start()90 virtual void Start() ORTHANC_OVERRIDE
91 {
92 }
93
Reset()94 virtual void Reset() ORTHANC_OVERRIDE
95 {
96 }
97
Step(const std::string & jobId)98 virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE
99 {
100 if (fails_)
101 {
102 return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL);
103 }
104 else if (count_ == steps_ - 1)
105 {
106 return JobStepResult::Success();
107 }
108 else
109 {
110 count_++;
111 return JobStepResult::Continue();
112 }
113 }
114
Stop(JobStopReason reason)115 virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
116 {
117 }
118
GetProgress()119 virtual float GetProgress() ORTHANC_OVERRIDE
120 {
121 return static_cast<float>(count_) / static_cast<float>(steps_ - 1);
122 }
123
GetJobType(std::string & type)124 virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE
125 {
126 type = "DummyJob";
127 }
128
Serialize(Json::Value & value)129 virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE
130 {
131 value = Json::objectValue;
132 value["Type"] = "DummyJob";
133 return true;
134 }
135
GetPublicContent(Json::Value & value)136 virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE
137 {
138 value["hello"] = "world";
139 }
140
GetOutput(std::string & output,MimeType & mime,const std::string & key)141 virtual bool GetOutput(std::string& output,
142 MimeType& mime,
143 const std::string& key) ORTHANC_OVERRIDE
144 {
145 return false;
146 }
147 };
148
149
150 class DummyInstancesJob : public SetOfInstancesJob
151 {
152 private:
153 bool trailingStepDone_;
154
155 protected:
HandleInstance(const std::string & instance)156 virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE
157 {
158 return (instance != "nope");
159 }
160
HandleTrailingStep()161 virtual bool HandleTrailingStep() ORTHANC_OVERRIDE
162 {
163 if (HasTrailingStep())
164 {
165 if (trailingStepDone_)
166 {
167 throw OrthancException(ErrorCode_InternalError);
168 }
169 else
170 {
171 trailingStepDone_ = true;
172 return true;
173 }
174 }
175 else
176 {
177 throw OrthancException(ErrorCode_InternalError);
178 }
179 }
180
181 public:
DummyInstancesJob()182 DummyInstancesJob() :
183 trailingStepDone_(false)
184 {
185 }
186
DummyInstancesJob(const Json::Value & value)187 DummyInstancesJob(const Json::Value& value) :
188 SetOfInstancesJob(value)
189 {
190 if (HasTrailingStep())
191 {
192 trailingStepDone_ = (GetPosition() == GetCommandsCount());
193 }
194 else
195 {
196 trailingStepDone_ = false;
197 }
198 }
199
IsTrailingStepDone() const200 bool IsTrailingStepDone() const
201 {
202 return trailingStepDone_;
203 }
204
Stop(JobStopReason reason)205 virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
206 {
207 }
208
GetJobType(std::string & s)209 virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE
210 {
211 s = "DummyInstancesJob";
212 }
213 };
214
215
216 class DummyUnserializer : public GenericJobUnserializer
217 {
218 public:
UnserializeJob(const Json::Value & value)219 virtual IJob* UnserializeJob(const Json::Value& value) ORTHANC_OVERRIDE
220 {
221 if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob")
222 {
223 return new DummyInstancesJob(value);
224 }
225 else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob")
226 {
227 return new DummyJob;
228 }
229 else
230 {
231 return GenericJobUnserializer::UnserializeJob(value);
232 }
233 }
234 };
235
236
237 class DynamicInteger : public IDynamicObject
238 {
239 private:
240 int value_;
241 std::set<int>& target_;
242
243 public:
DynamicInteger(int value,std::set<int> & target)244 DynamicInteger(int value, std::set<int>& target) :
245 value_(value), target_(target)
246 {
247 }
248
GetValue() const249 int GetValue() const
250 {
251 return value_;
252 }
253 };
254 }
255
256
TEST(JobsEngine,DISABLED_Lua)257 TEST(JobsEngine, DISABLED_Lua)
258 {
259 JobsEngine engine(10);
260 engine.SetThreadSleep(10);
261 engine.SetWorkersCount(2);
262 engine.Start();
263
264 LuaJobManager lua;
265 lua.SetMaxOperationsPerJob(5);
266 lua.SetTrailingOperationTimeout(200);
267
268 for (size_t i = 0; i < 30; i++)
269 {
270 boost::this_thread::sleep(boost::posix_time::milliseconds(150));
271
272 LuaJobManager::Lock lock(lua, engine);
273 size_t a = lock.AddLogOperation();
274 size_t b = lock.AddLogOperation();
275 size_t c = lock.AddSystemCallOperation("echo");
276 lock.AddStringInput(a, boost::lexical_cast<std::string>(i));
277 lock.AddNullInput(a);
278 lock.Connect(a, b);
279 lock.Connect(a, c);
280 }
281
282 boost::this_thread::sleep(boost::posix_time::milliseconds(2000));
283
284 engine.Stop();
285 }
286
287
CheckSameJson(const Json::Value & a,const Json::Value & b)288 static bool CheckSameJson(const Json::Value& a,
289 const Json::Value& b)
290 {
291 std::string s = a.toStyledString();
292 std::string t = b.toStyledString();
293
294 if (s == t)
295 {
296 return true;
297 }
298 else
299 {
300 LOG(ERROR) << "Expected serialization: " << s;
301 LOG(ERROR) << "Actual serialization: " << t;
302 return false;
303 }
304 }
305
306
CheckIdempotentSetOfInstances(IJobUnserializer & unserializer,SetOfInstancesJob & job)307 static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer,
308 SetOfInstancesJob& job)
309 {
310 Json::Value a = 42;
311
312 if (!job.Serialize(a))
313 {
314 return false;
315 }
316 else
317 {
318 std::unique_ptr<SetOfInstancesJob> unserialized
319 (dynamic_cast<SetOfInstancesJob*>(unserializer.UnserializeJob(a)));
320
321 Json::Value b = 43;
322 if (unserialized->Serialize(b))
323 {
324 return (CheckSameJson(a, b) &&
325 job.HasTrailingStep() == unserialized->HasTrailingStep() &&
326 job.GetPosition() == unserialized->GetPosition() &&
327 job.GetInstancesCount() == unserialized->GetInstancesCount() &&
328 job.GetCommandsCount() == unserialized->GetCommandsCount());
329 }
330 else
331 {
332 return false;
333 }
334 }
335 }
336
337
CheckIdempotentSerialization(IJobUnserializer & unserializer,IJobOperation & operation)338 static bool CheckIdempotentSerialization(IJobUnserializer& unserializer,
339 IJobOperation& operation)
340 {
341 Json::Value a = 42;
342 operation.Serialize(a);
343
344 std::unique_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a));
345
346 Json::Value b = 43;
347 unserialized->Serialize(b);
348
349 return CheckSameJson(a, b);
350 }
351
352
CheckIdempotentSerialization(IJobUnserializer & unserializer,IJobOperationValue & value)353 static bool CheckIdempotentSerialization(IJobUnserializer& unserializer,
354 IJobOperationValue& value)
355 {
356 Json::Value a = 42;
357 value.Serialize(a);
358
359 std::unique_ptr<IJobOperationValue> unserialized(unserializer.UnserializeValue(a));
360
361 Json::Value b = 43;
362 unserialized->Serialize(b);
363
364 return CheckSameJson(a, b);
365 }
366
367
TEST(JobsSerialization,GenericOperations)368 TEST(JobsSerialization, GenericOperations)
369 {
370 DummyUnserializer unserializer;
371 Json::Value s;
372
373 {
374 LogJobOperation operation;
375
376 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
377 operation.Serialize(s);
378 }
379
380 ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
381 ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
382
383 {
384 std::unique_ptr<IJobOperation> operation;
385 operation.reset(unserializer.UnserializeOperation(s));
386
387 // Make sure that we have indeed unserialized a log operation
388 Json::Value dummy;
389 ASSERT_THROW(dynamic_cast<DeleteResourceOperation&>(*operation).Serialize(dummy), std::bad_cast);
390 dynamic_cast<LogJobOperation&>(*operation).Serialize(dummy);
391 }
392 }
393
394
TEST(JobsSerialization,DicomInstanceOrigin)395 TEST(JobsSerialization, DicomInstanceOrigin)
396 {
397 Json::Value s;
398 std::string t;
399
400 {
401 DicomInstanceOrigin origin;
402
403 s = 42;
404 origin.Serialize(s);
405 }
406
407 {
408 DicomInstanceOrigin origin(s);
409 ASSERT_EQ(RequestOrigin_Unknown, origin.GetRequestOrigin());
410 ASSERT_EQ("", std::string(origin.GetRemoteAetC()));
411 ASSERT_FALSE(origin.LookupRemoteIp(t));
412 ASSERT_FALSE(origin.LookupRemoteAet(t));
413 ASSERT_FALSE(origin.LookupCalledAet(t));
414 ASSERT_FALSE(origin.LookupHttpUsername(t));
415 }
416
417 {
418 DicomInstanceOrigin origin(DicomInstanceOrigin::FromDicomProtocol("host", "aet", "called"));
419
420 s = 42;
421 origin.Serialize(s);
422 }
423
424 {
425 DicomInstanceOrigin origin(s);
426 ASSERT_EQ(RequestOrigin_DicomProtocol, origin.GetRequestOrigin());
427 ASSERT_EQ("aet", std::string(origin.GetRemoteAetC()));
428 ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t);
429 ASSERT_TRUE(origin.LookupRemoteAet(t)); ASSERT_EQ("aet", t);
430 ASSERT_TRUE(origin.LookupCalledAet(t)); ASSERT_EQ("called", t);
431 ASSERT_FALSE(origin.LookupHttpUsername(t));
432 }
433
434 {
435 DicomInstanceOrigin origin(DicomInstanceOrigin::FromHttp("host", "username"));
436
437 s = 42;
438 origin.Serialize(s);
439 }
440
441 {
442 DicomInstanceOrigin origin(s);
443 ASSERT_EQ(RequestOrigin_RestApi, origin.GetRequestOrigin());
444 ASSERT_EQ("", std::string(origin.GetRemoteAetC()));
445 ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t);
446 ASSERT_FALSE(origin.LookupRemoteAet(t));
447 ASSERT_FALSE(origin.LookupCalledAet(t));
448 ASSERT_TRUE(origin.LookupHttpUsername(t)); ASSERT_EQ("username", t);
449 }
450
451 {
452 DicomInstanceOrigin origin(DicomInstanceOrigin::FromLua());
453
454 s = 42;
455 origin.Serialize(s);
456 }
457
458 {
459 DicomInstanceOrigin origin(s);
460 ASSERT_EQ(RequestOrigin_Lua, origin.GetRequestOrigin());
461 ASSERT_FALSE(origin.LookupRemoteIp(t));
462 ASSERT_FALSE(origin.LookupRemoteAet(t));
463 ASSERT_FALSE(origin.LookupCalledAet(t));
464 ASSERT_FALSE(origin.LookupHttpUsername(t));
465 }
466
467 {
468 DicomInstanceOrigin origin(DicomInstanceOrigin::FromPlugins());
469
470 s = 42;
471 origin.Serialize(s);
472 }
473
474 {
475 DicomInstanceOrigin origin(s);
476 ASSERT_EQ(RequestOrigin_Plugins, origin.GetRequestOrigin());
477 ASSERT_FALSE(origin.LookupRemoteIp(t));
478 ASSERT_FALSE(origin.LookupRemoteAet(t));
479 ASSERT_FALSE(origin.LookupCalledAet(t));
480 ASSERT_FALSE(origin.LookupHttpUsername(t));
481 }
482
483 {
484 DicomInstanceOrigin origin(DicomInstanceOrigin::FromWebDav());
485
486 s = 42;
487 origin.Serialize(s);
488 }
489
490 {
491 DicomInstanceOrigin origin(s);
492 ASSERT_EQ(RequestOrigin_WebDav, origin.GetRequestOrigin());
493 ASSERT_EQ("", std::string(origin.GetRemoteAetC()));
494 ASSERT_FALSE(origin.LookupRemoteIp(t));
495 ASSERT_FALSE(origin.LookupRemoteAet(t));
496 ASSERT_FALSE(origin.LookupCalledAet(t));
497 ASSERT_FALSE(origin.LookupHttpUsername(t));
498 }
499 }
500
501
502 namespace
503 {
504 class OrthancJobsSerialization : public testing::Test
505 {
506 private:
507 MemoryStorageArea storage_;
508 SQLiteDatabaseWrapper db_; // The SQLite DB is in memory
509 std::unique_ptr<ServerContext> context_;
510
511 public:
OrthancJobsSerialization()512 OrthancJobsSerialization()
513 {
514 db_.Open();
515 context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10));
516 context_->SetupJobsEngine(true, false);
517 }
518
~OrthancJobsSerialization()519 virtual ~OrthancJobsSerialization() ORTHANC_OVERRIDE
520 {
521 context_->Stop();
522 context_.reset(NULL);
523 db_.Close();
524 }
525
GetContext()526 ServerContext& GetContext()
527 {
528 return *context_;
529 }
530
CreateInstance(std::string & id)531 bool CreateInstance(std::string& id)
532 {
533 // Create a sample DICOM file
534 ParsedDicomFile dicom(true);
535 dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"),
536 false, DicomReplaceMode_InsertIfAbsent, "");
537
538 std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(dicom));
539
540 return (context_->Store(id, *toStore, StoreInstanceMode_Default) == StoreStatus_Success);
541 }
542 };
543 }
544
545
TEST_F(OrthancJobsSerialization,Values)546 TEST_F(OrthancJobsSerialization, Values)
547 {
548 std::string id;
549 ASSERT_TRUE(CreateInstance(id));
550
551 Json::Value s;
552 OrthancJobUnserializer unserializer(GetContext());
553
554 {
555 DicomInstanceOperationValue instance(GetContext(), id);
556
557 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, instance));
558 instance.Serialize(s);
559 }
560
561 std::unique_ptr<IJobOperationValue> value;
562 value.reset(unserializer.UnserializeValue(s));
563 ASSERT_EQ(IJobOperationValue::Type_DicomInstance, value->GetType());
564 ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId());
565
566 {
567 std::string content;
568 dynamic_cast<DicomInstanceOperationValue&>(*value).ReadDicom(content);
569
570 ParsedDicomFile dicom(content);
571 ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME));
572 ASSERT_EQ("JODOGNE", content);
573 }
574 }
575
576
TEST_F(OrthancJobsSerialization,Operations)577 TEST_F(OrthancJobsSerialization, Operations)
578 {
579 std::string id;
580 ASSERT_TRUE(CreateInstance(id));
581
582 Json::Value s;
583 OrthancJobUnserializer unserializer(GetContext());
584
585 // DeleteResourceOperation
586
587 {
588 DeleteResourceOperation operation(GetContext());
589
590 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
591 operation.Serialize(s);
592 }
593
594 std::unique_ptr<IJobOperation> operation;
595
596 {
597 operation.reset(unserializer.UnserializeOperation(s));
598
599 Json::Value dummy;
600 ASSERT_THROW(dynamic_cast<LogJobOperation&>(*operation).Serialize(dummy), std::bad_cast);
601 dynamic_cast<DeleteResourceOperation&>(*operation).Serialize(dummy);
602 }
603
604 // StorePeerOperation
605
606 {
607 WebServiceParameters peer;
608 peer.SetUrl("http://localhost/");
609 peer.SetCredentials("username", "password");
610 peer.SetPkcs11Enabled(true);
611
612 StorePeerOperation operation(peer);
613
614 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
615 operation.Serialize(s);
616 }
617
618 {
619 operation.reset(unserializer.UnserializeOperation(s));
620
621 const StorePeerOperation& tmp = dynamic_cast<StorePeerOperation&>(*operation);
622 ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl());
623 ASSERT_EQ("username", tmp.GetPeer().GetUsername());
624 ASSERT_EQ("password", tmp.GetPeer().GetPassword());
625 ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled());
626 }
627
628 // StoreScuOperation
629
630 {
631 TimeoutDicomConnectionManager luaManager;
632
633 {
634 RemoteModalityParameters modality;
635 modality.SetApplicationEntityTitle("REMOTE");
636 modality.SetHost("192.168.1.1");
637 modality.SetPortNumber(1000);
638 modality.SetManufacturer(ModalityManufacturer_GE);
639
640 StoreScuOperation operation(GetContext(), luaManager, "TEST", modality);
641
642 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
643 operation.Serialize(s);
644 }
645
646 {
647 operation.reset(unserializer.UnserializeOperation(s));
648
649 const StoreScuOperation& tmp = dynamic_cast<StoreScuOperation&>(*operation);
650 ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle());
651 ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost());
652 ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber());
653 ASSERT_EQ(ModalityManufacturer_GE, tmp.GetRemoteModality().GetManufacturer());
654 ASSERT_EQ("TEST", tmp.GetLocalAet());
655 }
656 }
657
658 // SystemCallOperation
659
660 {
661 SystemCallOperation operation(std::string("echo"));
662 operation.AddPreArgument("a");
663 operation.AddPreArgument("b");
664 operation.AddPostArgument("c");
665
666 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
667 operation.Serialize(s);
668 }
669
670 {
671 operation.reset(unserializer.UnserializeOperation(s));
672
673 const SystemCallOperation& tmp = dynamic_cast<SystemCallOperation&>(*operation);
674 ASSERT_EQ("echo", tmp.GetCommand());
675 ASSERT_EQ(2u, tmp.GetPreArgumentsCount());
676 ASSERT_EQ(1u, tmp.GetPostArgumentsCount());
677 ASSERT_EQ("a", tmp.GetPreArgument(0));
678 ASSERT_EQ("b", tmp.GetPreArgument(1));
679 ASSERT_EQ("c", tmp.GetPostArgument(0));
680 }
681
682 // ModifyInstanceOperation
683
684 {
685 std::unique_ptr<DicomModification> modification(new DicomModification);
686 modification->SetupAnonymization(DicomVersion_2008);
687
688 ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release());
689
690 ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
691 operation.Serialize(s);
692 }
693
694 {
695 operation.reset(unserializer.UnserializeOperation(s));
696
697 const ModifyInstanceOperation& tmp = dynamic_cast<ModifyInstanceOperation&>(*operation);
698 ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin());
699 ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
700 }
701 }
702
703
TEST_F(OrthancJobsSerialization,Jobs)704 TEST_F(OrthancJobsSerialization, Jobs)
705 {
706 Json::Value s;
707
708 // ArchiveJob
709
710 {
711 ArchiveJob job(GetContext(), false, false);
712 ASSERT_FALSE(job.Serialize(s)); // Cannot serialize this
713 }
714
715 // DicomModalityStoreJob
716
717 OrthancJobUnserializer unserializer(GetContext());
718
719 {
720 RemoteModalityParameters modality;
721 modality.SetApplicationEntityTitle("REMOTE");
722 modality.SetHost("192.168.1.1");
723 modality.SetPortNumber(1000);
724 modality.SetManufacturer(ModalityManufacturer_GE);
725
726 DicomModalityStoreJob job(GetContext());
727 job.SetLocalAet("LOCAL");
728 job.SetRemoteModality(modality);
729 job.SetMoveOriginator("MOVESCU", 42);
730
731 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
732 ASSERT_TRUE(job.Serialize(s));
733 }
734
735 {
736 std::unique_ptr<IJob> job;
737 job.reset(unserializer.UnserializeJob(s));
738
739 DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job);
740 ASSERT_EQ("LOCAL", tmp.GetParameters().GetLocalApplicationEntityTitle());
741 ASSERT_EQ("REMOTE", tmp.GetParameters().GetRemoteModality().GetApplicationEntityTitle());
742 ASSERT_EQ("192.168.1.1", tmp.GetParameters().GetRemoteModality().GetHost());
743 ASSERT_EQ(1000, tmp.GetParameters().GetRemoteModality().GetPortNumber());
744 ASSERT_EQ(ModalityManufacturer_GE, tmp.GetParameters().GetRemoteModality().GetManufacturer());
745 ASSERT_TRUE(tmp.HasMoveOriginator());
746 ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet());
747 ASSERT_EQ(42, tmp.GetMoveOriginatorId());
748 }
749
750 // OrthancPeerStoreJob
751
752 {
753 WebServiceParameters peer;
754 peer.SetUrl("http://localhost/");
755 peer.SetCredentials("username", "password");
756 peer.SetPkcs11Enabled(true);
757
758 OrthancPeerStoreJob job(GetContext());
759 job.SetPeer(peer);
760
761 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
762 ASSERT_TRUE(job.Serialize(s));
763 }
764
765 {
766 std::unique_ptr<IJob> job;
767 job.reset(unserializer.UnserializeJob(s));
768
769 OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job);
770 ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl());
771 ASSERT_EQ("username", tmp.GetPeer().GetUsername());
772 ASSERT_EQ("password", tmp.GetPeer().GetPassword());
773 ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled());
774 ASSERT_FALSE(tmp.IsTranscode());
775 ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException);
776 }
777
778 {
779 OrthancPeerStoreJob job(GetContext());
780 ASSERT_THROW(job.SetTranscode("nope"), OrthancException);
781 job.SetTranscode("1.2.840.10008.1.2.4.50");
782
783 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
784 ASSERT_TRUE(job.Serialize(s));
785 }
786
787 {
788 std::unique_ptr<IJob> job;
789 job.reset(unserializer.UnserializeJob(s));
790
791 OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job);
792 ASSERT_EQ("http://127.0.0.1:8042/", tmp.GetPeer().GetUrl());
793 ASSERT_EQ("", tmp.GetPeer().GetUsername());
794 ASSERT_EQ("", tmp.GetPeer().GetPassword());
795 ASSERT_FALSE(tmp.GetPeer().IsPkcs11Enabled());
796 ASSERT_TRUE(tmp.IsTranscode());
797 ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax());
798 }
799
800 // ResourceModificationJob
801
802 {
803 std::unique_ptr<DicomModification> modification(new DicomModification);
804 modification->SetupAnonymization(DicomVersion_2008);
805
806 ResourceModificationJob job(GetContext());
807 job.SetModification(modification.release(), ResourceType_Patient, true);
808 job.SetOrigin(DicomInstanceOrigin::FromLua());
809
810 job.AddTrailingStep(); // Necessary since 1.7.0
811 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
812 ASSERT_TRUE(job.Serialize(s));
813 }
814
815 {
816 std::unique_ptr<IJob> job;
817 job.reset(unserializer.UnserializeJob(s));
818
819 ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
820 ASSERT_TRUE(tmp.IsAnonymization());
821 ASSERT_FALSE(tmp.IsTranscode());
822 ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException);
823 ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
824 ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
825 }
826
827 {
828 ResourceModificationJob job(GetContext());
829 ASSERT_THROW(job.SetTranscode("nope"), OrthancException);
830 job.SetTranscode(DicomTransferSyntax_JPEGProcess1);
831
832 job.AddTrailingStep(); // Necessary since 1.7.0
833 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
834 ASSERT_TRUE(job.Serialize(s));
835 }
836
837 {
838 std::unique_ptr<IJob> job;
839 job.reset(unserializer.UnserializeJob(s));
840
841 ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
842 ASSERT_FALSE(tmp.IsAnonymization());
843 ASSERT_TRUE(tmp.IsTranscode());
844 ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax());
845 ASSERT_EQ(RequestOrigin_Unknown, tmp.GetOrigin().GetRequestOrigin());
846 }
847
848 // SplitStudyJob
849
850 std::string instance;
851 ASSERT_TRUE(CreateInstance(instance));
852
853 std::string study, series;
854
855 {
856 ServerContext::DicomCacheLocker lock(GetContext(), instance);
857 study = lock.GetDicom().GetHasher().HashStudy();
858 series = lock.GetDicom().GetHasher().HashSeries();
859 }
860
861 {
862 std::list<std::string> tmp;
863 GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study);
864 ASSERT_EQ(1u, tmp.size());
865 ASSERT_EQ(study, tmp.front());
866 GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series);
867 ASSERT_EQ(1u, tmp.size());
868 ASSERT_EQ(series, tmp.front());
869 }
870
871 std::string study2;
872
873 {
874 std::string a, b;
875
876 {
877 ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException);
878
879 SplitStudyJob job(GetContext(), study);
880 job.SetKeepSource(true);
881 job.AddSourceSeries(series);
882 ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException);
883 job.SetOrigin(DicomInstanceOrigin::FromLua());
884 job.Replace(DICOM_TAG_PATIENT_NAME, "hello");
885 job.Remove(DICOM_TAG_PATIENT_BIRTH_DATE);
886 ASSERT_THROW(job.Replace(DICOM_TAG_SERIES_DESCRIPTION, "nope"), OrthancException);
887 ASSERT_THROW(job.Remove(DICOM_TAG_SERIES_DESCRIPTION), OrthancException);
888
889 ASSERT_TRUE(job.GetTargetStudy().empty());
890 a = job.GetTargetStudyUid();
891 ASSERT_TRUE(job.LookupTargetSeriesUid(b, series));
892
893 job.AddTrailingStep();
894 job.Start();
895 ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
896 ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
897
898 study2 = job.GetTargetStudy();
899 ASSERT_FALSE(study2.empty());
900
901 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
902 ASSERT_TRUE(job.Serialize(s));
903 }
904
905 {
906 std::unique_ptr<IJob> job;
907 job.reset(unserializer.UnserializeJob(s));
908
909 SplitStudyJob& tmp = dynamic_cast<SplitStudyJob&>(*job);
910 ASSERT_TRUE(tmp.IsKeepSource());
911 ASSERT_EQ(study, tmp.GetSourceStudy());
912 ASSERT_EQ(a, tmp.GetTargetStudyUid());
913 ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
914
915 std::string s;
916 ASSERT_EQ(study2, tmp.GetTargetStudy());
917 ASSERT_FALSE(tmp.LookupTargetSeriesUid(s, "nope"));
918 ASSERT_TRUE(tmp.LookupTargetSeriesUid(s, series));
919 ASSERT_EQ(b, s);
920
921 ASSERT_FALSE(tmp.LookupReplacement(s, DICOM_TAG_STUDY_DESCRIPTION));
922 ASSERT_TRUE(tmp.LookupReplacement(s, DICOM_TAG_PATIENT_NAME));
923 ASSERT_EQ("hello", s);
924 ASSERT_FALSE(tmp.IsRemoved(DICOM_TAG_PATIENT_NAME));
925 ASSERT_TRUE(tmp.IsRemoved(DICOM_TAG_PATIENT_BIRTH_DATE));
926 }
927 }
928
929 {
930 std::list<std::string> tmp;
931 GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study);
932 ASSERT_EQ(2u, tmp.size());
933 GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series);
934 ASSERT_EQ(2u, tmp.size());
935 }
936
937 // MergeStudyJob
938
939 {
940 ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException);
941
942 MergeStudyJob job(GetContext(), study);
943 job.SetKeepSource(true);
944 job.AddSource(study2);
945 ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException);
946 ASSERT_THROW(job.AddSourceStudy("nope"), OrthancException);
947 ASSERT_THROW(job.AddSource("nope"), OrthancException);
948 job.SetOrigin(DicomInstanceOrigin::FromLua());
949
950 ASSERT_EQ(job.GetTargetStudy(), study);
951
952 job.AddTrailingStep();
953 job.Start();
954 ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
955 ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
956
957 ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
958 ASSERT_TRUE(job.Serialize(s));
959 }
960
961 {
962 std::list<std::string> tmp;
963 GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study);
964 ASSERT_EQ(2u, tmp.size());
965 GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series);
966 ASSERT_EQ(3u, tmp.size());
967 }
968
969 {
970 std::unique_ptr<IJob> job;
971 job.reset(unserializer.UnserializeJob(s));
972
973 MergeStudyJob& tmp = dynamic_cast<MergeStudyJob&>(*job);
974 ASSERT_TRUE(tmp.IsKeepSource());
975 ASSERT_EQ(study, tmp.GetTargetStudy());
976 ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
977 }
978 }
979
980
981
TEST_F(OrthancJobsSerialization,DicomAssociationParameters)982 TEST_F(OrthancJobsSerialization, DicomAssociationParameters)
983 {
984 Json::Value v;
985
986 {
987 v = Json::objectValue;
988 DicomAssociationParameters p;
989 p.SerializeJob(v);
990 }
991
992 {
993 DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v);
994 ASSERT_EQ("ORTHANC", p.GetLocalApplicationEntityTitle());
995 ASSERT_EQ("ANY-SCP", p.GetRemoteModality().GetApplicationEntityTitle());
996 ASSERT_EQ(104u, p.GetRemoteModality().GetPortNumber());
997 ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer());
998 ASSERT_EQ("127.0.0.1", p.GetRemoteModality().GetHost());
999 ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), p.GetTimeout());
1000 }
1001
1002 {
1003 v = Json::objectValue;
1004 DicomAssociationParameters p;
1005 p.SetLocalApplicationEntityTitle("HELLO");
1006 p.SetRemoteApplicationEntityTitle("WORLD");
1007 p.SetRemotePort(42);
1008 p.SetRemoteHost("MY_HOST");
1009 p.SetTimeout(43);
1010 p.SerializeJob(v);
1011 }
1012
1013 {
1014 DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v);
1015 ASSERT_EQ("HELLO", p.GetLocalApplicationEntityTitle());
1016 ASSERT_EQ("WORLD", p.GetRemoteModality().GetApplicationEntityTitle());
1017 ASSERT_EQ(42u, p.GetRemoteModality().GetPortNumber());
1018 ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer());
1019 ASSERT_EQ("MY_HOST", p.GetRemoteModality().GetHost());
1020 ASSERT_EQ(43u, p.GetTimeout());
1021 }
1022
1023 {
1024 DicomModalityStoreJob job(GetContext());
1025 job.Serialize(v);
1026 }
1027
1028 {
1029 OrthancJobUnserializer unserializer(GetContext());
1030 std::unique_ptr<DicomModalityStoreJob> job(
1031 dynamic_cast<DicomModalityStoreJob*>(unserializer.UnserializeJob(v)));
1032 ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle());
1033 ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
1034 ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost());
1035 ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber());
1036 ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
1037 ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout());
1038 ASSERT_FALSE(job->HasMoveOriginator());
1039 ASSERT_THROW(job->GetMoveOriginatorAet(), OrthancException);
1040 ASSERT_THROW(job->GetMoveOriginatorId(), OrthancException);
1041 ASSERT_FALSE(job->HasStorageCommitment());
1042 }
1043
1044 {
1045 RemoteModalityParameters r;
1046 r.SetApplicationEntityTitle("HELLO");
1047 r.SetPortNumber(42);
1048 r.SetHost("MY_HOST");
1049
1050 DicomModalityStoreJob job(GetContext());
1051 job.SetLocalAet("WORLD");
1052 job.SetRemoteModality(r);
1053 job.SetTimeout(43);
1054 job.SetMoveOriginator("ORIGINATOR", 100);
1055 job.EnableStorageCommitment(true);
1056 job.Serialize(v);
1057 }
1058
1059 {
1060 OrthancJobUnserializer unserializer(GetContext());
1061 std::unique_ptr<DicomModalityStoreJob> job(
1062 dynamic_cast<DicomModalityStoreJob*>(unserializer.UnserializeJob(v)));
1063 ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle());
1064 ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
1065 ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost());
1066 ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber());
1067 ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
1068 ASSERT_EQ(43u, job->GetParameters().GetTimeout());
1069 ASSERT_TRUE(job->HasMoveOriginator());
1070 ASSERT_EQ("ORIGINATOR", job->GetMoveOriginatorAet());
1071 ASSERT_EQ(100, job->GetMoveOriginatorId());
1072 ASSERT_TRUE(job->HasStorageCommitment());
1073 }
1074
1075 {
1076 DicomMoveScuJob job(GetContext());
1077 job.Serialize(v);
1078 }
1079
1080 {
1081 OrthancJobUnserializer unserializer(GetContext());
1082 std::unique_ptr<DicomMoveScuJob> job(
1083 dynamic_cast<DicomMoveScuJob*>(unserializer.UnserializeJob(v)));
1084 ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle());
1085 ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
1086 ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost());
1087 ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber());
1088 ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
1089 ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout());
1090 }
1091
1092 {
1093 RemoteModalityParameters r;
1094 r.SetApplicationEntityTitle("HELLO");
1095 r.SetPortNumber(42);
1096 r.SetHost("MY_HOST");
1097
1098 DicomMoveScuJob job(GetContext());
1099 job.SetLocalAet("WORLD");
1100 job.SetRemoteModality(r);
1101 job.SetTimeout(43);
1102 job.Serialize(v);
1103 }
1104
1105 {
1106 OrthancJobUnserializer unserializer(GetContext());
1107 std::unique_ptr<DicomMoveScuJob> job(
1108 dynamic_cast<DicomMoveScuJob*>(unserializer.UnserializeJob(v)));
1109 ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle());
1110 ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
1111 ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost());
1112 ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber());
1113 ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
1114 ASSERT_EQ(43u, job->GetParameters().GetTimeout());
1115 }
1116 }
1117