Mercurial > hg > orthanc
diff OrthancServer/UnitTestsSources/MultiThreadingTests.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | UnitTestsSources/MultiThreadingTests.cpp@ef696db8426f |
children | 05b8fd21089c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/UnitTestsSources/MultiThreadingTests.cpp Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,2228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Compatibility.h" +#include "../Core/FileStorage/MemoryStorageArea.h" +#include "../Core/JobsEngine/JobsEngine.h" +#include "../Core/Logging.h" +#include "../Core/MultiThreading/SharedMessageQueue.h" +#include "../Core/OrthancException.h" +#include "../Core/SerializationToolbox.h" +#include "../Core/SystemToolbox.h" +#include "../Core/Toolbox.h" +#include "../OrthancServer/Database/SQLiteDatabaseWrapper.h" +#include "../OrthancServer/ServerContext.h" +#include "../OrthancServer/ServerJobs/LuaJobManager.h" +#include "../OrthancServer/ServerJobs/OrthancJobUnserializer.h" + +#include "../Core/JobsEngine/Operations/JobOperationValues.h" +#include "../Core/JobsEngine/Operations/NullOperationValue.h" +#include "../Core/JobsEngine/Operations/StringOperationValue.h" +#include "../OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h" + +#include "../Core/JobsEngine/Operations/LogJobOperation.h" +#include "../OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h" +#include "../OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h" +#include "../OrthancServer/ServerJobs/Operations/StorePeerOperation.h" +#include "../OrthancServer/ServerJobs/Operations/StoreScuOperation.h" +#include "../OrthancServer/ServerJobs/Operations/SystemCallOperation.h" + +#include "../OrthancServer/ServerJobs/ArchiveJob.h" +#include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h" +#include "../OrthancServer/ServerJobs/DicomMoveScuJob.h" +#include "../OrthancServer/ServerJobs/MergeStudyJob.h" +#include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h" +#include "../OrthancServer/ServerJobs/ResourceModificationJob.h" +#include "../OrthancServer/ServerJobs/SplitStudyJob.h" + + +using namespace Orthanc; + +namespace +{ + class DummyJob : public IJob + { + private: + bool fails_; + unsigned int count_; + unsigned int steps_; + + public: + DummyJob() : + fails_(false), + count_(0), + steps_(4) + { + } + + explicit DummyJob(bool fails) : + fails_(fails), + count_(0), + steps_(4) + { + } + + virtual void Start() ORTHANC_OVERRIDE + { + } + + virtual void Reset() ORTHANC_OVERRIDE + { + } + + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE + { + if (fails_) + { + return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL); + } + else if (count_ == steps_ - 1) + { + return JobStepResult::Success(); + } + else + { + count_++; + return JobStepResult::Continue(); + } + } + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual float GetProgress() ORTHANC_OVERRIDE + { + return static_cast<float>(count_) / static_cast<float>(steps_ - 1); + } + + virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE + { + type = "DummyJob"; + } + + virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE + { + value = Json::objectValue; + value["Type"] = "DummyJob"; + return true; + } + + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE + { + value["hello"] = "world"; + } + + virtual bool GetOutput(std::string& output, + MimeType& mime, + const std::string& key) ORTHANC_OVERRIDE + { + return false; + } + }; + + + class DummyInstancesJob : public SetOfInstancesJob + { + private: + bool trailingStepDone_; + + protected: + virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE + { + return (instance != "nope"); + } + + virtual bool HandleTrailingStep() ORTHANC_OVERRIDE + { + if (HasTrailingStep()) + { + if (trailingStepDone_) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + trailingStepDone_ = true; + return true; + } + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + public: + DummyInstancesJob() : + trailingStepDone_(false) + { + } + + DummyInstancesJob(const Json::Value& value) : + SetOfInstancesJob(value) + { + if (HasTrailingStep()) + { + trailingStepDone_ = (GetPosition() == GetCommandsCount()); + } + else + { + trailingStepDone_ = false; + } + } + + bool IsTrailingStepDone() const + { + return trailingStepDone_; + } + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE + { + s = "DummyInstancesJob"; + } + }; + + + class DummyUnserializer : public GenericJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value) ORTHANC_OVERRIDE + { + if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob") + { + return new DummyInstancesJob(value); + } + else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob") + { + return new DummyJob; + } + else + { + return GenericJobUnserializer::UnserializeJob(value); + } + } + }; + + + class DynamicInteger : public IDynamicObject + { + private: + int value_; + std::set<int>& target_; + + public: + DynamicInteger(int value, std::set<int>& target) : + value_(value), target_(target) + { + } + + int GetValue() const + { + return value_; + } + }; +} + + +TEST(MultiThreading, SharedMessageQueueBasic) +{ + std::set<int> s; + + SharedMessageQueue q; + ASSERT_TRUE(q.WaitEmpty(0)); + q.Enqueue(new DynamicInteger(10, s)); + ASSERT_FALSE(q.WaitEmpty(1)); + q.Enqueue(new DynamicInteger(20, s)); + q.Enqueue(new DynamicInteger(30, s)); + q.Enqueue(new DynamicInteger(40, s)); + + std::unique_ptr<DynamicInteger> i; + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(MultiThreading, SharedMessageQueueClean) +{ + std::set<int> s; + + try + { + SharedMessageQueue q; + q.Enqueue(new DynamicInteger(10, s)); + q.Enqueue(new DynamicInteger(20, s)); + throw OrthancException(ErrorCode_InternalError); + } + catch (OrthancException&) + { + } +} + + + + +static bool CheckState(JobsRegistry& registry, + const std::string& id, + JobState state) +{ + JobState s; + if (registry.GetState(s, id)) + { + return state == s; + } + else + { + return false; + } +} + + +static bool CheckErrorCode(JobsRegistry& registry, + const std::string& id, + ErrorCode code) +{ + JobInfo s; + if (registry.GetJobInfo(s, id)) + { + return code == s.GetStatus().GetErrorCode(); + } + else + { + return false; + } +} + + +TEST(JobsRegistry, Priority) +{ + JobsRegistry registry(10); + + std::string i1, i2, i3, i4; + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new DummyJob(), 30); + registry.Submit(i3, new DummyJob(), 20); + registry.Submit(i4, new DummyJob(), 5); + + registry.SetMaxCompletedJobs(2); + + std::set<std::string> id; + registry.ListJobs(id); + + ASSERT_EQ(4u, id.size()); + ASSERT_TRUE(id.find(i1) != id.end()); + ASSERT_TRUE(id.find(i2) != id.end()); + ASSERT_TRUE(id.find(i3) != id.end()); + ASSERT_TRUE(id.find(i4) != id.end()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(30, job.GetPriority()); + ASSERT_EQ(i2, job.GetId()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i2, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i3, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(20, job.GetPriority()); + ASSERT_EQ(i3, job.GetId()); + + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i3, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i3, JobState_Success)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(10, job.GetPriority()); + ASSERT_EQ(i1, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(5, job.GetPriority()); + ASSERT_EQ(i4, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 1); + ASSERT_FALSE(job.IsValid()); + } + + JobState s; + ASSERT_TRUE(registry.GetState(s, i1)); + ASSERT_FALSE(registry.GetState(s, i2)); // Removed because oldest + ASSERT_FALSE(registry.GetState(s, i3)); // Removed because second oldest + ASSERT_TRUE(registry.GetState(s, i4)); + + registry.SetMaxCompletedJobs(1); // (*) + ASSERT_FALSE(registry.GetState(s, i1)); // Just discarded by (*) + ASSERT_TRUE(registry.GetState(s, i4)); +} + + +TEST(JobsRegistry, Simultaneous) +{ + JobsRegistry registry(10); + + std::string i1, i2; + registry.Submit(i1, new DummyJob(), 20); + registry.Submit(i2, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, i1, JobState_Pending)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job1(registry, 0); + JobsRegistry::RunningJob job2(registry, 0); + + ASSERT_TRUE(job1.IsValid()); + ASSERT_TRUE(job2.IsValid()); + + job1.MarkFailure(); + job2.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i1, JobState_Running)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i1, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Success)); +} + + +TEST(JobsRegistry, Resubmit) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkFailure(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Retry) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkRetry(0); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.ScheduleRetries(); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PausePending) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); +} + + +TEST(JobsRegistry, PauseRunning) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + registry.Resubmit(id); + job.MarkPause(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PauseRetry) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkRetry(0); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Cancel) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_FALSE(registry.Cancel("nope")); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + registry.Submit(id, new DummyJob(), 10); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkCanceled(); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Pause(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkRetry(500); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); +} + + + +TEST(JobsEngine, SubmitAndWait) +{ + JobsEngine engine(10); + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + Json::Value content = Json::nullValue; + engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10); + ASSERT_EQ(Json::objectValue, content.type()); + ASSERT_EQ("world", content["hello"].asString()); + + content = Json::nullValue; + ASSERT_THROW(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10), OrthancException); + ASSERT_EQ(Json::nullValue, content.type()); + + engine.Stop(); +} + + +TEST(JobsEngine, DISABLED_SequenceOfOperationsJob) +{ + JobsEngine engine(10); + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + std::string id; + SequenceOfOperationsJob* job = NULL; + + { + std::unique_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob); + job = a.get(); + engine.GetRegistry().Submit(id, a.release(), 0); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + { + SequenceOfOperationsJob::Lock lock(*job); + size_t i = lock.AddOperation(new LogJobOperation); + size_t j = lock.AddOperation(new LogJobOperation); + size_t k = lock.AddOperation(new LogJobOperation); + + StringOperationValue a("Hello"); + StringOperationValue b("World"); + lock.AddInput(i, a); + lock.AddInput(i, b); + + lock.Connect(i, j); + lock.Connect(j, k); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); + +} + + +TEST(JobsEngine, DISABLED_Lua) +{ + JobsEngine engine(10); + engine.SetThreadSleep(10); + engine.SetWorkersCount(2); + engine.Start(); + + LuaJobManager lua; + lua.SetMaxOperationsPerJob(5); + lua.SetTrailingOperationTimeout(200); + + for (size_t i = 0; i < 30; i++) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(150)); + + LuaJobManager::Lock lock(lua, engine); + size_t a = lock.AddLogOperation(); + size_t b = lock.AddLogOperation(); + size_t c = lock.AddSystemCallOperation("echo"); + lock.AddStringInput(a, boost::lexical_cast<std::string>(i)); + lock.AddNullInput(a); + lock.Connect(a, b); + lock.Connect(a, c); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); +} + + +static bool CheckSameJson(const Json::Value& a, + const Json::Value& b) +{ + std::string s = a.toStyledString(); + std::string t = b.toStyledString(); + + if (s == t) + { + return true; + } + else + { + LOG(ERROR) << "Expected serialization: " << s; + LOG(ERROR) << "Actual serialization: " << t; + return false; + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::unique_ptr<IJob> unserialized(unserializer.UnserializeJob(a)); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b)); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer, + SetOfInstancesJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::unique_ptr<SetOfInstancesJob> unserialized + (dynamic_cast<SetOfInstancesJob*>(unserializer.UnserializeJob(a))); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b) && + job.HasTrailingStep() == unserialized->HasTrailingStep() && + job.GetPosition() == unserialized->GetPosition() && + job.GetInstancesCount() == unserialized->GetInstancesCount() && + job.GetCommandsCount() == unserialized->GetCommandsCount()); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJobOperation& operation) +{ + Json::Value a = 42; + operation.Serialize(a); + + std::unique_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + JobOperationValue& value) +{ + Json::Value a = 42; + value.Serialize(a); + + std::unique_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +TEST(JobsSerialization, BadFileFormat) +{ + GenericJobUnserializer unserializer; + + Json::Value s; + + s = Json::objectValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = Json::arrayValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = "hello"; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = 42; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); +} + + +TEST(JobsSerialization, JobOperationValues) +{ + Json::Value s; + + { + JobOperationValues values; + values.Append(new NullOperationValue); + values.Append(new StringOperationValue("hello")); + values.Append(new StringOperationValue("world")); + + s = 42; + values.Serialize(s); + } + + { + GenericJobUnserializer unserializer; + std::unique_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s)); + ASSERT_EQ(3u, values->GetSize()); + ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(2).GetType()); + + ASSERT_EQ("hello", dynamic_cast<const StringOperationValue&>(values->GetValue(1)).GetContent()); + ASSERT_EQ("world", dynamic_cast<const StringOperationValue&>(values->GetValue(2)).GetContent()); + } +} + + +TEST(JobsSerialization, GenericValues) +{ + GenericJobUnserializer unserializer; + Json::Value s; + + { + NullOperationValue null; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, null)); + null.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::unique_ptr<JobOperationValue> value; + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_Null, value->GetType()); + + { + StringOperationValue str("Hello"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, str)); + str.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_String, value->GetType()); + ASSERT_EQ("Hello", dynamic_cast<StringOperationValue&>(*value).GetContent()); +} + + +TEST(JobsSerialization, GenericOperations) +{ + DummyUnserializer unserializer; + Json::Value s; + + { + LogJobOperation operation; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + + { + std::unique_ptr<IJobOperation> operation; + operation.reset(unserializer.UnserializeOperation(s)); + + // Make sure that we have indeed unserialized a log operation + Json::Value dummy; + ASSERT_THROW(dynamic_cast<DeleteResourceOperation&>(*operation).Serialize(dummy), std::bad_cast); + dynamic_cast<LogJobOperation&>(*operation).Serialize(dummy); + } +} + + +TEST(JobsSerialization, GenericJobs) +{ + Json::Value s; + + // This tests SetOfInstancesJob + + { + DummyInstancesJob job; + job.SetDescription("description"); + job.AddInstance("hello"); + job.AddInstance("nope"); + job.AddInstance("world"); + job.SetPermissive(true); + ASSERT_THROW(job.Step("jobId"), OrthancException); // Not started yet + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + DummyUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + const DummyInstancesJob& tmp = dynamic_cast<const DummyInstancesJob&>(*job); + ASSERT_FALSE(tmp.IsStarted()); + ASSERT_TRUE(tmp.IsPermissive()); + ASSERT_EQ("description", tmp.GetDescription()); + ASSERT_EQ(3u, tmp.GetInstancesCount()); + ASSERT_EQ(2u, tmp.GetPosition()); + ASSERT_EQ(1u, tmp.GetFailedInstances().size()); + ASSERT_EQ("hello", tmp.GetInstance(0)); + ASSERT_EQ("nope", tmp.GetInstance(1)); + ASSERT_EQ("world", tmp.GetInstance(2)); + ASSERT_TRUE(tmp.IsFailedInstance("nope")); + } + + // SequenceOfOperationsJob + + { + SequenceOfOperationsJob job; + job.SetDescription("hello"); + + { + SequenceOfOperationsJob::Lock lock(job); + size_t a = lock.AddOperation(new LogJobOperation); + size_t b = lock.AddOperation(new LogJobOperation); + lock.Connect(a, b); + + StringOperationValue s1("hello"); + StringOperationValue s2("world"); + lock.AddInput(a, s1); + lock.AddInput(a, s2); + lock.SetTrailingOperationTimeout(300); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + + { + GenericJobUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + GenericJobUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + std::string tmp; + dynamic_cast<SequenceOfOperationsJob&>(*job).GetDescription(tmp); + ASSERT_EQ("hello", tmp); + } +} + + +static bool IsSameTagValue(ParsedDicomFile& dicom1, + ParsedDicomFile& dicom2, + DicomTag tag) +{ + std::string a, b; + return (dicom1.GetTagValue(a, tag) && + dicom2.GetTagValue(b, tag) && + (a == b)); +} + + + +TEST(JobsSerialization, DicomModification) +{ + Json::Value s; + + ParsedDicomFile source(true); + source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, ""); + source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, ""); + source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, ""); + + std::unique_ptr<ParsedDicomFile> modified(source.Clone(true)); + + { + DicomModification modification; + modification.SetLevel(ResourceType_Series); + modification.Clear(DICOM_TAG_STUDY_DESCRIPTION); + modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); + modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); + + modification.Apply(*modified); + + s = 42; + modification.Serialize(s); + } + + { + DicomModification modification(s); + ASSERT_EQ(ResourceType_Series, modification.GetLevel()); + + std::unique_ptr<ParsedDicomFile> second(source.Clone(true)); + modification.Apply(*second); + + std::string s; + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(s.empty()); + ASSERT_FALSE(second->GetTagValue(s, DICOM_TAG_SERIES_DESCRIPTION)); + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("Test 4", s); + + ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID)); + + ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + } +} + + +TEST(JobsSerialization, DicomInstanceOrigin) +{ + Json::Value s; + std::string t; + + { + DicomInstanceOrigin origin; + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Unknown, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromDicomProtocol("host", "aet", "called")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_DicomProtocol, origin.GetRequestOrigin()); + ASSERT_EQ("aet", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_TRUE(origin.LookupRemoteAet(t)); ASSERT_EQ("aet", t); + ASSERT_TRUE(origin.LookupCalledAet(t)); ASSERT_EQ("called", t); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromHttp("host", "username")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_RestApi, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_TRUE(origin.LookupHttpUsername(t)); ASSERT_EQ("username", t); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromLua()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Lua, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromPlugins()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Plugins, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } +} + + +namespace +{ + class OrthancJobsSerialization : public testing::Test + { + private: + MemoryStorageArea storage_; + SQLiteDatabaseWrapper db_; // The SQLite DB is in memory + std::unique_ptr<ServerContext> context_; + + public: + OrthancJobsSerialization() + { + db_.Open(); + context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10)); + context_->SetupJobsEngine(true, false); + } + + virtual ~OrthancJobsSerialization() ORTHANC_OVERRIDE + { + context_->Stop(); + context_.reset(NULL); + db_.Close(); + } + + ServerContext& GetContext() + { + return *context_; + } + + bool CreateInstance(std::string& id) + { + // Create a sample DICOM file + ParsedDicomFile dicom(true); + dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), + false, DicomReplaceMode_InsertIfAbsent, ""); + + DicomInstanceToStore toStore; + toStore.SetParsedDicomFile(dicom); + + return (context_->Store(id, toStore, StoreInstanceMode_Default) == StoreStatus_Success); + } + }; +} + + +TEST_F(OrthancJobsSerialization, Values) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + { + DicomInstanceOperationValue instance(GetContext(), id); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, instance)); + instance.Serialize(s); + } + + std::unique_ptr<JobOperationValue> value; + value.reset(unserializer.UnserializeValue(s)); + ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType()); + ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId()); + + { + std::string content; + dynamic_cast<DicomInstanceOperationValue&>(*value).ReadDicom(content); + + ParsedDicomFile dicom(content); + ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("JODOGNE", content); + } +} + + +TEST_F(OrthancJobsSerialization, Operations) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + // DeleteResourceOperation + + { + DeleteResourceOperation operation(GetContext()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + std::unique_ptr<IJobOperation> operation; + + { + operation.reset(unserializer.UnserializeOperation(s)); + + Json::Value dummy; + ASSERT_THROW(dynamic_cast<LogJobOperation&>(*operation).Serialize(dummy), std::bad_cast); + dynamic_cast<DeleteResourceOperation&>(*operation).Serialize(dummy); + } + + // StorePeerOperation + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetCredentials("username", "password"); + peer.SetPkcs11Enabled(true); + + StorePeerOperation operation(peer); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StorePeerOperation& tmp = dynamic_cast<StorePeerOperation&>(*operation); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + } + + // StoreScuOperation + + { + TimeoutDicomConnectionManager luaManager; + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + StoreScuOperation operation(GetContext(), luaManager, "TEST", modality); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StoreScuOperation& tmp = dynamic_cast<StoreScuOperation&>(*operation); + ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("TEST", tmp.GetLocalAet()); + } + } + + // SystemCallOperation + + { + SystemCallOperation operation(std::string("echo")); + operation.AddPreArgument("a"); + operation.AddPreArgument("b"); + operation.AddPostArgument("c"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const SystemCallOperation& tmp = dynamic_cast<SystemCallOperation&>(*operation); + ASSERT_EQ("echo", tmp.GetCommand()); + ASSERT_EQ(2u, tmp.GetPreArgumentsCount()); + ASSERT_EQ(1u, tmp.GetPostArgumentsCount()); + ASSERT_EQ("a", tmp.GetPreArgument(0)); + ASSERT_EQ("b", tmp.GetPreArgument(1)); + ASSERT_EQ("c", tmp.GetPostArgument(0)); + } + + // ModifyInstanceOperation + + { + std::unique_ptr<DicomModification> modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const ModifyInstanceOperation& tmp = dynamic_cast<ModifyInstanceOperation&>(*operation); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } +} + + +TEST_F(OrthancJobsSerialization, Jobs) +{ + Json::Value s; + + // ArchiveJob + + { + ArchiveJob job(GetContext(), false, false); + ASSERT_FALSE(job.Serialize(s)); // Cannot serialize this + } + + // DicomModalityStoreJob + + OrthancJobUnserializer unserializer(GetContext()); + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("LOCAL"); + job.SetRemoteModality(modality); + job.SetMoveOriginator("MOVESCU", 42); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job); + ASSERT_EQ("LOCAL", tmp.GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("REMOTE", tmp.GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_TRUE(tmp.HasMoveOriginator()); + ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet()); + ASSERT_EQ(42, tmp.GetMoveOriginatorId()); + } + + // OrthancPeerStoreJob + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetCredentials("username", "password"); + peer.SetPkcs11Enabled(true); + + OrthancPeerStoreJob job(GetContext()); + job.SetPeer(peer); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + ASSERT_FALSE(tmp.IsTranscode()); + ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException); + } + + { + OrthancPeerStoreJob job(GetContext()); + ASSERT_THROW(job.SetTranscode("nope"), OrthancException); + job.SetTranscode("1.2.840.10008.1.2.4.50"); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job); + ASSERT_EQ("http://127.0.0.1:8042/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("", tmp.GetPeer().GetUsername()); + ASSERT_EQ("", tmp.GetPeer().GetPassword()); + ASSERT_FALSE(tmp.GetPeer().IsPkcs11Enabled()); + ASSERT_TRUE(tmp.IsTranscode()); + ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax()); + } + + // ResourceModificationJob + + { + std::unique_ptr<DicomModification> modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ResourceModificationJob job(GetContext()); + job.SetModification(modification.release(), ResourceType_Patient, true); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + job.AddTrailingStep(); // Necessary since 1.7.0 + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job); + ASSERT_TRUE(tmp.IsAnonymization()); + ASSERT_FALSE(tmp.IsTranscode()); + ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } + + { + ResourceModificationJob job(GetContext()); + ASSERT_THROW(job.SetTranscode("nope"), OrthancException); + job.SetTranscode(DicomTransferSyntax_JPEGProcess1); + + job.AddTrailingStep(); // Necessary since 1.7.0 + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job); + ASSERT_FALSE(tmp.IsAnonymization()); + ASSERT_TRUE(tmp.IsTranscode()); + ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax()); + ASSERT_EQ(RequestOrigin_Unknown, tmp.GetOrigin().GetRequestOrigin()); + } + + // SplitStudyJob + + std::string instance; + ASSERT_TRUE(CreateInstance(instance)); + + std::string study, series; + + { + ServerContext::DicomCacheLocker lock(GetContext(), instance); + study = lock.GetDicom().GetHasher().HashStudy(); + series = lock.GetDicom().GetHasher().HashSeries(); + } + + { + std::list<std::string> tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(1u, tmp.size()); + ASSERT_EQ(study, tmp.front()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(1u, tmp.size()); + ASSERT_EQ(series, tmp.front()); + } + + std::string study2; + + { + std::string a, b; + + { + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + SplitStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSourceSeries(series); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + job.Replace(DICOM_TAG_PATIENT_NAME, "hello"); + job.Remove(DICOM_TAG_PATIENT_BIRTH_DATE); + ASSERT_THROW(job.Replace(DICOM_TAG_SERIES_DESCRIPTION, "nope"), OrthancException); + ASSERT_THROW(job.Remove(DICOM_TAG_SERIES_DESCRIPTION), OrthancException); + + ASSERT_TRUE(job.GetTargetStudy().empty()); + a = job.GetTargetStudyUid(); + ASSERT_TRUE(job.LookupTargetSeriesUid(b, series)); + + job.AddTrailingStep(); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + + study2 = job.GetTargetStudy(); + ASSERT_FALSE(study2.empty()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + SplitStudyJob& tmp = dynamic_cast<SplitStudyJob&>(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetSourceStudy()); + ASSERT_EQ(a, tmp.GetTargetStudyUid()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + + std::string s; + ASSERT_EQ(study2, tmp.GetTargetStudy()); + ASSERT_FALSE(tmp.LookupTargetSeriesUid(s, "nope")); + ASSERT_TRUE(tmp.LookupTargetSeriesUid(s, series)); + ASSERT_EQ(b, s); + + ASSERT_FALSE(tmp.LookupReplacement(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(tmp.LookupReplacement(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("hello", s); + ASSERT_FALSE(tmp.IsRemoved(DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(tmp.IsRemoved(DICOM_TAG_PATIENT_BIRTH_DATE)); + } + } + + { + std::list<std::string> tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(2u, tmp.size()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(2u, tmp.size()); + } + + // MergeStudyJob + + { + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + MergeStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSource(study2); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + ASSERT_THROW(job.AddSourceStudy("nope"), OrthancException); + ASSERT_THROW(job.AddSource("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + ASSERT_EQ(job.GetTargetStudy(), study); + + job.AddTrailingStep(); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::list<std::string> tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(2u, tmp.size()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(3u, tmp.size()); + } + + { + std::unique_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + MergeStudyJob& tmp = dynamic_cast<MergeStudyJob&>(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetTargetStudy()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + } +} + + +TEST(JobsSerialization, Registry) +{ + Json::Value s; + std::string i1, i2; + + { + JobsRegistry registry(10); + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new SequenceOfOperationsJob(), 30); + registry.Serialize(s); + } + + { + DummyUnserializer unserializer; + JobsRegistry registry(unserializer, s, 10); + + Json::Value t; + registry.Serialize(t); + ASSERT_TRUE(CheckSameJson(s, t)); + } +} + + +TEST(JobsSerialization, TrailingStep) +{ + { + Json::Value s; + + DummyInstancesJob job; + ASSERT_EQ(0u, job.GetCommandsCount()); + ASSERT_EQ(0u, job.GetInstancesCount()); + + job.Start(); + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + job.AddInstance("hello"); + job.AddInstance("world"); + ASSERT_EQ(2u, job.GetCommandsCount()); + ASSERT_EQ(2u, job.GetInstancesCount()); + + job.Start(); + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(2u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + ASSERT_EQ(0u, job.GetInstancesCount()); + ASSERT_EQ(0u, job.GetCommandsCount()); + job.AddTrailingStep(); + ASSERT_EQ(0u, job.GetInstancesCount()); + ASSERT_EQ(1u, job.GetCommandsCount()); + + job.Start(); // This adds the trailing step + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_TRUE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_TRUE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + job.AddInstance("hello"); + ASSERT_EQ(1u, job.GetInstancesCount()); + ASSERT_EQ(1u, job.GetCommandsCount()); + job.AddTrailingStep(); + ASSERT_EQ(1u, job.GetInstancesCount()); + ASSERT_EQ(2u, job.GetCommandsCount()); + + job.Start(); + ASSERT_EQ(2u, job.GetCommandsCount()); + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_TRUE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(2u, job.GetPosition()); + ASSERT_TRUE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } +} + + +TEST(JobsSerialization, RemoteModalityParameters) +{ + Json::Value s; + + { + RemoteModalityParameters modality; + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + modality.Serialize(s, false); + ASSERT_EQ(Json::arrayValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, modality.GetManufacturer()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } + + s = Json::nullValue; + + { + RemoteModalityParameters modality; + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_THROW(modality.SetPortNumber(0), OrthancException); + ASSERT_THROW(modality.SetPortNumber(65535), OrthancException); + modality.SetApplicationEntityTitle("HELLO"); + modality.SetHost("world"); + modality.SetPortNumber(45); + modality.SetManufacturer(ModalityManufacturer_GenericNoWildcardInDates); + modality.Serialize(s, true); + ASSERT_EQ(Json::objectValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle()); + ASSERT_EQ("world", modality.GetHost()); + ASSERT_EQ(45u, modality.GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_GenericNoWildcardInDates, modality.GetManufacturer()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } + + s["Port"] = "46"; + + { + RemoteModalityParameters modality(s); + ASSERT_EQ(46u, modality.GetPortNumber()); + } + + s["Port"] = -1; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + s["Port"] = 65535; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + s["Port"] = "nope"; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + + std::set<DicomRequestType> operations; + operations.insert(DicomRequestType_Echo); + operations.insert(DicomRequestType_Find); + operations.insert(DicomRequestType_Get); + operations.insert(DicomRequestType_Move); + operations.insert(DicomRequestType_Store); + operations.insert(DicomRequestType_NAction); + operations.insert(DicomRequestType_NEventReport); + + ASSERT_EQ(7u, operations.size()); + + for (std::set<DicomRequestType>::const_iterator + it = operations.begin(); it != operations.end(); ++it) + { + { + RemoteModalityParameters modality; + modality.SetRequestAllowed(*it, false); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + + modality.Serialize(s, false); + ASSERT_EQ(Json::objectValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + + ASSERT_FALSE(modality.IsRequestAllowed(*it)); + + for (std::set<DicomRequestType>::const_iterator + it2 = operations.begin(); it2 != operations.end(); ++it2) + { + if (*it2 != *it) + { + ASSERT_TRUE(modality.IsRequestAllowed(*it2)); + } + } + } + } + + { + Json::Value s; + s["AllowStorageCommitment"] = false; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } + + { + Json::Value s; + s["AllowNAction"] = false; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + s["AllowTranscoding"] = false; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_FALSE(modality.IsTranscodingAllowed()); + } + + { + Json::Value s; + s["AllowNAction"] = true; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } +} + + +TEST_F(OrthancJobsSerialization, DicomAssociationParameters) +{ + Json::Value v; + + { + v = Json::objectValue; + DicomAssociationParameters p; + p.SerializeJob(v); + } + + { + DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); + ASSERT_EQ("ORTHANC", p.GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", p.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ(104u, p.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("127.0.0.1", p.GetRemoteModality().GetHost()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), p.GetTimeout()); + } + + { + v = Json::objectValue; + DicomAssociationParameters p; + p.SetLocalApplicationEntityTitle("HELLO"); + p.SetRemoteApplicationEntityTitle("WORLD"); + p.SetRemotePort(42); + p.SetRemoteHost("MY_HOST"); + p.SetTimeout(43); + p.SerializeJob(v); + } + + { + DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); + ASSERT_EQ("HELLO", p.GetLocalApplicationEntityTitle()); + ASSERT_EQ("WORLD", p.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ(42u, p.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("MY_HOST", p.GetRemoteModality().GetHost()); + ASSERT_EQ(43u, p.GetTimeout()); + } + + { + DicomModalityStoreJob job(GetContext()); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr<DicomModalityStoreJob> job( + dynamic_cast<DicomModalityStoreJob*>(unserializer.UnserializeJob(v))); + ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + ASSERT_FALSE(job->HasMoveOriginator()); + ASSERT_THROW(job->GetMoveOriginatorAet(), OrthancException); + ASSERT_THROW(job->GetMoveOriginatorId(), OrthancException); + ASSERT_FALSE(job->HasStorageCommitment()); + } + + { + RemoteModalityParameters r; + r.SetApplicationEntityTitle("HELLO"); + r.SetPortNumber(42); + r.SetHost("MY_HOST"); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("WORLD"); + job.SetRemoteModality(r); + job.SetTimeout(43); + job.SetMoveOriginator("ORIGINATOR", 100); + job.EnableStorageCommitment(true); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr<DicomModalityStoreJob> job( + dynamic_cast<DicomModalityStoreJob*>(unserializer.UnserializeJob(v))); + ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + ASSERT_TRUE(job->HasMoveOriginator()); + ASSERT_EQ("ORIGINATOR", job->GetMoveOriginatorAet()); + ASSERT_EQ(100, job->GetMoveOriginatorId()); + ASSERT_TRUE(job->HasStorageCommitment()); + } + + { + DicomMoveScuJob job(GetContext()); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr<DicomMoveScuJob> job( + dynamic_cast<DicomMoveScuJob*>(unserializer.UnserializeJob(v))); + ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + } + + { + RemoteModalityParameters r; + r.SetApplicationEntityTitle("HELLO"); + r.SetPortNumber(42); + r.SetHost("MY_HOST"); + + DicomMoveScuJob job(GetContext()); + job.SetLocalAet("WORLD"); + job.SetRemoteModality(r); + job.SetTimeout(43); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr<DicomMoveScuJob> job( + dynamic_cast<DicomMoveScuJob*>(unserializer.UnserializeJob(v))); + ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + } +}