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 Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
24 // Must be the first to be sure to use the Orthanc framework shared library
25 #  include <OrthancFramework.h>
26 #endif
27 
28 #include <gtest/gtest.h>
29 
30 #include "../../OrthancFramework/Sources/Compatibility.h"
31 #include "../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h"
32 #include "../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h"
33 #include "../../OrthancFramework/Sources/DicomParsing/DicomModification.h"
34 #include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
35 #include "../../OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h"
36 #include "../../OrthancFramework/Sources/JobsEngine/JobsEngine.h"
37 #include "../../OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h"
38 #include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h"
39 #include "../../OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h"
40 #include "../../OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h"
41 #include "../../OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h"
42 #include "../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h"
43 #include "../../OrthancFramework/Sources/Logging.h"
44 #include "../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h"
45 #include "../../OrthancFramework/Sources/OrthancException.h"
46 #include "../../OrthancFramework/Sources/SerializationToolbox.h"
47 
48 
49 using namespace Orthanc;
50 
51 namespace
52 {
53   class DummyJob : public IJob
54   {
55   private:
56     bool         fails_;
57     unsigned int count_;
58     unsigned int steps_;
59 
60   public:
DummyJob()61     DummyJob() :
62       fails_(false),
63       count_(0),
64       steps_(4)
65     {
66     }
67 
DummyJob(bool fails)68     explicit DummyJob(bool fails) :
69       fails_(fails),
70       count_(0),
71       steps_(4)
72     {
73     }
74 
Start()75     virtual void Start() ORTHANC_OVERRIDE
76     {
77     }
78 
Reset()79     virtual void Reset() ORTHANC_OVERRIDE
80     {
81     }
82 
Step(const std::string & jobId)83     virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE
84     {
85       if (fails_)
86       {
87         return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL);
88       }
89       else if (count_ == steps_ - 1)
90       {
91         return JobStepResult::Success();
92       }
93       else
94       {
95         count_++;
96         return JobStepResult::Continue();
97       }
98     }
99 
Stop(JobStopReason reason)100     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
101     {
102     }
103 
GetProgress()104     virtual float GetProgress() ORTHANC_OVERRIDE
105     {
106       return static_cast<float>(count_) / static_cast<float>(steps_ - 1);
107     }
108 
GetJobType(std::string & type)109     virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE
110     {
111       type = "DummyJob";
112     }
113 
Serialize(Json::Value & value)114     virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE
115     {
116       value = Json::objectValue;
117       value["Type"] = "DummyJob";
118       return true;
119     }
120 
GetPublicContent(Json::Value & value)121     virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE
122     {
123       value["hello"] = "world";
124     }
125 
GetOutput(std::string & output,MimeType & mime,const std::string & key)126     virtual bool GetOutput(std::string& output,
127                            MimeType& mime,
128                            const std::string& key) ORTHANC_OVERRIDE
129     {
130       return false;
131     }
132   };
133 
134 
135   class DummyInstancesJob : public SetOfInstancesJob
136   {
137   private:
138     bool   trailingStepDone_;
139 
140   protected:
HandleInstance(const std::string & instance)141     virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE
142     {
143       return (instance != "nope");
144     }
145 
HandleTrailingStep()146     virtual bool HandleTrailingStep() ORTHANC_OVERRIDE
147     {
148       if (HasTrailingStep())
149       {
150         if (trailingStepDone_)
151         {
152           throw OrthancException(ErrorCode_InternalError);
153         }
154         else
155         {
156           trailingStepDone_ = true;
157           return true;
158         }
159       }
160       else
161       {
162         throw OrthancException(ErrorCode_InternalError);
163       }
164     }
165 
166   public:
DummyInstancesJob()167     DummyInstancesJob() :
168       trailingStepDone_(false)
169     {
170     }
171 
DummyInstancesJob(const Json::Value & value)172     explicit DummyInstancesJob(const Json::Value& value) :
173       SetOfInstancesJob(value)
174     {
175       if (HasTrailingStep())
176       {
177         trailingStepDone_ = (GetPosition() == GetCommandsCount());
178       }
179       else
180       {
181         trailingStepDone_ = false;
182       }
183     }
184 
IsTrailingStepDone() const185     bool IsTrailingStepDone() const
186     {
187       return trailingStepDone_;
188     }
189 
Stop(JobStopReason reason)190     virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
191     {
192     }
193 
GetJobType(std::string & s)194     virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE
195     {
196       s = "DummyInstancesJob";
197     }
198   };
199 
200 
201   class DummyUnserializer : public GenericJobUnserializer
202   {
203   public:
UnserializeJob(const Json::Value & value)204     virtual IJob* UnserializeJob(const Json::Value& value) ORTHANC_OVERRIDE
205     {
206       if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob")
207       {
208         return new DummyInstancesJob(value);
209       }
210       else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob")
211       {
212         return new DummyJob;
213       }
214       else
215       {
216         return GenericJobUnserializer::UnserializeJob(value);
217       }
218     }
219   };
220 
221 
222   class DynamicInteger : public IDynamicObject
223   {
224   private:
225     int value_;
226     std::set<int>& target_;
227 
228   public:
DynamicInteger(int value,std::set<int> & target)229     DynamicInteger(int value, std::set<int>& target) :
230       value_(value), target_(target)
231     {
232     }
233 
GetValue() const234     int GetValue() const
235     {
236       return value_;
237     }
238   };
239 }
240 
241 
TEST(MultiThreading,SharedMessageQueueBasic)242 TEST(MultiThreading, SharedMessageQueueBasic)
243 {
244   std::set<int> s;
245 
246   SharedMessageQueue q;
247   ASSERT_TRUE(q.WaitEmpty(0));
248   q.Enqueue(new DynamicInteger(10, s));
249   ASSERT_FALSE(q.WaitEmpty(1));
250   q.Enqueue(new DynamicInteger(20, s));
251   q.Enqueue(new DynamicInteger(30, s));
252   q.Enqueue(new DynamicInteger(40, s));
253 
254   std::unique_ptr<DynamicInteger> i;
255   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue());
256   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue());
257   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue());
258   ASSERT_FALSE(q.WaitEmpty(1));
259   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue());
260   ASSERT_TRUE(q.WaitEmpty(0));
261   ASSERT_EQ(NULL, q.Dequeue(1));
262 }
263 
264 
TEST(MultiThreading,SharedMessageQueueClean)265 TEST(MultiThreading, SharedMessageQueueClean)
266 {
267   std::set<int> s;
268 
269   try
270   {
271     SharedMessageQueue q;
272     q.Enqueue(new DynamicInteger(10, s));
273     q.Enqueue(new DynamicInteger(20, s));
274     throw OrthancException(ErrorCode_InternalError);
275   }
276   catch (OrthancException&)
277   {
278   }
279 }
280 
281 
282 
283 
CheckState(JobsRegistry & registry,const std::string & id,JobState state)284 static bool CheckState(JobsRegistry& registry,
285                        const std::string& id,
286                        JobState state)
287 {
288   JobState s;
289   if (registry.GetState(s, id))
290   {
291     return state == s;
292   }
293   else
294   {
295     return false;
296   }
297 }
298 
299 
CheckErrorCode(JobsRegistry & registry,const std::string & id,ErrorCode code)300 static bool CheckErrorCode(JobsRegistry& registry,
301                            const std::string& id,
302                            ErrorCode code)
303 {
304   JobInfo s;
305   if (registry.GetJobInfo(s, id))
306   {
307     return code == s.GetStatus().GetErrorCode();
308   }
309   else
310   {
311     return false;
312   }
313 }
314 
315 
TEST(JobsRegistry,Priority)316 TEST(JobsRegistry, Priority)
317 {
318   JobsRegistry registry(10);
319 
320   std::string i1, i2, i3, i4;
321   registry.Submit(i1, new DummyJob(), 10);
322   registry.Submit(i2, new DummyJob(), 30);
323   registry.Submit(i3, new DummyJob(), 20);
324   registry.Submit(i4, new DummyJob(), 5);
325 
326   registry.SetMaxCompletedJobs(2);
327 
328   std::set<std::string> id;
329   registry.ListJobs(id);
330 
331   ASSERT_EQ(4u, id.size());
332   ASSERT_TRUE(id.find(i1) != id.end());
333   ASSERT_TRUE(id.find(i2) != id.end());
334   ASSERT_TRUE(id.find(i3) != id.end());
335   ASSERT_TRUE(id.find(i4) != id.end());
336 
337   ASSERT_TRUE(CheckState(registry, i2, JobState_Pending));
338 
339   {
340     JobsRegistry::RunningJob job(registry, 0);
341     ASSERT_TRUE(job.IsValid());
342     ASSERT_EQ(30, job.GetPriority());
343     ASSERT_EQ(i2, job.GetId());
344 
345     ASSERT_TRUE(CheckState(registry, i2, JobState_Running));
346   }
347 
348   ASSERT_TRUE(CheckState(registry, i2, JobState_Failure));
349   ASSERT_TRUE(CheckState(registry, i3, JobState_Pending));
350 
351   {
352     JobsRegistry::RunningJob job(registry, 0);
353     ASSERT_TRUE(job.IsValid());
354     ASSERT_EQ(20, job.GetPriority());
355     ASSERT_EQ(i3, job.GetId());
356 
357     job.MarkSuccess();
358 
359     ASSERT_TRUE(CheckState(registry, i3, JobState_Running));
360   }
361 
362   ASSERT_TRUE(CheckState(registry, i3, JobState_Success));
363 
364   {
365     JobsRegistry::RunningJob job(registry, 0);
366     ASSERT_TRUE(job.IsValid());
367     ASSERT_EQ(10, job.GetPriority());
368     ASSERT_EQ(i1, job.GetId());
369   }
370 
371   {
372     JobsRegistry::RunningJob job(registry, 0);
373     ASSERT_TRUE(job.IsValid());
374     ASSERT_EQ(5, job.GetPriority());
375     ASSERT_EQ(i4, job.GetId());
376   }
377 
378   {
379     JobsRegistry::RunningJob job(registry, 1);
380     ASSERT_FALSE(job.IsValid());
381   }
382 
383   JobState s;
384   ASSERT_TRUE(registry.GetState(s, i1));
385   ASSERT_FALSE(registry.GetState(s, i2));  // Removed because oldest
386   ASSERT_FALSE(registry.GetState(s, i3));  // Removed because second oldest
387   ASSERT_TRUE(registry.GetState(s, i4));
388 
389   registry.SetMaxCompletedJobs(1);  // (*)
390   ASSERT_FALSE(registry.GetState(s, i1));  // Just discarded by (*)
391   ASSERT_TRUE(registry.GetState(s, i4));
392 }
393 
394 
TEST(JobsRegistry,Simultaneous)395 TEST(JobsRegistry, Simultaneous)
396 {
397   JobsRegistry registry(10);
398 
399   std::string i1, i2;
400   registry.Submit(i1, new DummyJob(), 20);
401   registry.Submit(i2, new DummyJob(), 10);
402 
403   ASSERT_TRUE(CheckState(registry, i1, JobState_Pending));
404   ASSERT_TRUE(CheckState(registry, i2, JobState_Pending));
405 
406   {
407     JobsRegistry::RunningJob job1(registry, 0);
408     JobsRegistry::RunningJob job2(registry, 0);
409 
410     ASSERT_TRUE(job1.IsValid());
411     ASSERT_TRUE(job2.IsValid());
412 
413     job1.MarkFailure();
414     job2.MarkSuccess();
415 
416     ASSERT_TRUE(CheckState(registry, i1, JobState_Running));
417     ASSERT_TRUE(CheckState(registry, i2, JobState_Running));
418   }
419 
420   ASSERT_TRUE(CheckState(registry, i1, JobState_Failure));
421   ASSERT_TRUE(CheckState(registry, i2, JobState_Success));
422 }
423 
424 
TEST(JobsRegistry,Resubmit)425 TEST(JobsRegistry, Resubmit)
426 {
427   JobsRegistry registry(10);
428 
429   std::string id;
430   registry.Submit(id, new DummyJob(), 10);
431 
432   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
433 
434   registry.Resubmit(id);
435   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
436 
437   {
438     JobsRegistry::RunningJob job(registry, 0);
439     ASSERT_TRUE(job.IsValid());
440     job.MarkFailure();
441 
442     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
443 
444     registry.Resubmit(id);
445     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
446   }
447 
448   ASSERT_TRUE(CheckState(registry, id, JobState_Failure));
449 
450   registry.Resubmit(id);
451   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
452 
453   {
454     JobsRegistry::RunningJob job(registry, 0);
455     ASSERT_TRUE(job.IsValid());
456     ASSERT_EQ(id, job.GetId());
457 
458     job.MarkSuccess();
459     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
460   }
461 
462   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
463 
464   registry.Resubmit(id);
465   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
466 }
467 
468 
TEST(JobsRegistry,Retry)469 TEST(JobsRegistry, Retry)
470 {
471   JobsRegistry registry(10);
472 
473   std::string id;
474   registry.Submit(id, new DummyJob(), 10);
475 
476   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
477 
478   {
479     JobsRegistry::RunningJob job(registry, 0);
480     ASSERT_TRUE(job.IsValid());
481     job.MarkRetry(0);
482 
483     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
484   }
485 
486   ASSERT_TRUE(CheckState(registry, id, JobState_Retry));
487 
488   registry.Resubmit(id);
489   ASSERT_TRUE(CheckState(registry, id, JobState_Retry));
490 
491   registry.ScheduleRetries();
492   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
493 
494   {
495     JobsRegistry::RunningJob job(registry, 0);
496     ASSERT_TRUE(job.IsValid());
497     job.MarkSuccess();
498 
499     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
500   }
501 
502   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
503 }
504 
505 
TEST(JobsRegistry,PausePending)506 TEST(JobsRegistry, PausePending)
507 {
508   JobsRegistry registry(10);
509 
510   std::string id;
511   registry.Submit(id, new DummyJob(), 10);
512 
513   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
514 
515   registry.Pause(id);
516   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
517 
518   registry.Pause(id);
519   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
520 
521   registry.Resubmit(id);
522   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
523 
524   registry.Resume(id);
525   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
526 }
527 
528 
TEST(JobsRegistry,PauseRunning)529 TEST(JobsRegistry, PauseRunning)
530 {
531   JobsRegistry registry(10);
532 
533   std::string id;
534   registry.Submit(id, new DummyJob(), 10);
535 
536   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
537 
538   {
539     JobsRegistry::RunningJob job(registry, 0);
540     ASSERT_TRUE(job.IsValid());
541 
542     registry.Resubmit(id);
543     job.MarkPause();
544     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
545   }
546 
547   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
548 
549   registry.Resubmit(id);
550   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
551 
552   registry.Resume(id);
553   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
554 
555   {
556     JobsRegistry::RunningJob job(registry, 0);
557     ASSERT_TRUE(job.IsValid());
558 
559     job.MarkSuccess();
560     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
561   }
562 
563   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
564 }
565 
566 
TEST(JobsRegistry,PauseRetry)567 TEST(JobsRegistry, PauseRetry)
568 {
569   JobsRegistry registry(10);
570 
571   std::string id;
572   registry.Submit(id, new DummyJob(), 10);
573 
574   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
575 
576   {
577     JobsRegistry::RunningJob job(registry, 0);
578     ASSERT_TRUE(job.IsValid());
579 
580     job.MarkRetry(0);
581     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
582   }
583 
584   ASSERT_TRUE(CheckState(registry, id, JobState_Retry));
585 
586   registry.Pause(id);
587   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
588 
589   registry.Resume(id);
590   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
591 
592   {
593     JobsRegistry::RunningJob job(registry, 0);
594     ASSERT_TRUE(job.IsValid());
595 
596     job.MarkSuccess();
597     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
598   }
599 
600   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
601 }
602 
603 
TEST(JobsRegistry,Cancel)604 TEST(JobsRegistry, Cancel)
605 {
606   JobsRegistry registry(10);
607 
608   std::string id;
609   registry.Submit(id, new DummyJob(), 10);
610 
611   ASSERT_FALSE(registry.Cancel("nope"));
612 
613   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
614   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
615 
616   ASSERT_TRUE(registry.Cancel(id));
617   ASSERT_TRUE(CheckState(registry, id, JobState_Failure));
618   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
619 
620   ASSERT_TRUE(registry.Cancel(id));
621   ASSERT_TRUE(CheckState(registry, id, JobState_Failure));
622   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
623 
624   ASSERT_TRUE(registry.Resubmit(id));
625   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
626   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
627 
628   {
629     JobsRegistry::RunningJob job(registry, 0);
630     ASSERT_TRUE(job.IsValid());
631 
632     ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
633 
634     job.MarkSuccess();
635     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
636   }
637 
638   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
639   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
640 
641   ASSERT_TRUE(registry.Cancel(id));
642   ASSERT_TRUE(CheckState(registry, id, JobState_Success));
643   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
644 
645   registry.Submit(id, new DummyJob(), 10);
646 
647   {
648     JobsRegistry::RunningJob job(registry, 0);
649     ASSERT_TRUE(job.IsValid());
650     ASSERT_EQ(id, job.GetId());
651 
652     ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
653     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
654 
655     job.MarkCanceled();
656   }
657 
658   ASSERT_TRUE(CheckState(registry, id, JobState_Failure));
659   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
660 
661   ASSERT_TRUE(registry.Resubmit(id));
662   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
663   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
664 
665   ASSERT_TRUE(registry.Pause(id));
666   ASSERT_TRUE(CheckState(registry, id, JobState_Paused));
667   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
668 
669   ASSERT_TRUE(registry.Cancel(id));
670   ASSERT_TRUE(CheckState(registry, id, JobState_Failure));
671   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
672 
673   ASSERT_TRUE(registry.Resubmit(id));
674   ASSERT_TRUE(CheckState(registry, id, JobState_Pending));
675   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
676 
677   {
678     JobsRegistry::RunningJob job(registry, 0);
679     ASSERT_TRUE(job.IsValid());
680     ASSERT_EQ(id, job.GetId());
681 
682     ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
683     ASSERT_TRUE(CheckState(registry, id, JobState_Running));
684 
685     job.MarkRetry(500);
686   }
687 
688   ASSERT_TRUE(CheckState(registry, id, JobState_Retry));
689   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success));
690 
691   ASSERT_TRUE(registry.Cancel(id));
692   ASSERT_TRUE(CheckState(registry, id, JobState_Failure));
693   ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob));
694 }
695 
696 
697 
TEST(JobsEngine,SubmitAndWait)698 TEST(JobsEngine, SubmitAndWait)
699 {
700   JobsEngine engine(10);
701   engine.SetThreadSleep(10);
702   engine.SetWorkersCount(3);
703   engine.Start();
704 
705   Json::Value content = Json::nullValue;
706   engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10);
707   ASSERT_EQ(Json::objectValue, content.type());
708   ASSERT_EQ("world", content["hello"].asString());
709 
710   content = Json::nullValue;
711   ASSERT_THROW(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10), OrthancException);
712   ASSERT_EQ(Json::nullValue, content.type());
713 
714   engine.Stop();
715 }
716 
717 
TEST(JobsEngine,DISABLED_SequenceOfOperationsJob)718 TEST(JobsEngine, DISABLED_SequenceOfOperationsJob)
719 {
720   JobsEngine engine(10);
721   engine.SetThreadSleep(10);
722   engine.SetWorkersCount(3);
723   engine.Start();
724 
725   std::string id;
726   SequenceOfOperationsJob* job = NULL;
727 
728   {
729     std::unique_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob);
730     job = a.get();
731     engine.GetRegistry().Submit(id, a.release(), 0);
732   }
733 
734   boost::this_thread::sleep(boost::posix_time::milliseconds(500));
735 
736   {
737     SequenceOfOperationsJob::Lock lock(*job);
738     size_t i = lock.AddOperation(new LogJobOperation);
739     size_t j = lock.AddOperation(new LogJobOperation);
740     size_t k = lock.AddOperation(new LogJobOperation);
741 
742     StringOperationValue a("Hello");
743     StringOperationValue b("World");
744     lock.AddInput(i, a);
745     lock.AddInput(i, b);
746 
747     lock.Connect(i, j);
748     lock.Connect(j, k);
749   }
750 
751   boost::this_thread::sleep(boost::posix_time::milliseconds(2000));
752 
753   engine.Stop();
754 
755 }
756 
757 
CheckSameJson(const Json::Value & a,const Json::Value & b)758 static bool CheckSameJson(const Json::Value& a,
759                           const Json::Value& b)
760 {
761   std::string s = a.toStyledString();
762   std::string t = b.toStyledString();
763 
764   if (s == t)
765   {
766     return true;
767   }
768   else
769   {
770     LOG(ERROR) << "Expected serialization: " << s;
771     LOG(ERROR) << "Actual serialization: " << t;
772     return false;
773   }
774 }
775 
776 
CheckIdempotentSerialization(IJobUnserializer & unserializer,IJob & job)777 static bool CheckIdempotentSerialization(IJobUnserializer& unserializer,
778                                          IJob& job)
779 {
780   Json::Value a = 42;
781 
782   if (!job.Serialize(a))
783   {
784     return false;
785   }
786   else
787   {
788     std::unique_ptr<IJob> unserialized(unserializer.UnserializeJob(a));
789 
790     Json::Value b = 43;
791     if (unserialized->Serialize(b))
792     {
793       return (CheckSameJson(a, b));
794     }
795     else
796     {
797       return false;
798     }
799   }
800 }
801 
802 
CheckIdempotentSetOfInstances(IJobUnserializer & unserializer,SetOfInstancesJob & job)803 static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer,
804                                           SetOfInstancesJob& job)
805 {
806   Json::Value a = 42;
807 
808   if (!job.Serialize(a))
809   {
810     return false;
811   }
812   else
813   {
814     std::unique_ptr<SetOfInstancesJob> unserialized
815       (dynamic_cast<SetOfInstancesJob*>(unserializer.UnserializeJob(a)));
816 
817     Json::Value b = 43;
818     if (unserialized->Serialize(b))
819     {
820       return (CheckSameJson(a, b) &&
821               job.HasTrailingStep() == unserialized->HasTrailingStep() &&
822               job.GetPosition() == unserialized->GetPosition() &&
823               job.GetInstancesCount() == unserialized->GetInstancesCount() &&
824               job.GetCommandsCount() == unserialized->GetCommandsCount());
825     }
826     else
827     {
828       return false;
829     }
830   }
831 }
832 
833 
CheckIdempotentSerialization(IJobUnserializer & unserializer,const IJobOperationValue & value)834 static bool CheckIdempotentSerialization(IJobUnserializer& unserializer,
835                                          const IJobOperationValue& value)
836 {
837   Json::Value a = 42;
838   value.Serialize(a);
839 
840   std::unique_ptr<IJobOperationValue> unserialized(unserializer.UnserializeValue(a));
841 
842   Json::Value b = 43;
843   unserialized->Serialize(b);
844 
845   return CheckSameJson(a, b);
846 }
847 
848 
TEST(JobsSerialization,BadFileFormat)849 TEST(JobsSerialization, BadFileFormat)
850 {
851   GenericJobUnserializer unserializer;
852 
853   Json::Value s;
854 
855   s = Json::objectValue;
856   ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
857   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
858   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
859 
860   s = Json::arrayValue;
861   ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
862   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
863   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
864 
865   s = "hello";
866   ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
867   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
868   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
869 
870   s = 42;
871   ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
872   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
873   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
874 }
875 
876 
TEST(JobsSerialization,JobOperationValues)877 TEST(JobsSerialization, JobOperationValues)
878 {
879   Json::Value s;
880 
881   {
882     JobOperationValues values;
883     values.Append(new NullOperationValue);
884     values.Append(new StringOperationValue("hello"));
885     values.Append(new StringOperationValue("world"));
886 
887     s = 42;
888     values.Serialize(s);
889   }
890 
891   {
892     GenericJobUnserializer unserializer;
893     std::unique_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s));
894     ASSERT_EQ(3u, values->GetSize());
895     ASSERT_EQ(IJobOperationValue::Type_Null, values->GetValue(0).GetType());
896     ASSERT_EQ(IJobOperationValue::Type_String, values->GetValue(1).GetType());
897     ASSERT_EQ(IJobOperationValue::Type_String, values->GetValue(2).GetType());
898 
899     ASSERT_EQ("hello", dynamic_cast<const StringOperationValue&>(values->GetValue(1)).GetContent());
900     ASSERT_EQ("world", dynamic_cast<const StringOperationValue&>(values->GetValue(2)).GetContent());
901   }
902 }
903 
904 
TEST(JobsSerialization,GenericValues)905 TEST(JobsSerialization, GenericValues)
906 {
907   GenericJobUnserializer unserializer;
908   Json::Value s;
909 
910   {
911     NullOperationValue null;
912 
913     ASSERT_TRUE(CheckIdempotentSerialization(unserializer, null));
914     null.Serialize(s);
915   }
916 
917   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
918   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
919 
920   std::unique_ptr<IJobOperationValue> value;
921   value.reset(unserializer.UnserializeValue(s));
922 
923   ASSERT_EQ(IJobOperationValue::Type_Null, value->GetType());
924 
925   {
926     StringOperationValue str("Hello");
927 
928     ASSERT_TRUE(CheckIdempotentSerialization(unserializer, str));
929     str.Serialize(s);
930   }
931 
932   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
933   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
934   value.reset(unserializer.UnserializeValue(s));
935 
936   ASSERT_EQ(IJobOperationValue::Type_String, value->GetType());
937   ASSERT_EQ("Hello", dynamic_cast<StringOperationValue&>(*value).GetContent());
938 }
939 
940 
TEST(JobsSerialization,GenericJobs)941 TEST(JobsSerialization, GenericJobs)
942 {
943   Json::Value s;
944 
945   // This tests SetOfInstancesJob
946 
947   {
948     DummyInstancesJob job;
949     job.SetDescription("description");
950     job.AddInstance("hello");
951     job.AddInstance("nope");
952     job.AddInstance("world");
953     job.SetPermissive(true);
954     ASSERT_THROW(job.Step("jobId"), OrthancException);  // Not started yet
955     ASSERT_FALSE(job.HasTrailingStep());
956     ASSERT_FALSE(job.IsTrailingStepDone());
957     job.Start();
958     ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
959     ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
960 
961     {
962       DummyUnserializer unserializer;
963       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
964     }
965 
966     ASSERT_TRUE(job.Serialize(s));
967   }
968 
969   {
970     DummyUnserializer unserializer;
971     ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
972     ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
973 
974     std::unique_ptr<IJob> job;
975     job.reset(unserializer.UnserializeJob(s));
976 
977     const DummyInstancesJob& tmp = dynamic_cast<const DummyInstancesJob&>(*job);
978     ASSERT_FALSE(tmp.IsStarted());
979     ASSERT_TRUE(tmp.IsPermissive());
980     ASSERT_EQ("description", tmp.GetDescription());
981     ASSERT_EQ(3u, tmp.GetInstancesCount());
982     ASSERT_EQ(2u, tmp.GetPosition());
983     ASSERT_EQ(1u, tmp.GetFailedInstances().size());
984     ASSERT_EQ("hello", tmp.GetInstance(0));
985     ASSERT_EQ("nope", tmp.GetInstance(1));
986     ASSERT_EQ("world", tmp.GetInstance(2));
987     ASSERT_TRUE(tmp.IsFailedInstance("nope"));
988   }
989 
990   // SequenceOfOperationsJob
991 
992   {
993     SequenceOfOperationsJob job;
994     job.SetDescription("hello");
995 
996     {
997       SequenceOfOperationsJob::Lock lock(job);
998       size_t a = lock.AddOperation(new LogJobOperation);
999       size_t b = lock.AddOperation(new LogJobOperation);
1000       lock.Connect(a, b);
1001 
1002       StringOperationValue s1("hello");
1003       StringOperationValue s2("world");
1004       lock.AddInput(a, s1);
1005       lock.AddInput(a, s2);
1006       lock.SetTrailingOperationTimeout(300);
1007     }
1008 
1009     ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
1010 
1011     {
1012       GenericJobUnserializer unserializer;
1013       ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job));
1014     }
1015 
1016     ASSERT_TRUE(job.Serialize(s));
1017   }
1018 
1019   {
1020     GenericJobUnserializer unserializer;
1021     ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
1022     ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
1023 
1024     std::unique_ptr<IJob> job;
1025     job.reset(unserializer.UnserializeJob(s));
1026 
1027     std::string tmp;
1028     dynamic_cast<SequenceOfOperationsJob&>(*job).GetDescription(tmp);
1029     ASSERT_EQ("hello", tmp);
1030   }
1031 }
1032 
1033 
IsSameTagValue(const ParsedDicomFile & dicom1,const ParsedDicomFile & dicom2,DicomTag tag)1034 static bool IsSameTagValue(const ParsedDicomFile& dicom1,
1035                            const ParsedDicomFile& dicom2,
1036                            DicomTag tag)
1037 {
1038   std::string a, b;
1039   return (dicom1.GetTagValue(a, tag) &&
1040           dicom2.GetTagValue(b, tag) &&
1041           (a == b));
1042 }
1043 
1044 
1045 
TEST(JobsSerialization,DicomModification)1046 TEST(JobsSerialization, DicomModification)
1047 {
1048   Json::Value s;
1049 
1050   ParsedDicomFile source(true);
1051   source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, "");
1052   source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, "");
1053   source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, "");
1054 
1055   std::unique_ptr<ParsedDicomFile> modified(source.Clone(true));
1056 
1057   {
1058     DicomModification modification;
1059     modification.SetLevel(ResourceType_Series);
1060     modification.Clear(DICOM_TAG_STUDY_DESCRIPTION);
1061     modification.Remove(DICOM_TAG_SERIES_DESCRIPTION);
1062     modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true);
1063 
1064     modification.Apply(*modified);
1065 
1066     s = 42;
1067     modification.Serialize(s);
1068   }
1069 
1070   {
1071     DicomModification modification(s);
1072     ASSERT_EQ(ResourceType_Series, modification.GetLevel());
1073 
1074     std::unique_ptr<ParsedDicomFile> second(source.Clone(true));
1075     modification.Apply(*second);
1076 
1077     std::string t;
1078     ASSERT_TRUE(second->GetTagValue(t, DICOM_TAG_STUDY_DESCRIPTION));
1079     ASSERT_TRUE(t.empty());
1080     ASSERT_FALSE(second->GetTagValue(t, DICOM_TAG_SERIES_DESCRIPTION));
1081     ASSERT_TRUE(second->GetTagValue(t, DICOM_TAG_PATIENT_NAME));
1082     ASSERT_EQ("Test 4", t);
1083 
1084     ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID));
1085     ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID));
1086 
1087     ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID));
1088     ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID));
1089   }
1090 }
1091 
1092 
TEST(JobsSerialization,DicomModification2)1093 TEST(JobsSerialization, DicomModification2)
1094 {
1095   Json::Value s;
1096 
1097   {
1098     DicomModification modification;
1099     modification.SetupAnonymization(DicomVersion_2017c);
1100     modification.Remove(DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 1, DICOM_TAG_SOP_INSTANCE_UID));
1101     modification.Replace(DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 1, DICOM_TAG_SOP_CLASS_UID), "Hello", true);
1102     modification.Keep(DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 1, DICOM_TAG_PATIENT_NAME));
1103 
1104     s = 42;
1105     modification.Serialize(s);
1106   }
1107 
1108   {
1109     DicomModification modification(s);
1110 
1111     // Check idempotent serialization
1112     Json::Value ss;
1113     modification.Serialize(ss);
1114     ASSERT_EQ(s.toStyledString(), ss.toStyledString());
1115   }
1116 }
1117 
1118 
TEST(JobsSerialization,Registry)1119 TEST(JobsSerialization, Registry)
1120 {
1121   Json::Value s;
1122   std::string i1, i2;
1123 
1124   {
1125     JobsRegistry registry(10);
1126     registry.Submit(i1, new DummyJob(), 10);
1127     registry.Submit(i2, new SequenceOfOperationsJob(), 30);
1128     registry.Serialize(s);
1129   }
1130 
1131   {
1132     DummyUnserializer unserializer;
1133     JobsRegistry registry(unserializer, s, 10);
1134 
1135     Json::Value t;
1136     registry.Serialize(t);
1137     ASSERT_TRUE(CheckSameJson(s, t));
1138   }
1139 }
1140 
1141 
TEST(JobsSerialization,TrailingStep)1142 TEST(JobsSerialization, TrailingStep)
1143 {
1144   {
1145     Json::Value s;
1146 
1147     DummyInstancesJob job;
1148     ASSERT_EQ(0u, job.GetCommandsCount());
1149     ASSERT_EQ(0u, job.GetInstancesCount());
1150 
1151     job.Start();
1152     ASSERT_EQ(0u, job.GetPosition());
1153     ASSERT_FALSE(job.HasTrailingStep());
1154     ASSERT_FALSE(job.IsTrailingStepDone());
1155 
1156     {
1157       DummyUnserializer unserializer;
1158       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1159     }
1160 
1161     ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
1162     ASSERT_EQ(1u, job.GetPosition());
1163     ASSERT_FALSE(job.IsTrailingStepDone());
1164 
1165     {
1166       DummyUnserializer unserializer;
1167       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1168     }
1169 
1170     ASSERT_THROW(job.Step("jobId"), OrthancException);
1171   }
1172 
1173   {
1174     Json::Value s;
1175 
1176     DummyInstancesJob job;
1177     job.AddInstance("hello");
1178     job.AddInstance("world");
1179     ASSERT_EQ(2u, job.GetCommandsCount());
1180     ASSERT_EQ(2u, job.GetInstancesCount());
1181 
1182     job.Start();
1183     ASSERT_EQ(0u, job.GetPosition());
1184     ASSERT_FALSE(job.HasTrailingStep());
1185     ASSERT_FALSE(job.IsTrailingStepDone());
1186 
1187     {
1188       DummyUnserializer unserializer;
1189       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1190     }
1191 
1192     ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
1193     ASSERT_EQ(1u, job.GetPosition());
1194     ASSERT_FALSE(job.IsTrailingStepDone());
1195 
1196     {
1197       DummyUnserializer unserializer;
1198       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1199     }
1200 
1201     ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
1202     ASSERT_EQ(2u, job.GetPosition());
1203     ASSERT_FALSE(job.IsTrailingStepDone());
1204 
1205     {
1206       DummyUnserializer unserializer;
1207       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1208     }
1209 
1210     ASSERT_THROW(job.Step("jobId"), OrthancException);
1211   }
1212 
1213   {
1214     Json::Value s;
1215 
1216     DummyInstancesJob job;
1217     ASSERT_EQ(0u, job.GetInstancesCount());
1218     ASSERT_EQ(0u, job.GetCommandsCount());
1219     job.AddTrailingStep();
1220     ASSERT_EQ(0u, job.GetInstancesCount());
1221     ASSERT_EQ(1u, job.GetCommandsCount());
1222 
1223     job.Start(); // This adds the trailing step
1224     ASSERT_EQ(0u, job.GetPosition());
1225     ASSERT_TRUE(job.HasTrailingStep());
1226     ASSERT_FALSE(job.IsTrailingStepDone());
1227 
1228     {
1229       DummyUnserializer unserializer;
1230       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1231     }
1232 
1233     ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
1234     ASSERT_EQ(1u, job.GetPosition());
1235     ASSERT_TRUE(job.IsTrailingStepDone());
1236 
1237     {
1238       DummyUnserializer unserializer;
1239       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1240     }
1241 
1242     ASSERT_THROW(job.Step("jobId"), OrthancException);
1243   }
1244 
1245   {
1246     Json::Value s;
1247 
1248     DummyInstancesJob job;
1249     job.AddInstance("hello");
1250     ASSERT_EQ(1u, job.GetInstancesCount());
1251     ASSERT_EQ(1u, job.GetCommandsCount());
1252     job.AddTrailingStep();
1253     ASSERT_EQ(1u, job.GetInstancesCount());
1254     ASSERT_EQ(2u, job.GetCommandsCount());
1255 
1256     job.Start();
1257     ASSERT_EQ(2u, job.GetCommandsCount());
1258     ASSERT_EQ(0u, job.GetPosition());
1259     ASSERT_TRUE(job.HasTrailingStep());
1260     ASSERT_FALSE(job.IsTrailingStepDone());
1261 
1262     {
1263       DummyUnserializer unserializer;
1264       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1265     }
1266 
1267     ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
1268     ASSERT_EQ(1u, job.GetPosition());
1269     ASSERT_FALSE(job.IsTrailingStepDone());
1270 
1271     {
1272       DummyUnserializer unserializer;
1273       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1274     }
1275 
1276     ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
1277     ASSERT_EQ(2u, job.GetPosition());
1278     ASSERT_TRUE(job.IsTrailingStepDone());
1279 
1280     {
1281       DummyUnserializer unserializer;
1282       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
1283     }
1284 
1285     ASSERT_THROW(job.Step("jobId"), OrthancException);
1286   }
1287 }
1288 
1289 
TEST(JobsSerialization,RemoteModalityParameters)1290 TEST(JobsSerialization, RemoteModalityParameters)
1291 {
1292   Json::Value s;
1293 
1294   {
1295     RemoteModalityParameters modality;
1296     ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
1297     modality.Serialize(s, false);
1298     ASSERT_EQ(Json::arrayValue, s.type());
1299     ASSERT_FALSE(modality.IsDicomTlsEnabled());
1300     ASSERT_FALSE(modality.HasTimeout());
1301     ASSERT_EQ(0u, modality.GetTimeout());
1302   }
1303 
1304   {
1305     RemoteModalityParameters modality(s);
1306     ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
1307     ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle());
1308     ASSERT_EQ("127.0.0.1", modality.GetHost());
1309     ASSERT_EQ(104u, modality.GetPortNumber());
1310     ASSERT_EQ(ModalityManufacturer_Generic, modality.GetManufacturer());
1311     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo));
1312     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find));
1313     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get));
1314     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store));
1315     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move));
1316     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
1317     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
1318     ASSERT_TRUE(modality.IsTranscodingAllowed());
1319     ASSERT_FALSE(modality.IsDicomTlsEnabled());
1320     ASSERT_FALSE(modality.HasLocalAet());
1321     ASSERT_THROW(modality.GetLocalAet(), OrthancException);
1322     ASSERT_FALSE(modality.HasTimeout());
1323     ASSERT_EQ(0u, modality.GetTimeout());
1324   }
1325 
1326   s = Json::nullValue;
1327 
1328   {
1329     RemoteModalityParameters modality;
1330     ASSERT_THROW(modality.SetPortNumber(0), OrthancException);
1331     ASSERT_THROW(modality.SetPortNumber(65535), OrthancException);
1332     modality.SetApplicationEntityTitle("HELLO");
1333     modality.SetHost("world");
1334     modality.SetPortNumber(45);
1335     modality.SetManufacturer(ModalityManufacturer_GenericNoWildcardInDates);
1336     ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
1337     modality.Serialize(s, true);
1338     ASSERT_EQ(Json::objectValue, s.type());
1339     ASSERT_FALSE(modality.HasLocalAet());
1340     ASSERT_FALSE(modality.HasTimeout());
1341     ASSERT_EQ(0u, modality.GetTimeout());
1342   }
1343 
1344   {
1345     RemoteModalityParameters modality(s);
1346     ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle());
1347     ASSERT_EQ("world", modality.GetHost());
1348     ASSERT_EQ(45u, modality.GetPortNumber());
1349     ASSERT_EQ(ModalityManufacturer_GenericNoWildcardInDates, modality.GetManufacturer());
1350     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo));
1351     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find));
1352     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get));
1353     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store));
1354     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move));
1355     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
1356     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
1357     ASSERT_TRUE(modality.IsTranscodingAllowed());
1358     ASSERT_FALSE(modality.IsDicomTlsEnabled());
1359     ASSERT_FALSE(modality.HasLocalAet());
1360     ASSERT_FALSE(modality.HasTimeout());
1361     ASSERT_EQ(0u, modality.GetTimeout());
1362   }
1363 
1364   s["Port"] = "46";
1365 
1366   {
1367     RemoteModalityParameters modality(s);
1368     ASSERT_EQ(46u, modality.GetPortNumber());
1369   }
1370 
1371   s["Port"] = -1;     ASSERT_THROW(RemoteModalityParameters m(s), OrthancException);
1372   s["Port"] = 65535;  ASSERT_THROW(RemoteModalityParameters m(s), OrthancException);
1373   s["Port"] = "nope"; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException);
1374 
1375   std::set<DicomRequestType> operations;
1376   operations.insert(DicomRequestType_Echo);
1377   operations.insert(DicomRequestType_Find);
1378   operations.insert(DicomRequestType_Get);
1379   operations.insert(DicomRequestType_Move);
1380   operations.insert(DicomRequestType_Store);
1381   operations.insert(DicomRequestType_NAction);
1382   operations.insert(DicomRequestType_NEventReport);
1383 
1384   ASSERT_EQ(7u, operations.size());
1385 
1386   for (std::set<DicomRequestType>::const_iterator
1387          it = operations.begin(); it != operations.end(); ++it)
1388   {
1389     {
1390       RemoteModalityParameters modality;
1391       modality.SetRequestAllowed(*it, false);
1392       ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
1393 
1394       modality.Serialize(s, false);
1395       ASSERT_EQ(Json::objectValue, s.type());
1396     }
1397 
1398     {
1399       RemoteModalityParameters modality(s);
1400 
1401       ASSERT_FALSE(modality.IsRequestAllowed(*it));
1402 
1403       for (std::set<DicomRequestType>::const_iterator
1404              it2 = operations.begin(); it2 != operations.end(); ++it2)
1405       {
1406         if (*it2 != *it)
1407         {
1408           ASSERT_TRUE(modality.IsRequestAllowed(*it2));
1409         }
1410       }
1411     }
1412   }
1413 
1414   s = Json::nullValue;
1415 
1416   {
1417     RemoteModalityParameters modality;
1418     modality.SetLocalAet("hello");
1419     modality.SetTimeout(42);
1420     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
1421     modality.Serialize(s, true);
1422     ASSERT_EQ(Json::objectValue, s.type());
1423     ASSERT_TRUE(modality.HasLocalAet());
1424     ASSERT_TRUE(modality.HasTimeout());
1425     ASSERT_EQ(42u, modality.GetTimeout());
1426   }
1427 
1428   {
1429     RemoteModalityParameters modality(s);
1430     ASSERT_TRUE(modality.HasLocalAet());
1431     ASSERT_EQ("hello", modality.GetLocalAet());
1432     ASSERT_TRUE(modality.HasTimeout());
1433     ASSERT_EQ(42u, modality.GetTimeout());
1434   }
1435 
1436   {
1437     Json::Value t;
1438     t["AllowStorageCommitment"] = false;
1439     t["AET"] = "AET";
1440     t["Host"] = "host";
1441     t["Port"] = "104";
1442 
1443     RemoteModalityParameters modality(t);
1444     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
1445     ASSERT_EQ("AET", modality.GetApplicationEntityTitle());
1446     ASSERT_EQ("host", modality.GetHost());
1447     ASSERT_EQ(104u, modality.GetPortNumber());
1448     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
1449     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
1450     ASSERT_TRUE(modality.IsTranscodingAllowed());
1451     ASSERT_FALSE(modality.IsDicomTlsEnabled());
1452     ASSERT_FALSE(modality.HasLocalAet());
1453     ASSERT_THROW(modality.GetLocalAet(), OrthancException);
1454     ASSERT_FALSE(modality.HasTimeout());
1455     ASSERT_EQ(0u, modality.GetTimeout());
1456   }
1457 
1458   {
1459     Json::Value t;
1460     t["AllowNAction"] = false;
1461     t["AllowNEventReport"] = true;
1462     t["AET"] = "AET";
1463     t["Host"] = "host";
1464     t["Port"] = "104";
1465     t["AllowTranscoding"] = false;
1466     t["UseDicomTls"] = true;
1467     t["LocalAet"] = "world";
1468     t["Timeout"] = 20;
1469 
1470     RemoteModalityParameters modality(t);
1471     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
1472     ASSERT_EQ("AET", modality.GetApplicationEntityTitle());
1473     ASSERT_EQ("host", modality.GetHost());
1474     ASSERT_EQ(104u, modality.GetPortNumber());
1475     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
1476     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
1477     ASSERT_FALSE(modality.IsTranscodingAllowed());
1478     ASSERT_TRUE(modality.IsDicomTlsEnabled());
1479     ASSERT_TRUE(modality.HasLocalAet());
1480     ASSERT_EQ("world", modality.GetLocalAet());
1481     ASSERT_TRUE(modality.HasTimeout());
1482     ASSERT_EQ(20u, modality.GetTimeout());
1483   }
1484 
1485   {
1486     Json::Value t;
1487     t["AllowNAction"] = true;
1488     t["AllowNEventReport"] = true;
1489     t["AET"] = "AET";
1490     t["Host"] = "host";
1491     t["Port"] = "104";
1492 
1493     RemoteModalityParameters modality(t);
1494     ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
1495     ASSERT_EQ("AET", modality.GetApplicationEntityTitle());
1496     ASSERT_EQ("host", modality.GetHost());
1497     ASSERT_EQ(104u, modality.GetPortNumber());
1498     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
1499     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
1500     ASSERT_TRUE(modality.IsTranscodingAllowed());
1501     ASSERT_FALSE(modality.IsDicomTlsEnabled());
1502     ASSERT_FALSE(modality.HasLocalAet());
1503     ASSERT_THROW(modality.GetLocalAet(), OrthancException);
1504   }
1505 }
1506 
1507 
1508 
TEST(JobsSerialization,DicomAssociationParameters)1509 TEST(JobsSerialization, DicomAssociationParameters)
1510 {
1511   {
1512     DicomAssociationParameters a;
1513 
1514     Json::Value v = Json::objectValue;
1515     a.SerializeJob(v);
1516     ASSERT_EQ(Json::objectValue, v.type());
1517     ASSERT_EQ("ORTHANC", v["LocalAet"].asString());
1518     ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), v["Timeout"].asUInt());
1519     ASSERT_TRUE(v.isMember("Remote"));
1520     ASSERT_TRUE(v.isMember("MaximumPduLength"));
1521 
1522     ASSERT_EQ(5u, v.getMemberNames().size());
1523 
1524     DicomAssociationParameters b;
1525     b.UnserializeJob(v);
1526     ASSERT_EQ("ANY-SCP", b.GetRemoteModality().GetApplicationEntityTitle());
1527     ASSERT_EQ("127.0.0.1", b.GetRemoteModality().GetHost());
1528     ASSERT_EQ(104u, b.GetRemoteModality().GetPortNumber());
1529     ASSERT_EQ("ORTHANC", b.GetLocalApplicationEntityTitle());
1530     ASSERT_EQ(DicomAssociationParameters::GetDefaultMaximumPduLength(), b.GetMaximumPduLength());
1531     ASSERT_FALSE(b.GetRemoteModality().IsDicomTlsEnabled());
1532     ASSERT_FALSE(b.GetRemoteModality().HasLocalAet());
1533     ASSERT_THROW(b.GetRemoteModality().GetLocalAet(), OrthancException);
1534     ASSERT_FALSE(b.GetRemoteModality().HasTimeout());
1535     ASSERT_EQ(0u, b.GetRemoteModality().GetTimeout());
1536     ASSERT_TRUE(b.IsRemoteCertificateRequired());
1537   }
1538 
1539   {
1540     RemoteModalityParameters p;
1541     p.SetApplicationEntityTitle("WORLD");
1542     p.SetPortNumber(4242);
1543     p.SetHost("hello.world.com");
1544     p.SetDicomTlsEnabled(true);
1545     p.SetTimeout(42);
1546 
1547     DicomAssociationParameters a("HELLO", p);
1548     a.SetOwnCertificatePath("key", "crt");
1549     a.SetTrustedCertificatesPath("trusted");
1550     a.SetRemoteCertificateRequired(false);
1551 
1552     ASSERT_THROW(a.SetMaximumPduLength(4095), OrthancException);
1553     ASSERT_THROW(a.SetMaximumPduLength(131073), OrthancException);
1554     a.SetMaximumPduLength(4096);
1555     a.SetMaximumPduLength(131072);
1556 
1557     Json::Value v = Json::objectValue;
1558     a.SerializeJob(v);
1559 
1560     ASSERT_EQ(8u, v.getMemberNames().size());
1561 
1562     DicomAssociationParameters b = DicomAssociationParameters::UnserializeJob(v);
1563 
1564     ASSERT_EQ("WORLD", b.GetRemoteModality().GetApplicationEntityTitle());
1565     ASSERT_EQ("hello.world.com", b.GetRemoteModality().GetHost());
1566     ASSERT_EQ(4242u, b.GetRemoteModality().GetPortNumber());
1567     ASSERT_EQ("HELLO", b.GetLocalApplicationEntityTitle());
1568     ASSERT_TRUE(b.GetRemoteModality().IsDicomTlsEnabled());
1569     ASSERT_EQ("key", b.GetOwnPrivateKeyPath());
1570     ASSERT_EQ("crt", b.GetOwnCertificatePath());
1571     ASSERT_EQ("trusted", b.GetTrustedCertificatesPath());
1572     ASSERT_EQ(131072u, b.GetMaximumPduLength());
1573     ASSERT_TRUE(b.GetRemoteModality().HasTimeout());
1574     ASSERT_EQ(42u, b.GetRemoteModality().GetTimeout());
1575     ASSERT_FALSE(b.IsRemoteCertificateRequired());
1576   }
1577 }
1578 
1579 
TEST(SerializationToolbox,Numbers)1580 TEST(SerializationToolbox, Numbers)
1581 {
1582   {
1583     int32_t i;
1584     ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, ""));
1585     ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "ee"));
1586     ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "42"));  ASSERT_EQ(42, i);
1587     ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "-42"));  ASSERT_EQ(-42, i);
1588     ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "-2147483648")); ASSERT_EQ(-2147483648l, i);
1589     ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "2147483647")); ASSERT_EQ(2147483647l, i);
1590     ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "-2147483649"));
1591     ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "2147483648"));
1592     ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "-2\\-3\\-4"));
1593     ASSERT_TRUE(SerializationToolbox::ParseFirstInteger32(i, "-2\\-3\\-4"));  ASSERT_EQ(-2, i);
1594   }
1595 
1596   {
1597     uint32_t i;
1598     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, ""));
1599     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "ee"));
1600     ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger32(i, "42"));  ASSERT_EQ(42u, i);
1601     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "-42"));
1602     ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger32(i, "4294967295")); ASSERT_EQ(4294967295u, i);
1603     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "4294967296"));
1604     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "2\\3\\4"));
1605     ASSERT_TRUE(SerializationToolbox::ParseFirstUnsignedInteger32(i, "2\\3\\4"));  ASSERT_EQ(2u, i);
1606   }
1607 
1608   {
1609     int64_t i;
1610     ASSERT_FALSE(SerializationToolbox::ParseInteger64(i, ""));
1611     ASSERT_FALSE(SerializationToolbox::ParseInteger64(i, "ee"));
1612     ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "42"));  ASSERT_EQ(42, i);
1613     ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "-42"));  ASSERT_EQ(-42, i);
1614     ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "-2147483649")); ASSERT_EQ(-2147483649ll, i);
1615     ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "2147483648")); ASSERT_EQ(2147483648ll, i);
1616     ASSERT_FALSE(SerializationToolbox::ParseInteger64(i, "-2\\-3\\-4"));
1617     ASSERT_TRUE(SerializationToolbox::ParseFirstInteger64(i, "-2\\-3\\-4"));  ASSERT_EQ(-2, i);
1618   }
1619 
1620   {
1621     uint64_t i;
1622     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, ""));
1623     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, "ee"));
1624     ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger64(i, "42"));  ASSERT_EQ(42u, i);
1625     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, "-42"));
1626     ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger64(i, "4294967296")); ASSERT_EQ(4294967296lu, i);
1627     ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, "2\\3\\4"));
1628     ASSERT_TRUE(SerializationToolbox::ParseFirstUnsignedInteger64(i, "2\\3\\4"));  ASSERT_EQ(2u, i);
1629   }
1630 
1631   {
1632     float i;
1633     ASSERT_FALSE(SerializationToolbox::ParseFloat(i, ""));
1634     ASSERT_FALSE(SerializationToolbox::ParseFloat(i, "ee"));
1635     ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "42"));  ASSERT_FLOAT_EQ(42.0f, i);
1636     ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "-42"));  ASSERT_FLOAT_EQ(-42.0f, i);
1637     ASSERT_FALSE(SerializationToolbox::ParseFloat(i, "2\\3\\4"));
1638     ASSERT_TRUE(SerializationToolbox::ParseFirstFloat(i, "1.367\\2.367\\3.367"));  ASSERT_FLOAT_EQ(1.367f, i);
1639 
1640     ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "1.2"));  ASSERT_FLOAT_EQ(1.2f, i);
1641     ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "-1.2e+2"));  ASSERT_FLOAT_EQ(-120.0f, i);
1642     ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "-1e-2"));  ASSERT_FLOAT_EQ(-0.01f, i);
1643     ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "1.3671875"));  ASSERT_FLOAT_EQ(1.3671875f, i);
1644   }
1645 
1646   {
1647     double i;
1648     ASSERT_FALSE(SerializationToolbox::ParseDouble(i, ""));
1649     ASSERT_FALSE(SerializationToolbox::ParseDouble(i, "ee"));
1650     ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "42"));  ASSERT_DOUBLE_EQ(42.0, i);
1651     ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "-42"));  ASSERT_DOUBLE_EQ(-42.0, i);
1652     ASSERT_FALSE(SerializationToolbox::ParseDouble(i, "2\\3\\4"));
1653     ASSERT_TRUE(SerializationToolbox::ParseFirstDouble(i, "1.367\\2.367\\3.367"));  ASSERT_DOUBLE_EQ(1.367, i);
1654 
1655     ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "1.2"));  ASSERT_DOUBLE_EQ(1.2, i);
1656     ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "-1.2e+2"));  ASSERT_DOUBLE_EQ(-120.0, i);
1657     ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "-1e-2"));  ASSERT_DOUBLE_EQ(-0.01, i);
1658     ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "1.3671875"));  ASSERT_DOUBLE_EQ(1.3671875, i);
1659   }
1660 }
1661