changeset 726:edffcf3ce7ab

sonar
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 19 Feb 2014 12:39:17 +0100
parents ef4569ae7952
children 52b5316a2517
files CMakeLists.txt UnitTestsSources/ServerIndex.cpp UnitTestsSources/ServerIndexTests.cpp UnitTestsSources/UnitTestsMain.cpp UnitTestsSources/main.cpp
diffstat 5 files changed, 1168 insertions(+), 1168 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Feb 19 11:01:55 2014 +0100
+++ b/CMakeLists.txt	Wed Feb 19 12:39:17 2014 +0100
@@ -259,12 +259,12 @@
   UnitTestsSources/RestApi.cpp
   UnitTestsSources/SQLite.cpp
   UnitTestsSources/SQLiteChromium.cpp
-  UnitTestsSources/ServerIndex.cpp
+  UnitTestsSources/ServerIndexTests.cpp
   UnitTestsSources/Versions.cpp
   UnitTestsSources/Zip.cpp
   UnitTestsSources/Lua.cpp
   UnitTestsSources/MultiThreading.cpp
-  UnitTestsSources/main.cpp
+  UnitTestsSources/UnitTestsMain.cpp
   )
 target_link_libraries(UnitTests ServerLibrary CoreLibrary)
 
--- a/UnitTestsSources/ServerIndex.cpp	Wed Feb 19 11:01:55 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,556 +0,0 @@
-#include "gtest/gtest.h"
-
-#include "../OrthancServer/DatabaseWrapper.h"
-#include "../OrthancServer/ServerContext.h"
-#include "../OrthancServer/ServerIndex.h"
-#include "../Core/Uuid.h"
-#include "../Core/DicomFormat/DicomNullValue.h"
-
-#include <ctype.h>
-#include <glog/logging.h>
-#include <algorithm>
-
-using namespace Orthanc;
-
-namespace
-{
-  class ServerIndexListener : public IServerIndexListener
-  {
-  public:
-    std::vector<std::string> deletedFiles_;
-    std::string ancestorId_;
-    ResourceType ancestorType_;
-
-    void Reset()
-    {
-      ancestorId_ = "";
-      deletedFiles_.clear();
-    }
-
-    virtual void SignalRemainingAncestor(ResourceType type,
-                                         const std::string& publicId) 
-    {
-      ancestorId_ = publicId;
-      ancestorType_ = type;
-    }
-
-    virtual void SignalFileDeleted(const FileInfo& info)
-    {
-      const std::string fileUuid = info.GetUuid();
-      deletedFiles_.push_back(fileUuid);
-      LOG(INFO) << "A file must be removed: " << fileUuid;
-    }                                
-  };
-}
-
-
-TEST(DatabaseWrapper, Simple)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  int64_t a[] = {
-    index.CreateResource("a", ResourceType_Patient),   // 0
-    index.CreateResource("b", ResourceType_Study),     // 1
-    index.CreateResource("c", ResourceType_Series),    // 2
-    index.CreateResource("d", ResourceType_Instance),  // 3
-    index.CreateResource("e", ResourceType_Instance),  // 4
-    index.CreateResource("f", ResourceType_Instance),  // 5
-    index.CreateResource("g", ResourceType_Study)      // 6
-  };
-
-  ASSERT_EQ("a", index.GetPublicId(a[0]));
-  ASSERT_EQ("b", index.GetPublicId(a[1]));
-  ASSERT_EQ("c", index.GetPublicId(a[2]));
-  ASSERT_EQ("d", index.GetPublicId(a[3]));
-  ASSERT_EQ("e", index.GetPublicId(a[4]));
-  ASSERT_EQ("f", index.GetPublicId(a[5]));
-  ASSERT_EQ("g", index.GetPublicId(a[6]));
-
-  ASSERT_EQ(ResourceType_Patient, index.GetResourceType(a[0]));
-  ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[1]));
-  ASSERT_EQ(ResourceType_Series, index.GetResourceType(a[2]));
-  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[3]));
-  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[4]));
-  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[5]));
-  ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[6]));
-
-  {
-    Json::Value t;
-    index.GetAllPublicIds(t, ResourceType_Patient);
-
-    ASSERT_EQ(1u, t.size());
-    ASSERT_EQ("a", t[0u].asString());
-
-    index.GetAllPublicIds(t, ResourceType_Series);
-    ASSERT_EQ(1u, t.size());
-    ASSERT_EQ("c", t[0u].asString());
-
-    index.GetAllPublicIds(t, ResourceType_Study);
-    ASSERT_EQ(2u, t.size());
-
-    index.GetAllPublicIds(t, ResourceType_Instance);
-    ASSERT_EQ(3u, t.size());
-  }
-
-  index.SetGlobalProperty(GlobalProperty_FlushSleep, "World");
-
-  index.AttachChild(a[0], a[1]);
-  index.AttachChild(a[1], a[2]);
-  index.AttachChild(a[2], a[3]);
-  index.AttachChild(a[2], a[4]);
-  index.AttachChild(a[6], a[5]);
-
-  int64_t parent;
-  ASSERT_FALSE(index.LookupParent(parent, a[0]));
-  ASSERT_TRUE(index.LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent);
-  ASSERT_TRUE(index.LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent);
-  ASSERT_TRUE(index.LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent);
-  ASSERT_TRUE(index.LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent);
-  ASSERT_TRUE(index.LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent);
-  ASSERT_FALSE(index.LookupParent(parent, a[6]));
-
-  std::string s;
-  
-  ASSERT_FALSE(index.GetParentPublicId(s, a[0]));
-  ASSERT_FALSE(index.GetParentPublicId(s, a[6]));
-  ASSERT_TRUE(index.GetParentPublicId(s, a[1])); ASSERT_EQ("a", s);
-  ASSERT_TRUE(index.GetParentPublicId(s, a[2])); ASSERT_EQ("b", s);
-  ASSERT_TRUE(index.GetParentPublicId(s, a[3])); ASSERT_EQ("c", s);
-  ASSERT_TRUE(index.GetParentPublicId(s, a[4])); ASSERT_EQ("c", s);
-  ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s);
-
-  std::list<std::string> l;
-  index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
-  index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front());
-  index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); 
-  index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); 
-  index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); 
-  index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front());
-
-  index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); 
-  if (l.front() == "d")
-  {
-    ASSERT_EQ("e", l.back());
-  }
-  else
-  {
-    ASSERT_EQ("d", l.back());
-    ASSERT_EQ("e", l.front());
-  }
-
-  std::list<MetadataType> md;
-  index.ListAvailableMetadata(md, a[4]);
-  ASSERT_EQ(0u, md.size());
-
-  index.AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", 
-                                     CompressionType_Zlib, 21, "compressedMD5"));
-  index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5"));
-  index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5"));
-  index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
-  
-  index.ListAvailableMetadata(md, a[4]);
-  ASSERT_EQ(1u, md.size());
-  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
-  index.SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU");
-  index.ListAvailableMetadata(md, a[4]);
-  ASSERT_EQ(2u, md.size());
-  index.DeleteMetadata(a[4], MetadataType_ModifiedFrom);
-  index.ListAvailableMetadata(md, a[4]);
-  ASSERT_EQ(1u, md.size());
-  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
-
-  ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize());
-  ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize());
-
-  DicomMap m;
-  m.SetValue(0x0010, 0x0010, "PatientName");
-  index.SetMainDicomTags(a[3], m);
-
-  int64_t b;
-  ResourceType t;
-  ASSERT_TRUE(index.LookupResource("g", b, t));
-  ASSERT_EQ(7, b);
-  ASSERT_EQ(ResourceType_Study, t);
-
-  ASSERT_TRUE(index.LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet));
-  ASSERT_FALSE(index.LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
-  ASSERT_EQ("PINNACLE", s);
-  ASSERT_EQ("PINNACLE", index.GetMetadata(a[4], MetadataType_Instance_RemoteAet));
-  ASSERT_EQ("None", index.GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None"));
-
-  ASSERT_TRUE(index.LookupGlobalProperty(s, GlobalProperty_FlushSleep));
-  ASSERT_FALSE(index.LookupGlobalProperty(s, static_cast<GlobalProperty>(42)));
-  ASSERT_EQ("World", s);
-  ASSERT_EQ("World", index.GetGlobalProperty(GlobalProperty_FlushSleep));
-  ASSERT_EQ("None", index.GetGlobalProperty(static_cast<GlobalProperty>(42), "None"));
-
-  FileInfo att;
-  ASSERT_TRUE(index.LookupAttachment(att, a[4], FileContentType_DicomAsJson));
-  ASSERT_EQ("my json file", att.GetUuid());
-  ASSERT_EQ(21u, att.GetCompressedSize());
-  ASSERT_EQ("md5", att.GetUncompressedMD5());
-  ASSERT_EQ("compressedMD5", att.GetCompressedMD5());
-  ASSERT_EQ(42u, att.GetUncompressedSize());
-  ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType());
-
-  ASSERT_TRUE(index.LookupAttachment(att, a[6], FileContentType_Dicom));
-  ASSERT_EQ("world", att.GetUuid());
-  ASSERT_EQ(44u, att.GetCompressedSize());
-  ASSERT_EQ("md5", att.GetUncompressedMD5());
-  ASSERT_EQ("md5", att.GetCompressedMD5());
-  ASSERT_EQ(44u, att.GetUncompressedSize());
-  ASSERT_EQ(CompressionType_None, att.GetCompressionType());
-
-  ASSERT_EQ(0u, listener.deletedFiles_.size());
-  ASSERT_EQ(7u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(3u, index.GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(1u, index.GetTableRecordCount("Metadata"));
-  ASSERT_EQ(1u, index.GetTableRecordCount("MainDicomTags"));
-  index.DeleteResource(a[0]);
-
-  ASSERT_EQ(2u, listener.deletedFiles_.size());
-  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
-                         listener.deletedFiles_.end(),
-                         "my json file") == listener.deletedFiles_.end());
-  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
-                         listener.deletedFiles_.end(),
-                         "my dicom file") == listener.deletedFiles_.end());
-
-  ASSERT_EQ(2u, index.GetTableRecordCount("Resources"));
-  ASSERT_EQ(0u, index.GetTableRecordCount("Metadata"));
-  ASSERT_EQ(1u, index.GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(0u, index.GetTableRecordCount("MainDicomTags"));
-  index.DeleteResource(a[5]);
-  ASSERT_EQ(0u, index.GetTableRecordCount("Resources"));
-  ASSERT_EQ(0u, index.GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties"));
-
-  ASSERT_EQ(3u, listener.deletedFiles_.size());
-  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
-                         listener.deletedFiles_.end(),
-                         "world") == listener.deletedFiles_.end());
-}
-
-
-
-
-TEST(DatabaseWrapper, Upward)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  int64_t a[] = {
-    index.CreateResource("a", ResourceType_Patient),   // 0
-    index.CreateResource("b", ResourceType_Study),     // 1
-    index.CreateResource("c", ResourceType_Series),    // 2
-    index.CreateResource("d", ResourceType_Instance),  // 3
-    index.CreateResource("e", ResourceType_Instance),  // 4
-    index.CreateResource("f", ResourceType_Study),     // 5
-    index.CreateResource("g", ResourceType_Series),    // 6
-    index.CreateResource("h", ResourceType_Series)     // 7
-  };
-
-  index.AttachChild(a[0], a[1]);
-  index.AttachChild(a[1], a[2]);
-  index.AttachChild(a[2], a[3]);
-  index.AttachChild(a[2], a[4]);
-  index.AttachChild(a[1], a[6]);
-  index.AttachChild(a[0], a[5]);
-  index.AttachChild(a[5], a[7]);
-
-  {
-    Json::Value j;
-    index.GetChildren(j, a[0]);
-    ASSERT_EQ(2u, j.size());
-    ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") ||
-                (j[1u] == "b" && j[0u] == "f"));
-
-    index.GetChildren(j, a[1]);
-    ASSERT_EQ(2u, j.size());
-    ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") ||
-                (j[1u] == "c" && j[0u] == "g"));
-
-    index.GetChildren(j, a[2]);
-    ASSERT_EQ(2u, j.size());
-    ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") ||
-                (j[1u] == "d" && j[0u] == "e"));
-
-    index.GetChildren(j, a[3]); ASSERT_EQ(0u, j.size());
-    index.GetChildren(j, a[4]); ASSERT_EQ(0u, j.size());
-    index.GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString());
-    index.GetChildren(j, a[6]); ASSERT_EQ(0u, j.size());
-    index.GetChildren(j, a[7]); ASSERT_EQ(0u, j.size());
-  }
-
-  listener.Reset();
-  index.DeleteResource(a[3]);
-  ASSERT_EQ("c", listener.ancestorId_);
-  ASSERT_EQ(ResourceType_Series, listener.ancestorType_);
-
-  listener.Reset();
-  index.DeleteResource(a[4]);
-  ASSERT_EQ("b", listener.ancestorId_);
-  ASSERT_EQ(ResourceType_Study, listener.ancestorType_);
-
-  listener.Reset();
-  index.DeleteResource(a[7]);
-  ASSERT_EQ("a", listener.ancestorId_);
-  ASSERT_EQ(ResourceType_Patient, listener.ancestorType_);
-
-  listener.Reset();
-  index.DeleteResource(a[6]);
-  ASSERT_EQ("", listener.ancestorId_);  // No more ancestor
-}
-
-
-TEST(DatabaseWrapper, PatientRecycling)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  std::vector<int64_t> patients;
-  for (int i = 0; i < 10; i++)
-  {
-    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
-    patients.push_back(index.CreateResource(p, ResourceType_Patient));
-    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, 
-                                              "md5-" + boost::lexical_cast<std::string>(i)));
-    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
-  }
-
-  ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-
-  listener.Reset();
-
-  index.DeleteResource(patients[5]);
-  index.DeleteResource(patients[0]);
-  ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder"));
-
-  ASSERT_EQ(2u, listener.deletedFiles_.size());
-  ASSERT_EQ("Patient 5", listener.deletedFiles_[0]);
-  ASSERT_EQ("Patient 0", listener.deletedFiles_[1]);
-
-  int64_t p;
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
-  index.DeleteResource(p);
-  index.DeleteResource(patients[8]);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
-  index.DeleteResource(p);
-  ASSERT_FALSE(index.SelectPatientToRecycle(p));
-
-  ASSERT_EQ(10u, listener.deletedFiles_.size());
-  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-}
-
-
-TEST(DatabaseWrapper, PatientProtection)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  std::vector<int64_t> patients;
-  for (int i = 0; i < 5; i++)
-  {
-    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
-    patients.push_back(index.CreateResource(p, ResourceType_Patient));
-    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10,
-                                              "md5-" + boost::lexical_cast<std::string>(i)));
-    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
-  }
-
-  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-
-  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
-  index.SetProtectedPatient(patients[2], true);
-  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
-  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
-  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
-
-  index.SetProtectedPatient(patients[2], true);
-  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
-  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-  index.SetProtectedPatient(patients[2], false);
-  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
-  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-  index.SetProtectedPatient(patients[2], false);
-  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
-  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-
-  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-  index.SetProtectedPatient(patients[2], true);
-  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
-  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
-  index.SetProtectedPatient(patients[2], false);
-  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
-  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-  index.SetProtectedPatient(patients[3], true);
-  ASSERT_TRUE(index.IsProtectedPatient(patients[3]));
-  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-
-  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, listener.deletedFiles_.size());
-
-  // Unprotecting a patient puts it at the last position in the recycling queue
-  int64_t p;
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
-  index.DeleteResource(p);
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
-  index.DeleteResource(p);
-  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[2]));
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
-  index.DeleteResource(p);
-  // "patients[3]" is still protected
-  ASSERT_FALSE(index.SelectPatientToRecycle(p));
-
-  ASSERT_EQ(4u, listener.deletedFiles_.size());
-  ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-
-  index.SetProtectedPatient(patients[3], false);
-  ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[3]));
-  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[2]));
-  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
-  index.DeleteResource(p);
-
-  ASSERT_EQ(5u, listener.deletedFiles_.size());
-  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
-}
-
-
-
-TEST(DatabaseWrapper, Sequence)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-  ASSERT_EQ(2u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-  ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-  ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
-}
-
-
-
-TEST(DatabaseWrapper, LookupTagValue)
-{
-  ServerIndexListener listener;
-  DatabaseWrapper index(listener);
-
-  int64_t a[] = {
-    index.CreateResource("a", ResourceType_Study),   // 0
-    index.CreateResource("b", ResourceType_Study),   // 1
-    index.CreateResource("c", ResourceType_Study),   // 2
-    index.CreateResource("d", ResourceType_Series)   // 3
-  };
-
-  DicomMap m;
-  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[0], m);
-  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index.SetMainDicomTags(a[1], m);
-  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[2], m);
-  m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index.SetMainDicomTags(a[3], m);
-
-  std::list<int64_t> s;
-
-  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0");
-  ASSERT_EQ(2u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
-
-  index.LookupTagValue(s, "0");
-  ASSERT_EQ(3u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end());
-
-  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1");
-  ASSERT_EQ(1u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
-
-  index.LookupTagValue(s, "1");
-  ASSERT_EQ(1u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
-
-
-  /*{
-      std::list<std::string> s;
-      context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059");
-      for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++)
-      {
-        std::cout << "*** " << *i << std::endl;;
-      }      
-      }*/
-}
-
-
-
-TEST(ServerIndex, AttachmentRecycling)
-{
-  const std::string path = "OrthancStorageUnitTests";
-  Toolbox::RemoveFile(path + "/index");
-  ServerContext context(path, ":memory:");   // The SQLite DB is in memory
-  ServerIndex& index = context.GetIndex();
-
-  index.SetMaximumStorageSize(10);
-
-  Json::Value tmp;
-  index.ComputeStatistics(tmp);
-  ASSERT_EQ(0, tmp["CountPatients"].asInt());
-  ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString()));
-
-  ServerIndex::Attachments attachments;
-
-  std::vector<std::string> ids;
-  for (int i = 0; i < 10; i++)
-  {
-    std::string id = boost::lexical_cast<std::string>(i);
-    DicomMap instance;
-    instance.SetValue(DICOM_TAG_PATIENT_ID, "patient-" + id);
-    instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id);
-    instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id);
-    instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id);
-    ASSERT_EQ(StoreStatus_Success, index.Store(instance, attachments, ""));
-
-    DicomInstanceHasher hasher(instance);
-    ids.push_back(hasher.HashPatient());
-    ids.push_back(hasher.HashStudy());
-    ids.push_back(hasher.HashSeries());
-    ids.push_back(hasher.HashInstance());
-  }
-
-  index.ComputeStatistics(tmp);
-  ASSERT_EQ(10, tmp["CountPatients"].asInt());
-  ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString()));
-
-  for (size_t i = 0; i < ids.size(); i++)
-  {
-    FileInfo info(Toolbox::GenerateUuid(), FileContentType_Dicom, 1, "md5");
-    index.AddAttachment(info, ids[i]);
-
-    index.ComputeStatistics(tmp);
-    ASSERT_GE(10, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString()));
-  }
-
-  // Because the DB is in memory, the SQLite index must not have been created
-  ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException);  
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/ServerIndexTests.cpp	Wed Feb 19 12:39:17 2014 +0100
@@ -0,0 +1,556 @@
+#include "gtest/gtest.h"
+
+#include "../OrthancServer/DatabaseWrapper.h"
+#include "../OrthancServer/ServerContext.h"
+#include "../OrthancServer/ServerIndex.h"
+#include "../Core/Uuid.h"
+#include "../Core/DicomFormat/DicomNullValue.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+#include <algorithm>
+
+using namespace Orthanc;
+
+namespace
+{
+  class ServerIndexListener : public IServerIndexListener
+  {
+  public:
+    std::vector<std::string> deletedFiles_;
+    std::string ancestorId_;
+    ResourceType ancestorType_;
+
+    void Reset()
+    {
+      ancestorId_ = "";
+      deletedFiles_.clear();
+    }
+
+    virtual void SignalRemainingAncestor(ResourceType type,
+                                         const std::string& publicId) 
+    {
+      ancestorId_ = publicId;
+      ancestorType_ = type;
+    }
+
+    virtual void SignalFileDeleted(const FileInfo& info)
+    {
+      const std::string fileUuid = info.GetUuid();
+      deletedFiles_.push_back(fileUuid);
+      LOG(INFO) << "A file must be removed: " << fileUuid;
+    }                                
+  };
+}
+
+
+TEST(DatabaseWrapper, Simple)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Patient),   // 0
+    index.CreateResource("b", ResourceType_Study),     // 1
+    index.CreateResource("c", ResourceType_Series),    // 2
+    index.CreateResource("d", ResourceType_Instance),  // 3
+    index.CreateResource("e", ResourceType_Instance),  // 4
+    index.CreateResource("f", ResourceType_Instance),  // 5
+    index.CreateResource("g", ResourceType_Study)      // 6
+  };
+
+  ASSERT_EQ("a", index.GetPublicId(a[0]));
+  ASSERT_EQ("b", index.GetPublicId(a[1]));
+  ASSERT_EQ("c", index.GetPublicId(a[2]));
+  ASSERT_EQ("d", index.GetPublicId(a[3]));
+  ASSERT_EQ("e", index.GetPublicId(a[4]));
+  ASSERT_EQ("f", index.GetPublicId(a[5]));
+  ASSERT_EQ("g", index.GetPublicId(a[6]));
+
+  ASSERT_EQ(ResourceType_Patient, index.GetResourceType(a[0]));
+  ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[1]));
+  ASSERT_EQ(ResourceType_Series, index.GetResourceType(a[2]));
+  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[3]));
+  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[4]));
+  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[5]));
+  ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[6]));
+
+  {
+    Json::Value t;
+    index.GetAllPublicIds(t, ResourceType_Patient);
+
+    ASSERT_EQ(1u, t.size());
+    ASSERT_EQ("a", t[0u].asString());
+
+    index.GetAllPublicIds(t, ResourceType_Series);
+    ASSERT_EQ(1u, t.size());
+    ASSERT_EQ("c", t[0u].asString());
+
+    index.GetAllPublicIds(t, ResourceType_Study);
+    ASSERT_EQ(2u, t.size());
+
+    index.GetAllPublicIds(t, ResourceType_Instance);
+    ASSERT_EQ(3u, t.size());
+  }
+
+  index.SetGlobalProperty(GlobalProperty_FlushSleep, "World");
+
+  index.AttachChild(a[0], a[1]);
+  index.AttachChild(a[1], a[2]);
+  index.AttachChild(a[2], a[3]);
+  index.AttachChild(a[2], a[4]);
+  index.AttachChild(a[6], a[5]);
+
+  int64_t parent;
+  ASSERT_FALSE(index.LookupParent(parent, a[0]));
+  ASSERT_TRUE(index.LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent);
+  ASSERT_TRUE(index.LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent);
+  ASSERT_FALSE(index.LookupParent(parent, a[6]));
+
+  std::string s;
+  
+  ASSERT_FALSE(index.GetParentPublicId(s, a[0]));
+  ASSERT_FALSE(index.GetParentPublicId(s, a[6]));
+  ASSERT_TRUE(index.GetParentPublicId(s, a[1])); ASSERT_EQ("a", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[2])); ASSERT_EQ("b", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[3])); ASSERT_EQ("c", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[4])); ASSERT_EQ("c", s);
+  ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s);
+
+  std::list<std::string> l;
+  index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
+  index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front());
+  index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front());
+
+  index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); 
+  if (l.front() == "d")
+  {
+    ASSERT_EQ("e", l.back());
+  }
+  else
+  {
+    ASSERT_EQ("d", l.back());
+    ASSERT_EQ("e", l.front());
+  }
+
+  std::list<MetadataType> md;
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(0u, md.size());
+
+  index.AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", 
+                                     CompressionType_Zlib, 21, "compressedMD5"));
+  index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5"));
+  index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5"));
+  index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
+  
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(1u, md.size());
+  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
+  index.SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU");
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(2u, md.size());
+  index.DeleteMetadata(a[4], MetadataType_ModifiedFrom);
+  index.ListAvailableMetadata(md, a[4]);
+  ASSERT_EQ(1u, md.size());
+  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
+
+  ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize());
+  ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize());
+
+  DicomMap m;
+  m.SetValue(0x0010, 0x0010, "PatientName");
+  index.SetMainDicomTags(a[3], m);
+
+  int64_t b;
+  ResourceType t;
+  ASSERT_TRUE(index.LookupResource("g", b, t));
+  ASSERT_EQ(7, b);
+  ASSERT_EQ(ResourceType_Study, t);
+
+  ASSERT_TRUE(index.LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet));
+  ASSERT_FALSE(index.LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
+  ASSERT_EQ("PINNACLE", s);
+  ASSERT_EQ("PINNACLE", index.GetMetadata(a[4], MetadataType_Instance_RemoteAet));
+  ASSERT_EQ("None", index.GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None"));
+
+  ASSERT_TRUE(index.LookupGlobalProperty(s, GlobalProperty_FlushSleep));
+  ASSERT_FALSE(index.LookupGlobalProperty(s, static_cast<GlobalProperty>(42)));
+  ASSERT_EQ("World", s);
+  ASSERT_EQ("World", index.GetGlobalProperty(GlobalProperty_FlushSleep));
+  ASSERT_EQ("None", index.GetGlobalProperty(static_cast<GlobalProperty>(42), "None"));
+
+  FileInfo att;
+  ASSERT_TRUE(index.LookupAttachment(att, a[4], FileContentType_DicomAsJson));
+  ASSERT_EQ("my json file", att.GetUuid());
+  ASSERT_EQ(21u, att.GetCompressedSize());
+  ASSERT_EQ("md5", att.GetUncompressedMD5());
+  ASSERT_EQ("compressedMD5", att.GetCompressedMD5());
+  ASSERT_EQ(42u, att.GetUncompressedSize());
+  ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType());
+
+  ASSERT_TRUE(index.LookupAttachment(att, a[6], FileContentType_Dicom));
+  ASSERT_EQ("world", att.GetUuid());
+  ASSERT_EQ(44u, att.GetCompressedSize());
+  ASSERT_EQ("md5", att.GetUncompressedMD5());
+  ASSERT_EQ("md5", att.GetCompressedMD5());
+  ASSERT_EQ(44u, att.GetUncompressedSize());
+  ASSERT_EQ(CompressionType_None, att.GetCompressionType());
+
+  ASSERT_EQ(0u, listener.deletedFiles_.size());
+  ASSERT_EQ(7u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(3u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("Metadata"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("MainDicomTags"));
+  index.DeleteResource(a[0]);
+
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my json file") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my dicom file") == listener.deletedFiles_.end());
+
+  ASSERT_EQ(2u, index.GetTableRecordCount("Resources"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("Metadata"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("MainDicomTags"));
+  index.DeleteResource(a[5]);
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties"));
+
+  ASSERT_EQ(3u, listener.deletedFiles_.size());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "world") == listener.deletedFiles_.end());
+}
+
+
+
+
+TEST(DatabaseWrapper, Upward)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Patient),   // 0
+    index.CreateResource("b", ResourceType_Study),     // 1
+    index.CreateResource("c", ResourceType_Series),    // 2
+    index.CreateResource("d", ResourceType_Instance),  // 3
+    index.CreateResource("e", ResourceType_Instance),  // 4
+    index.CreateResource("f", ResourceType_Study),     // 5
+    index.CreateResource("g", ResourceType_Series),    // 6
+    index.CreateResource("h", ResourceType_Series)     // 7
+  };
+
+  index.AttachChild(a[0], a[1]);
+  index.AttachChild(a[1], a[2]);
+  index.AttachChild(a[2], a[3]);
+  index.AttachChild(a[2], a[4]);
+  index.AttachChild(a[1], a[6]);
+  index.AttachChild(a[0], a[5]);
+  index.AttachChild(a[5], a[7]);
+
+  {
+    Json::Value j;
+    index.GetChildren(j, a[0]);
+    ASSERT_EQ(2u, j.size());
+    ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") ||
+                (j[1u] == "b" && j[0u] == "f"));
+
+    index.GetChildren(j, a[1]);
+    ASSERT_EQ(2u, j.size());
+    ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") ||
+                (j[1u] == "c" && j[0u] == "g"));
+
+    index.GetChildren(j, a[2]);
+    ASSERT_EQ(2u, j.size());
+    ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") ||
+                (j[1u] == "d" && j[0u] == "e"));
+
+    index.GetChildren(j, a[3]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[4]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString());
+    index.GetChildren(j, a[6]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[7]); ASSERT_EQ(0u, j.size());
+  }
+
+  listener.Reset();
+  index.DeleteResource(a[3]);
+  ASSERT_EQ("c", listener.ancestorId_);
+  ASSERT_EQ(ResourceType_Series, listener.ancestorType_);
+
+  listener.Reset();
+  index.DeleteResource(a[4]);
+  ASSERT_EQ("b", listener.ancestorId_);
+  ASSERT_EQ(ResourceType_Study, listener.ancestorType_);
+
+  listener.Reset();
+  index.DeleteResource(a[7]);
+  ASSERT_EQ("a", listener.ancestorId_);
+  ASSERT_EQ(ResourceType_Patient, listener.ancestorType_);
+
+  listener.Reset();
+  index.DeleteResource(a[6]);
+  ASSERT_EQ("", listener.ancestorId_);  // No more ancestor
+}
+
+
+TEST(DatabaseWrapper, PatientRecycling)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 10; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, 
+                                              "md5-" + boost::lexical_cast<std::string>(i)));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  listener.Reset();
+
+  index.DeleteResource(patients[5]);
+  index.DeleteResource(patients[0]);
+  ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder"));
+
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
+  ASSERT_EQ("Patient 5", listener.deletedFiles_[0]);
+  ASSERT_EQ("Patient 0", listener.deletedFiles_[1]);
+
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
+  index.DeleteResource(p);
+  index.DeleteResource(patients[8]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(10u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}
+
+
+TEST(DatabaseWrapper, PatientProtection)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 5; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10,
+                                              "md5-" + boost::lexical_cast<std::string>(i)));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[3], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[3]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, listener.deletedFiles_.size());
+
+  // Unprotecting a patient puts it at the last position in the recycling queue
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  // "patients[3]" is still protected
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(4u, listener.deletedFiles_.size());
+  ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  index.SetProtectedPatient(patients[3], false);
+  ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[3]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+
+  ASSERT_EQ(5u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}
+
+
+
+TEST(DatabaseWrapper, Sequence)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ(2u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
+}
+
+
+
+TEST(DatabaseWrapper, LookupTagValue)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  int64_t a[] = {
+    index.CreateResource("a", ResourceType_Study),   // 0
+    index.CreateResource("b", ResourceType_Study),   // 1
+    index.CreateResource("c", ResourceType_Study),   // 2
+    index.CreateResource("d", ResourceType_Series)   // 3
+  };
+
+  DicomMap m;
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[0], m);
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index.SetMainDicomTags(a[1], m);
+  m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[2], m);
+  m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index.SetMainDicomTags(a[3], m);
+
+  std::list<int64_t> s;
+
+  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  ASSERT_EQ(2u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+
+  index.LookupTagValue(s, "0");
+  ASSERT_EQ(3u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end());
+
+  index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+
+  index.LookupTagValue(s, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+
+
+  /*{
+      std::list<std::string> s;
+      context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059");
+      for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++)
+      {
+        std::cout << "*** " << *i << std::endl;;
+      }      
+      }*/
+}
+
+
+
+TEST(ServerIndex, AttachmentRecycling)
+{
+  const std::string path = "OrthancStorageUnitTests";
+  Toolbox::RemoveFile(path + "/index");
+  ServerContext context(path, ":memory:");   // The SQLite DB is in memory
+  ServerIndex& index = context.GetIndex();
+
+  index.SetMaximumStorageSize(10);
+
+  Json::Value tmp;
+  index.ComputeStatistics(tmp);
+  ASSERT_EQ(0, tmp["CountPatients"].asInt());
+  ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString()));
+
+  ServerIndex::Attachments attachments;
+
+  std::vector<std::string> ids;
+  for (int i = 0; i < 10; i++)
+  {
+    std::string id = boost::lexical_cast<std::string>(i);
+    DicomMap instance;
+    instance.SetValue(DICOM_TAG_PATIENT_ID, "patient-" + id);
+    instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id);
+    instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id);
+    instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id);
+    ASSERT_EQ(StoreStatus_Success, index.Store(instance, attachments, ""));
+
+    DicomInstanceHasher hasher(instance);
+    ids.push_back(hasher.HashPatient());
+    ids.push_back(hasher.HashStudy());
+    ids.push_back(hasher.HashSeries());
+    ids.push_back(hasher.HashInstance());
+  }
+
+  index.ComputeStatistics(tmp);
+  ASSERT_EQ(10, tmp["CountPatients"].asInt());
+  ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString()));
+
+  for (size_t i = 0; i < ids.size(); i++)
+  {
+    FileInfo info(Toolbox::GenerateUuid(), FileContentType_Dicom, 1, "md5");
+    index.AddAttachment(info, ids[i]);
+
+    index.ComputeStatistics(tmp);
+    ASSERT_GE(10, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString()));
+  }
+
+  // Because the DB is in memory, the SQLite index must not have been created
+  ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException);  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed Feb 19 12:39:17 2014 +0100
@@ -0,0 +1,610 @@
+#include "../Core/EnumerationDictionary.h"
+
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+
+#include "../Core/Compression/ZlibCompressor.h"
+#include "../Core/DicomFormat/DicomTag.h"
+#include "../Core/HttpServer/HttpHandler.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Toolbox.h"
+#include "../Core/Uuid.h"
+#include "../OrthancServer/FromDcmtkBridge.h"
+#include "../OrthancServer/OrthancInitialization.h"
+
+using namespace Orthanc;
+
+
+TEST(Uuid, Generation)
+{
+  for (int i = 0; i < 10; i++)
+  {
+    std::string s = Toolbox::GenerateUuid();
+    ASSERT_TRUE(Toolbox::IsUuid(s));
+  }
+}
+
+TEST(Uuid, Test)
+{
+  ASSERT_FALSE(Toolbox::IsUuid(""));
+  ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345"));
+  ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000"));
+  ASSERT_FALSE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-44665544000_"));
+  ASSERT_FALSE(Toolbox::IsUuid("01234567890123456789012345678901234_"));
+  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000"));
+  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000"));
+  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok"));
+  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok"));
+}
+
+TEST(Toolbox, IsSHA1)
+{
+  ASSERT_FALSE(Toolbox::IsSHA1(""));
+  ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123"));
+  ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234"));
+  ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9"));
+
+  std::string s;
+  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
+  ASSERT_TRUE(Toolbox::IsSHA1(s));
+  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
+
+  ASSERT_FALSE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b_"));
+}
+
+static void StringToVector(std::vector<uint8_t>& v,
+                           const std::string& s)
+{
+  v.resize(s.size());
+  for (size_t i = 0; i < s.size(); i++)
+    v[i] = s[i];
+}
+
+
+TEST(Zlib, Basic)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.Compress(compressed, s);
+
+  std::vector<uint8_t> v, vv;
+  StringToVector(v, s);
+  c.Compress(compressed2, v);
+  ASSERT_EQ(compressed, compressed2);
+
+  std::string uncompressed;
+  c.Uncompress(uncompressed, compressed);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+
+  StringToVector(vv, compressed);
+  c.Uncompress(uncompressed, vv);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+}
+
+
+TEST(Zlib, Level)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.SetCompressionLevel(9);
+  c.Compress(compressed, s);
+
+  c.SetCompressionLevel(0);
+  c.Compress(compressed2, s);
+
+  ASSERT_TRUE(compressed.size() < compressed2.size());
+}
+
+
+TEST(Zlib, Corrupted)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed;
+  ZlibCompressor c;
+  c.Compress(compressed, s);
+
+  compressed[compressed.size() - 1] = 'a';
+  std::string u;
+
+  ASSERT_THROW(c.Uncompress(u, compressed), OrthancException);
+}
+
+
+TEST(Zlib, Empty)
+{
+  std::string s = "";
+  std::vector<uint8_t> v, vv;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.Compress(compressed, s);
+  c.Compress(compressed2, v);
+  ASSERT_EQ(compressed, compressed2);
+
+  std::string uncompressed;
+  c.Uncompress(uncompressed, compressed);
+  ASSERT_EQ(0u, uncompressed.size());
+
+  StringToVector(vv, compressed);
+  c.Uncompress(uncompressed, vv);
+  ASSERT_EQ(0u, uncompressed.size());
+}
+
+
+TEST(ParseGetQuery, Basic)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c");
+  ASSERT_EQ(3u, a.size());
+  ASSERT_EQ(a["aaa"], "baaa");
+  ASSERT_EQ(a["bb"], "a");
+  ASSERT_EQ(a["aa"], "c");
+}
+
+TEST(ParseGetQuery, BasicEmpty)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa");
+  ASSERT_EQ(3u, a.size());
+  ASSERT_EQ(a["aaa"], "");
+  ASSERT_EQ(a["bb"], "aa");
+  ASSERT_EQ(a["aa"], "");
+}
+
+TEST(ParseGetQuery, Single)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa=baaa");
+  ASSERT_EQ(1u, a.size());
+  ASSERT_EQ(a["aaa"], "baaa");
+}
+
+TEST(ParseGetQuery, SingleEmpty)
+{
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(a, "aaa");
+  ASSERT_EQ(1u, a.size());
+  ASSERT_EQ(a["aaa"], "");
+}
+
+TEST(DicomFormat, Tag)
+{
+  ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
+
+  DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
+  ASSERT_EQ(0x0008, t.GetGroup());
+  ASSERT_EQ(0x103E, t.GetElement());
+
+  t = FromDcmtkBridge::ParseTag("0020-e040");
+  ASSERT_EQ(0x0020, t.GetGroup());
+  ASSERT_EQ(0xe040, t.GetElement());
+
+  // Test ==() and !=() operators
+  ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
+  ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
+}
+
+
+TEST(Uri, SplitUriComponents)
+{
+  UriComponents c;
+  Toolbox::SplitUriComponents(c, "/cou/hello/world");
+  ASSERT_EQ(3u, c.size());
+  ASSERT_EQ("cou", c[0]);
+  ASSERT_EQ("hello", c[1]);
+  ASSERT_EQ("world", c[2]);
+
+  Toolbox::SplitUriComponents(c, "/cou/hello/world/");
+  ASSERT_EQ(3u, c.size());
+  ASSERT_EQ("cou", c[0]);
+  ASSERT_EQ("hello", c[1]);
+  ASSERT_EQ("world", c[2]);
+
+  Toolbox::SplitUriComponents(c, "/cou/hello/world/a");
+  ASSERT_EQ(4u, c.size());
+  ASSERT_EQ("cou", c[0]);
+  ASSERT_EQ("hello", c[1]);
+  ASSERT_EQ("world", c[2]);
+  ASSERT_EQ("a", c[3]);
+
+  Toolbox::SplitUriComponents(c, "/");
+  ASSERT_EQ(0u, c.size());
+
+  Toolbox::SplitUriComponents(c, "/hello");
+  ASSERT_EQ(1u, c.size());
+  ASSERT_EQ("hello", c[0]);
+
+  Toolbox::SplitUriComponents(c, "/hello/");
+  ASSERT_EQ(1u, c.size());
+  ASSERT_EQ("hello", c[0]);
+
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException);
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException);
+  ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException);
+
+  c.clear();
+  c.push_back("test");
+  ASSERT_EQ("/", Toolbox::FlattenUri(c, 10));
+}
+
+
+TEST(Uri, Child)
+{
+  UriComponents c1;  Toolbox::SplitUriComponents(c1, "/hello/world");  
+  UriComponents c2;  Toolbox::SplitUriComponents(c2, "/hello/hello");  
+  UriComponents c3;  Toolbox::SplitUriComponents(c3, "/hello");  
+  UriComponents c4;  Toolbox::SplitUriComponents(c4, "/world");  
+  UriComponents c5;  Toolbox::SplitUriComponents(c5, "/");  
+
+  ASSERT_TRUE(Toolbox::IsChildUri(c1, c1));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c2));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c3));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c1, c5));
+
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c1));
+  ASSERT_TRUE(Toolbox::IsChildUri(c2, c2));
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c3));
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c2, c5));
+
+  ASSERT_TRUE(Toolbox::IsChildUri(c3, c1));
+  ASSERT_TRUE(Toolbox::IsChildUri(c3, c2));
+  ASSERT_TRUE(Toolbox::IsChildUri(c3, c3));
+  ASSERT_FALSE(Toolbox::IsChildUri(c3, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c3, c5));
+
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c1));
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c2));
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c3));
+  ASSERT_TRUE(Toolbox::IsChildUri(c4, c4));
+  ASSERT_FALSE(Toolbox::IsChildUri(c4, c5));
+
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c1));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c2));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c3));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c4));
+  ASSERT_TRUE(Toolbox::IsChildUri(c5, c5));
+}
+
+TEST(Uri, AutodetectMimeType)
+{
+  ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES"));
+  ASSERT_EQ("", Toolbox::AutodetectMimeType(""));
+  ASSERT_EQ("", Toolbox::AutodetectMimeType("/"));
+  ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a"));
+
+  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt"));
+  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt"));
+  ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml"));
+
+  ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js"));
+  ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json"));
+  ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf"));
+  ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css"));
+  ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html"));
+  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt"));
+  ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml"));
+  ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif"));
+  ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg"));
+  ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg"));
+  ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png"));
+}
+
+TEST(Toolbox, ComputeMD5)
+{
+  std::string s;
+
+  // # echo -n "Hello" | md5sum
+
+  Toolbox::ComputeMD5(s, "Hello");
+  ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s);
+  Toolbox::ComputeMD5(s, "");
+  ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s);
+}
+
+TEST(Toolbox, ComputeSHA1)
+{
+  std::string s;
+  
+  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
+  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
+  Toolbox::ComputeSHA1(s, "");
+  ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s);
+}
+
+
+TEST(Toolbox, Base64)
+{
+  ASSERT_EQ("", Toolbox::EncodeBase64(""));
+  ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a"));
+
+  const std::string hello = "SGVsbG8gd29ybGQ=";
+  ASSERT_EQ(hello, Toolbox::EncodeBase64("Hello world"));
+  ASSERT_EQ("Hello world", Toolbox::DecodeBase64(hello));
+}
+
+TEST(Toolbox, PathToExecutable)
+{
+  printf("[%s]\n", Toolbox::GetPathToExecutable().c_str());
+  printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str());
+}
+
+TEST(Toolbox, StripSpaces)
+{
+  ASSERT_EQ("", Toolbox::StripSpaces("       \t  \r   \n  "));
+  ASSERT_EQ("coucou", Toolbox::StripSpaces("    coucou   \t  \r   \n  "));
+  ASSERT_EQ("cou   cou", Toolbox::StripSpaces("    cou   cou    \n  "));
+  ASSERT_EQ("c", Toolbox::StripSpaces("    \n\t c\r    \n  "));
+}
+
+TEST(Toolbox, Case)
+{
+  std::string s = "CoU";
+  std::string ss;
+
+  Toolbox::ToUpperCase(ss, s);
+  ASSERT_EQ("COU", ss);
+  Toolbox::ToLowerCase(ss, s);
+  ASSERT_EQ("cou", ss); 
+
+  s = "CoU";
+  Toolbox::ToUpperCase(s);
+  ASSERT_EQ("COU", s);
+
+  s = "CoU";
+  Toolbox::ToLowerCase(s);
+  ASSERT_EQ("cou", s);
+}
+
+
+#include <glog/logging.h>
+
+TEST(Logger, Basic)
+{
+  LOG(INFO) << "I say hello";
+}
+
+TEST(Toolbox, ConvertFromLatin1)
+{
+  // This is a Latin-1 test string
+  const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 };
+  
+  /*FILE* f = fopen("/tmp/tutu", "w");
+  fwrite(&data[0], 9, 1, f);
+  fclose(f);*/
+
+  std::string s((char*) &data[0], 10);
+  ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s));
+
+  // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C"
+  std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1");
+  ASSERT_EQ(15u, utf8.size());
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0]));
+  ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2]));
+  ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4]));
+  ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6]));
+  ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7]));
+  ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8]));
+  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9]));
+  ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10]));
+  ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11]));
+  ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12]));
+  ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13]));
+  ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14]));  // Null-terminated string
+}
+
+TEST(Toolbox, UrlDecode)
+{
+  std::string s;
+
+  s = "Hello%20World";
+  Toolbox::UrlDecode(s);
+  ASSERT_EQ("Hello World", s);
+
+  s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff";
+  Toolbox::UrlDecode(s);
+  std::string ss = "!#$&'()*+,/:;=?@[]"; 
+  ss.push_back((char) 144); 
+  ss.push_back((char) 255);
+  ASSERT_EQ(ss, s);
+
+  s = "(2000%2C00A4)+Other";
+  Toolbox::UrlDecode(s);
+  ASSERT_EQ("(2000,00A4) Other", s);
+}
+
+
+#if defined(__linux)
+TEST(OrthancInitialization, AbsoluteDirectory)
+{
+  ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello"));
+  ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp"));
+}
+#endif
+
+
+
+#include "../OrthancServer/ServerEnumerations.h"
+
+TEST(EnumerationDictionary, Simple)
+{
+  Toolbox::EnumerationDictionary<MetadataType>  d;
+
+  ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException);
+  ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5"));
+  ASSERT_EQ(256, d.Translate("256"));
+
+  d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
+
+  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate"));
+  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2"));
+  ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate));
+
+  ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException);
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers
+  d.Add(MetadataType_ModifiedFrom, "ModifiedFrom");  // ok
+}
+
+
+TEST(EnumerationDictionary, ServerEnumerations)
+{
+  ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient));
+  ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study));
+  ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series));
+  ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance));
+
+  ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries));
+
+  ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure));
+  ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success));
+
+  ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries));
+
+  ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries));
+  ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate));
+
+  ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT"));
+  ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy"));
+  ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs"));
+  ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance"));
+  ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE"));
+  ASSERT_THROW(StringToResourceType("heLLo"), OrthancException);
+
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException);
+  ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024)
+  ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535)
+  RegisterUserMetadata(2047, "Ceci est un test");
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_EQ(2047, StringToMetadata("Ceci est un test"));
+}
+
+
+
+TEST(Toolbox, WriteFile)
+{
+  std::string path;
+
+  {
+    Toolbox::TemporaryFile tmp;
+    path = tmp.GetPath();
+
+    std::string s;
+    s.append("Hello");
+    s.push_back('\0');
+    s.append("World");
+    ASSERT_EQ(11u, s.size());
+
+    Toolbox::WriteFile(s, path.c_str());
+
+    std::string t;
+    Toolbox::ReadFile(t, path.c_str());
+
+    ASSERT_EQ(11u, t.size());
+    ASSERT_EQ(0, t[5]);
+    ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size()));
+  }
+
+  std::string u;
+  ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException);
+}
+
+
+TEST(Toolbox, Wildcard)
+{
+  ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd"));
+  ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd"));
+  ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd"));
+  ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d"));
+  ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]"));
+}
+
+
+TEST(Toolbox, Tokenize)
+{
+  std::vector<std::string> t;
+  
+  Toolbox::TokenizeString(t, "", ','); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("", t[0]);
+  
+  Toolbox::TokenizeString(t, "abc", ','); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("abc", t[0]);
+  
+  Toolbox::TokenizeString(t, "ab,cd,ef,", ','); 
+  ASSERT_EQ(4, t.size());
+  ASSERT_EQ("ab", t[0]);
+  ASSERT_EQ("cd", t[1]);
+  ASSERT_EQ("ef", t[2]);
+  ASSERT_EQ("", t[3]);
+}
+
+
+
+#if defined(__linux)
+#include <endian.h>
+#endif
+
+TEST(Toolbox, Endianness)
+{
+  // Parts of this test come from Adam Conrad
+  // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5
+
+#if defined(_WIN32)
+  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
+
+#elif defined(__linux)
+
+#if !defined(__BYTE_ORDER)
+#  error Support your platform here
+#endif
+
+#  if __BYTE_ORDER == __BIG_ENDIAN
+  ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness());
+#  else // __LITTLE_ENDIAN
+  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
+#  endif
+
+#else
+#error Support your platform here
+#endif
+}
+
+
+
+int main(int argc, char **argv)
+{
+  // Initialize Google's logging library.
+  FLAGS_logtostderr = true;
+  FLAGS_minloglevel = 0;
+
+  // Go to trace-level verbosity
+  //FLAGS_v = 1;
+
+  Toolbox::DetectEndianness();
+
+  google::InitGoogleLogging("Orthanc");
+
+  OrthancInitialize();
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS();
+  OrthancFinalize();
+  return result;
+}
--- a/UnitTestsSources/main.cpp	Wed Feb 19 11:01:55 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,610 +0,0 @@
-#include "../Core/EnumerationDictionary.h"
-
-#include "gtest/gtest.h"
-
-#include <ctype.h>
-
-#include "../Core/Compression/ZlibCompressor.h"
-#include "../Core/DicomFormat/DicomTag.h"
-#include "../Core/HttpServer/HttpHandler.h"
-#include "../Core/OrthancException.h"
-#include "../Core/Toolbox.h"
-#include "../Core/Uuid.h"
-#include "../OrthancServer/FromDcmtkBridge.h"
-#include "../OrthancServer/OrthancInitialization.h"
-
-using namespace Orthanc;
-
-
-TEST(Uuid, Generation)
-{
-  for (int i = 0; i < 10; i++)
-  {
-    std::string s = Toolbox::GenerateUuid();
-    ASSERT_TRUE(Toolbox::IsUuid(s));
-  }
-}
-
-TEST(Uuid, Test)
-{
-  ASSERT_FALSE(Toolbox::IsUuid(""));
-  ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345"));
-  ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000"));
-  ASSERT_FALSE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-44665544000_"));
-  ASSERT_FALSE(Toolbox::IsUuid("01234567890123456789012345678901234_"));
-  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000"));
-  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000"));
-  ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok"));
-  ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok"));
-}
-
-TEST(Toolbox, IsSHA1)
-{
-  ASSERT_FALSE(Toolbox::IsSHA1(""));
-  ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123"));
-  ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234"));
-  ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9"));
-
-  std::string s;
-  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
-  ASSERT_TRUE(Toolbox::IsSHA1(s));
-  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
-
-  ASSERT_FALSE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b_"));
-}
-
-static void StringToVector(std::vector<uint8_t>& v,
-                           const std::string& s)
-{
-  v.resize(s.size());
-  for (size_t i = 0; i < s.size(); i++)
-    v[i] = s[i];
-}
-
-
-TEST(Zlib, Basic)
-{
-  std::string s = Toolbox::GenerateUuid();
-  s = s + s + s + s;
- 
-  std::string compressed, compressed2;
-  ZlibCompressor c;
-  c.Compress(compressed, s);
-
-  std::vector<uint8_t> v, vv;
-  StringToVector(v, s);
-  c.Compress(compressed2, v);
-  ASSERT_EQ(compressed, compressed2);
-
-  std::string uncompressed;
-  c.Uncompress(uncompressed, compressed);
-  ASSERT_EQ(s.size(), uncompressed.size());
-  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
-
-  StringToVector(vv, compressed);
-  c.Uncompress(uncompressed, vv);
-  ASSERT_EQ(s.size(), uncompressed.size());
-  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
-}
-
-
-TEST(Zlib, Level)
-{
-  std::string s = Toolbox::GenerateUuid();
-  s = s + s + s + s;
- 
-  std::string compressed, compressed2;
-  ZlibCompressor c;
-  c.SetCompressionLevel(9);
-  c.Compress(compressed, s);
-
-  c.SetCompressionLevel(0);
-  c.Compress(compressed2, s);
-
-  ASSERT_TRUE(compressed.size() < compressed2.size());
-}
-
-
-TEST(Zlib, Corrupted)
-{
-  std::string s = Toolbox::GenerateUuid();
-  s = s + s + s + s;
- 
-  std::string compressed;
-  ZlibCompressor c;
-  c.Compress(compressed, s);
-
-  compressed[compressed.size() - 1] = 'a';
-  std::string u;
-
-  ASSERT_THROW(c.Uncompress(u, compressed), OrthancException);
-}
-
-
-TEST(Zlib, Empty)
-{
-  std::string s = "";
-  std::vector<uint8_t> v, vv;
- 
-  std::string compressed, compressed2;
-  ZlibCompressor c;
-  c.Compress(compressed, s);
-  c.Compress(compressed2, v);
-  ASSERT_EQ(compressed, compressed2);
-
-  std::string uncompressed;
-  c.Uncompress(uncompressed, compressed);
-  ASSERT_EQ(0u, uncompressed.size());
-
-  StringToVector(vv, compressed);
-  c.Uncompress(uncompressed, vv);
-  ASSERT_EQ(0u, uncompressed.size());
-}
-
-
-TEST(ParseGetQuery, Basic)
-{
-  HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c");
-  ASSERT_EQ(3u, a.size());
-  ASSERT_EQ(a["aaa"], "baaa");
-  ASSERT_EQ(a["bb"], "a");
-  ASSERT_EQ(a["aa"], "c");
-}
-
-TEST(ParseGetQuery, BasicEmpty)
-{
-  HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa");
-  ASSERT_EQ(3u, a.size());
-  ASSERT_EQ(a["aaa"], "");
-  ASSERT_EQ(a["bb"], "aa");
-  ASSERT_EQ(a["aa"], "");
-}
-
-TEST(ParseGetQuery, Single)
-{
-  HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa=baaa");
-  ASSERT_EQ(1u, a.size());
-  ASSERT_EQ(a["aaa"], "baaa");
-}
-
-TEST(ParseGetQuery, SingleEmpty)
-{
-  HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa");
-  ASSERT_EQ(1u, a.size());
-  ASSERT_EQ(a["aaa"], "");
-}
-
-TEST(DicomFormat, Tag)
-{
-  ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
-
-  DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
-  ASSERT_EQ(0x0008, t.GetGroup());
-  ASSERT_EQ(0x103E, t.GetElement());
-
-  t = FromDcmtkBridge::ParseTag("0020-e040");
-  ASSERT_EQ(0x0020, t.GetGroup());
-  ASSERT_EQ(0xe040, t.GetElement());
-
-  // Test ==() and !=() operators
-  ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
-  ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
-}
-
-
-TEST(Uri, SplitUriComponents)
-{
-  UriComponents c;
-  Toolbox::SplitUriComponents(c, "/cou/hello/world");
-  ASSERT_EQ(3u, c.size());
-  ASSERT_EQ("cou", c[0]);
-  ASSERT_EQ("hello", c[1]);
-  ASSERT_EQ("world", c[2]);
-
-  Toolbox::SplitUriComponents(c, "/cou/hello/world/");
-  ASSERT_EQ(3u, c.size());
-  ASSERT_EQ("cou", c[0]);
-  ASSERT_EQ("hello", c[1]);
-  ASSERT_EQ("world", c[2]);
-
-  Toolbox::SplitUriComponents(c, "/cou/hello/world/a");
-  ASSERT_EQ(4u, c.size());
-  ASSERT_EQ("cou", c[0]);
-  ASSERT_EQ("hello", c[1]);
-  ASSERT_EQ("world", c[2]);
-  ASSERT_EQ("a", c[3]);
-
-  Toolbox::SplitUriComponents(c, "/");
-  ASSERT_EQ(0u, c.size());
-
-  Toolbox::SplitUriComponents(c, "/hello");
-  ASSERT_EQ(1u, c.size());
-  ASSERT_EQ("hello", c[0]);
-
-  Toolbox::SplitUriComponents(c, "/hello/");
-  ASSERT_EQ(1u, c.size());
-  ASSERT_EQ("hello", c[0]);
-
-  ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException);
-  ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException);
-  ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException);
-
-  c.clear();
-  c.push_back("test");
-  ASSERT_EQ("/", Toolbox::FlattenUri(c, 10));
-}
-
-
-TEST(Uri, Child)
-{
-  UriComponents c1;  Toolbox::SplitUriComponents(c1, "/hello/world");  
-  UriComponents c2;  Toolbox::SplitUriComponents(c2, "/hello/hello");  
-  UriComponents c3;  Toolbox::SplitUriComponents(c3, "/hello");  
-  UriComponents c4;  Toolbox::SplitUriComponents(c4, "/world");  
-  UriComponents c5;  Toolbox::SplitUriComponents(c5, "/");  
-
-  ASSERT_TRUE(Toolbox::IsChildUri(c1, c1));
-  ASSERT_FALSE(Toolbox::IsChildUri(c1, c2));
-  ASSERT_FALSE(Toolbox::IsChildUri(c1, c3));
-  ASSERT_FALSE(Toolbox::IsChildUri(c1, c4));
-  ASSERT_FALSE(Toolbox::IsChildUri(c1, c5));
-
-  ASSERT_FALSE(Toolbox::IsChildUri(c2, c1));
-  ASSERT_TRUE(Toolbox::IsChildUri(c2, c2));
-  ASSERT_FALSE(Toolbox::IsChildUri(c2, c3));
-  ASSERT_FALSE(Toolbox::IsChildUri(c2, c4));
-  ASSERT_FALSE(Toolbox::IsChildUri(c2, c5));
-
-  ASSERT_TRUE(Toolbox::IsChildUri(c3, c1));
-  ASSERT_TRUE(Toolbox::IsChildUri(c3, c2));
-  ASSERT_TRUE(Toolbox::IsChildUri(c3, c3));
-  ASSERT_FALSE(Toolbox::IsChildUri(c3, c4));
-  ASSERT_FALSE(Toolbox::IsChildUri(c3, c5));
-
-  ASSERT_FALSE(Toolbox::IsChildUri(c4, c1));
-  ASSERT_FALSE(Toolbox::IsChildUri(c4, c2));
-  ASSERT_FALSE(Toolbox::IsChildUri(c4, c3));
-  ASSERT_TRUE(Toolbox::IsChildUri(c4, c4));
-  ASSERT_FALSE(Toolbox::IsChildUri(c4, c5));
-
-  ASSERT_TRUE(Toolbox::IsChildUri(c5, c1));
-  ASSERT_TRUE(Toolbox::IsChildUri(c5, c2));
-  ASSERT_TRUE(Toolbox::IsChildUri(c5, c3));
-  ASSERT_TRUE(Toolbox::IsChildUri(c5, c4));
-  ASSERT_TRUE(Toolbox::IsChildUri(c5, c5));
-}
-
-TEST(Uri, AutodetectMimeType)
-{
-  ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES"));
-  ASSERT_EQ("", Toolbox::AutodetectMimeType(""));
-  ASSERT_EQ("", Toolbox::AutodetectMimeType("/"));
-  ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a"));
-
-  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt"));
-  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt"));
-  ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml"));
-
-  ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js"));
-  ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json"));
-  ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf"));
-  ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css"));
-  ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html"));
-  ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt"));
-  ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml"));
-  ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif"));
-  ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg"));
-  ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg"));
-  ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png"));
-}
-
-TEST(Toolbox, ComputeMD5)
-{
-  std::string s;
-
-  // # echo -n "Hello" | md5sum
-
-  Toolbox::ComputeMD5(s, "Hello");
-  ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s);
-  Toolbox::ComputeMD5(s, "");
-  ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s);
-}
-
-TEST(Toolbox, ComputeSHA1)
-{
-  std::string s;
-  
-  Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog");
-  ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s);
-  Toolbox::ComputeSHA1(s, "");
-  ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s);
-}
-
-
-TEST(Toolbox, Base64)
-{
-  ASSERT_EQ("", Toolbox::EncodeBase64(""));
-  ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a"));
-
-  const std::string hello = "SGVsbG8gd29ybGQ=";
-  ASSERT_EQ(hello, Toolbox::EncodeBase64("Hello world"));
-  ASSERT_EQ("Hello world", Toolbox::DecodeBase64(hello));
-}
-
-TEST(Toolbox, PathToExecutable)
-{
-  printf("[%s]\n", Toolbox::GetPathToExecutable().c_str());
-  printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str());
-}
-
-TEST(Toolbox, StripSpaces)
-{
-  ASSERT_EQ("", Toolbox::StripSpaces("       \t  \r   \n  "));
-  ASSERT_EQ("coucou", Toolbox::StripSpaces("    coucou   \t  \r   \n  "));
-  ASSERT_EQ("cou   cou", Toolbox::StripSpaces("    cou   cou    \n  "));
-  ASSERT_EQ("c", Toolbox::StripSpaces("    \n\t c\r    \n  "));
-}
-
-TEST(Toolbox, Case)
-{
-  std::string s = "CoU";
-  std::string ss;
-
-  Toolbox::ToUpperCase(ss, s);
-  ASSERT_EQ("COU", ss);
-  Toolbox::ToLowerCase(ss, s);
-  ASSERT_EQ("cou", ss); 
-
-  s = "CoU";
-  Toolbox::ToUpperCase(s);
-  ASSERT_EQ("COU", s);
-
-  s = "CoU";
-  Toolbox::ToLowerCase(s);
-  ASSERT_EQ("cou", s);
-}
-
-
-#include <glog/logging.h>
-
-TEST(Logger, Basic)
-{
-  LOG(INFO) << "I say hello";
-}
-
-TEST(Toolbox, ConvertFromLatin1)
-{
-  // This is a Latin-1 test string
-  const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 };
-  
-  /*FILE* f = fopen("/tmp/tutu", "w");
-  fwrite(&data[0], 9, 1, f);
-  fclose(f);*/
-
-  std::string s((char*) &data[0], 10);
-  ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s));
-
-  // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C"
-  std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1");
-  ASSERT_EQ(15u, utf8.size());
-  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0]));
-  ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1]));
-  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2]));
-  ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3]));
-  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4]));
-  ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5]));
-  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6]));
-  ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7]));
-  ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8]));
-  ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9]));
-  ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10]));
-  ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11]));
-  ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12]));
-  ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13]));
-  ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14]));  // Null-terminated string
-}
-
-TEST(Toolbox, UrlDecode)
-{
-  std::string s;
-
-  s = "Hello%20World";
-  Toolbox::UrlDecode(s);
-  ASSERT_EQ("Hello World", s);
-
-  s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff";
-  Toolbox::UrlDecode(s);
-  std::string ss = "!#$&'()*+,/:;=?@[]"; 
-  ss.push_back((char) 144); 
-  ss.push_back((char) 255);
-  ASSERT_EQ(ss, s);
-
-  s = "(2000%2C00A4)+Other";
-  Toolbox::UrlDecode(s);
-  ASSERT_EQ("(2000,00A4) Other", s);
-}
-
-
-#if defined(__linux)
-TEST(OrthancInitialization, AbsoluteDirectory)
-{
-  ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello"));
-  ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp"));
-}
-#endif
-
-
-
-#include "../OrthancServer/ServerEnumerations.h"
-
-TEST(EnumerationDictionary, Simple)
-{
-  Toolbox::EnumerationDictionary<MetadataType>  d;
-
-  ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException);
-  ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5"));
-  ASSERT_EQ(256, d.Translate("256"));
-
-  d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
-
-  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate"));
-  ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2"));
-  ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate));
-
-  ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException);
-  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used
-  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers
-  d.Add(MetadataType_ModifiedFrom, "ModifiedFrom");  // ok
-}
-
-
-TEST(EnumerationDictionary, ServerEnumerations)
-{
-  ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient));
-  ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study));
-  ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series));
-  ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance));
-
-  ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries));
-
-  ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure));
-  ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success));
-
-  ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries));
-
-  ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries));
-  ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate));
-
-  ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT"));
-  ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy"));
-  ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs"));
-  ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance"));
-  ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE"));
-  ASSERT_THROW(StringToResourceType("heLLo"), OrthancException);
-
-  ASSERT_EQ(2047, StringToMetadata("2047"));
-  ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException);
-  ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024)
-  ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535)
-  RegisterUserMetadata(2047, "Ceci est un test");
-  ASSERT_EQ(2047, StringToMetadata("2047"));
-  ASSERT_EQ(2047, StringToMetadata("Ceci est un test"));
-}
-
-
-
-TEST(Toolbox, WriteFile)
-{
-  std::string path;
-
-  {
-    Toolbox::TemporaryFile tmp;
-    path = tmp.GetPath();
-
-    std::string s;
-    s.append("Hello");
-    s.push_back('\0');
-    s.append("World");
-    ASSERT_EQ(11u, s.size());
-
-    Toolbox::WriteFile(s, path.c_str());
-
-    std::string t;
-    Toolbox::ReadFile(t, path.c_str());
-
-    ASSERT_EQ(11u, t.size());
-    ASSERT_EQ(0, t[5]);
-    ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size()));
-  }
-
-  std::string u;
-  ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException);
-}
-
-
-TEST(Toolbox, Wildcard)
-{
-  ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd"));
-  ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd"));
-  ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd"));
-  ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d"));
-  ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]"));
-}
-
-
-TEST(Toolbox, Tokenize)
-{
-  std::vector<std::string> t;
-  
-  Toolbox::TokenizeString(t, "", ','); 
-  ASSERT_EQ(1, t.size());
-  ASSERT_EQ("", t[0]);
-  
-  Toolbox::TokenizeString(t, "abc", ','); 
-  ASSERT_EQ(1, t.size());
-  ASSERT_EQ("abc", t[0]);
-  
-  Toolbox::TokenizeString(t, "ab,cd,ef,", ','); 
-  ASSERT_EQ(4, t.size());
-  ASSERT_EQ("ab", t[0]);
-  ASSERT_EQ("cd", t[1]);
-  ASSERT_EQ("ef", t[2]);
-  ASSERT_EQ("", t[3]);
-}
-
-
-
-#if defined(__linux)
-#include <endian.h>
-#endif
-
-TEST(Toolbox, Endianness)
-{
-  // Parts of this test come from Adam Conrad
-  // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5
-
-#if defined(_WIN32)
-  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
-
-#elif defined(__linux)
-
-#if !defined(__BYTE_ORDER)
-#  error Support your platform here
-#endif
-
-#  if __BYTE_ORDER == __BIG_ENDIAN
-  ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness());
-#  else // __LITTLE_ENDIAN
-  ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
-#  endif
-
-#else
-#error Support your platform here
-#endif
-}
-
-
-
-int main(int argc, char **argv)
-{
-  // Initialize Google's logging library.
-  FLAGS_logtostderr = true;
-  FLAGS_minloglevel = 0;
-
-  // Go to trace-level verbosity
-  //FLAGS_v = 1;
-
-  Toolbox::DetectEndianness();
-
-  google::InitGoogleLogging("Orthanc");
-
-  OrthancInitialize();
-  ::testing::InitGoogleTest(&argc, argv);
-  int result = RUN_ALL_TESTS();
-  OrthancFinalize();
-  return result;
-}