Mercurial > hg > orthanc
diff OrthancServer/UnitTestsSources/ServerJobsTests.cpp @ 4060:149172a06b4d framework
splitting MultiThreadingTests.cpp into JobsTests.cpp and ServerJobsTests.cpp
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 11 Jun 2020 14:29:14 +0200 |
parents | OrthancServer/UnitTestsSources/MultiThreadingTests.cpp@05b8fd21089c |
children | 0953b3dc3261 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Thu Jun 11 14:29:14 2020 +0200 @@ -0,0 +1,1099 @@ +/** + * 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 "../../OrthancFramework/Sources/Compatibility.h" +#include "../../OrthancFramework/Sources/FileStorage/MemoryStorageArea.h" +#include "../../OrthancFramework/Sources/SerializationToolbox.h" + +#include "../Sources/Database/SQLiteDatabaseWrapper.h" +#include "../Sources/ServerContext.h" +#include "../Sources/ServerJobs/LuaJobManager.h" +#include "../Sources/ServerJobs/OrthancJobUnserializer.h" + +#include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h" +#include "../Sources/ServerJobs/Operations/DicomInstanceOperationValue.h" +#include "../Sources/ServerJobs/Operations/DeleteResourceOperation.h" +#include "../Sources/ServerJobs/Operations/ModifyInstanceOperation.h" +#include "../Sources/ServerJobs/Operations/StorePeerOperation.h" +#include "../Sources/ServerJobs/Operations/StoreScuOperation.h" +#include "../Sources/ServerJobs/Operations/SystemCallOperation.h" + +#include "../Sources/ServerJobs/ArchiveJob.h" +#include "../Sources/ServerJobs/DicomModalityStoreJob.h" +#include "../Sources/ServerJobs/DicomMoveScuJob.h" +#include "../Sources/ServerJobs/MergeStudyJob.h" +#include "../Sources/ServerJobs/OrthancPeerStoreJob.h" +#include "../Sources/ServerJobs/ResourceModificationJob.h" +#include "../Sources/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(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 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, 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, 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_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()); + } +}