changeset 391:227d9a932467

testing revisions in metadata
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 16 Apr 2021 17:13:53 +0200
parents 5c2472f008eb
children 5cbcb4a83b41
files GenerateConfigurationForTests.py Tests/Tests.py Tests/Toolbox.py
diffstat 3 files changed, 189 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/GenerateConfigurationForTests.py	Mon Mar 08 16:59:25 2021 +0100
+++ b/GenerateConfigurationForTests.py	Fri Apr 16 17:13:53 2021 +0200
@@ -149,6 +149,7 @@
 config['WebDavDeleteAllowed'] = True
 config['WebDavUploadAllowed'] = True
 config['StorageCompression'] = args.compression
+config['CheckRevisions'] = True
 
 del config['KeepAlive']
 
--- a/Tests/Tests.py	Mon Mar 08 16:59:25 2021 +0100
+++ b/Tests/Tests.py	Fri Apr 16 17:13:53 2021 +0200
@@ -1126,15 +1126,26 @@
         self.assertEqual(DoGet(_REMOTE, '/instances/%s/metadata/SopClassUid' % i), '1.2.840.10008.5.1.4.1.1.4')
 
         # Play with custom metadata
-        DoPut(_REMOTE, '/patients/%s/metadata/5555' % p, 'coucou')
+        (headers, body) = DoPutRaw(_REMOTE, '/patients/%s/metadata/5555' % p, 'coucou')
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('', body)
+        self.assertEqual('"0"', headers['etag'])
+        
         m = DoGet(_REMOTE, '/patients/%s/metadata' % p)
         self.assertEqual(2, len(m))
         self.assertTrue('LastUpdate' in m)
         self.assertTrue('5555' in m)
         self.assertEqual('coucou', DoGet(_REMOTE, '/patients/%s/metadata/5555' % p))
-        DoPut(_REMOTE, '/patients/%s/metadata/5555' % p, 'hello')
-        self.assertEqual('hello', DoGet(_REMOTE, '/patients/%s/metadata/5555' % p))
-        DoDelete(_REMOTE, '/patients/%s/metadata/5555' % p)
+        DoPut(_REMOTE, '/patients/%s/metadata/5555' % p, 'hello', headers = {
+            'If-Match' : headers['etag']
+        })
+
+        (headers, body) = DoGetRaw(_REMOTE, '/patients/%s/metadata/5555' % p)
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('hello', body)
+        DoDelete(_REMOTE, '/patients/%s/metadata/5555' % p, headers = {
+            'If-Match' : headers['etag']
+        })
         m = DoGet(_REMOTE, '/patients/%s/metadata' % p)
         self.assertEqual(1, len(m))
         self.assertTrue('LastUpdate' in m)
@@ -4641,7 +4652,10 @@
         # changes of type "UpdatedMetadata"
         i = DoGet(_REMOTE, '/instances') [0]
         DoPut(_REMOTE, '/instances/%s/metadata/4000' % i, 'hello', 'text/plain')
-        self.assertEqual('hello', DoGet(_REMOTE, '/instances/%s/metadata/4000' % i))
+
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/4000' % i)
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('hello', body)
 
         c = DoGet(_REMOTE, '/changes?last')
         self.assertEqual(1, len(c['Changes']))
@@ -4649,7 +4663,9 @@
         self.assertEqual(seq + 5, c['Last'])
         self.assertEqual('UpdatedMetadata', c['Changes'][0]['ChangeType'])
 
-        DoDelete(_REMOTE, '/instances/%s/metadata/4000' % i)
+        DoDelete(_REMOTE, '/instances/%s/metadata/4000' % i, headers = {
+            'If-Match' : headers['etag']
+        })
         c = DoGet(_REMOTE, '/changes?last')
         self.assertEqual(1, len(c['Changes']))
         self.assertTrue(c['Done'])
@@ -6556,3 +6572,141 @@
         instance = DoGet(_REMOTE, '/instances/%s' % a)
         self.assertEqual(tags['0008,0018'], instance['MainDicomTags']['SOPInstanceUID'])
 
+
+    def test_revisions(self):
+        # This test fails on Orthanc <= 1.9.1 (support for revisions
+        # was introduced in 1.9.2), or if configuration option
+        # "CheckRevisions" is "False". Conventions for HTTP headers
+        # related to revisions mimic CouchDB:
+        # https://docs.couchdb.org/en/stable/api/document/common.html
+        i = UploadInstance(_REMOTE, 'DummyCT.dcm') ['ID']
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/TransferSyntax' % i)
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('1.2.840.10008.1.2.4.70', body)
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/TransferSyntax' % i, headers = {
+            'If-None-Match' : '"0"'
+        })
+        self.assertEqual('304', headers['status'])  # Not modified
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('', body)  # Body must be empty on 304 status
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/TransferSyntax' % i, headers = {
+            'If-None-Match' : '"1"'
+        })
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('1.2.840.10008.1.2.4.70', body)
+        
+        (headers, body) = DoDeleteRaw(_REMOTE, '/instances/%s/metadata/TransferSyntax' % i)
+        self.assertEqual('403', headers['status'])  # Forbidden (system metadata)
+        
+        (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/TransferSyntax' % i, 'hello')
+        self.assertEqual('403', headers['status'])  # Forbidden (system metadata)
+        
+        (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello')
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/1024' % i)
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('hello', body)
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/1024' % i, headers = {
+            'If-None-Match' : '"0"'
+        })
+        self.assertEqual('304', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('', body)
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/1024' % i, headers = {
+            'If-None-Match' : '"1"'
+        })
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('hello', body)
+        self.assertEqual('hello', DoGet(_REMOTE, '/instances/%s/metadata/1024' % i))
+        
+        (headers, body) = DoDeleteRaw(_REMOTE, '/instances/%s/metadata/1024' % i)
+        self.assertEqual('409', headers['status'])  # No revision given, but "CheckRevisions" is True
+        
+        (headers, body) = DoDeleteRaw(_REMOTE, '/instances/%s/metadata/1024' % i, headers = {
+            'If-Match' : '45'
+        })
+        self.assertEqual('409', headers['status'])  # Conflict, as bad revision
+        
+        (headers, body) = DoDeleteRaw(_REMOTE, '/instances/%s/metadata/1024' % i, headers = {
+            'If-Match' : '0'
+        })
+        self.assertEqual('200', headers['status'])
+
+        (headers, body) = DoDeleteRaw(_REMOTE, '/instances/%s/metadata/1024' % i, headers = {
+            'If-Match' : '0'
+        })
+        self.assertEqual('404', headers['status'])
+
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/1024' % i)
+        self.assertEqual('404', headers['status'])
+
+        self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/instances/%s/metadata/1024' % i))
+
+        (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello')
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"0"', headers['etag'])
+
+        (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello')
+        self.assertEqual('409', headers['status'])
+
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/TransferSyntax' % i, headers = {
+            'If-None-Match' : '"0"'
+        })
+        self.assertEqual('304', headers['status'])  # Not modified
+        self.assertEqual('"0"', headers['etag'])
+        self.assertEqual('', body)  # Body must be empty on 304 status
+
+        (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello', headers = {
+            'If-Match' : '0'            
+        })
+        self.assertEqual('200', headers['status'])
+        self.assertEqual('"1"', headers['etag'])
+        self.assertEqual('', body)
+        
+        (headers, body) = DoGetRaw(_REMOTE, '/instances/%s/metadata/1024' % i, headers = {
+            'If-None-Match' : headers['etag']
+        })
+
+        if headers['status'] == '200':
+            print("Your database backend doesn't store revisions")
+            (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello2', headers = {
+                'If-Match' : '1'
+            })
+            self.assertEqual('409', headers['status'])
+
+            (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello2', headers = {
+                'If-Match' : '0'
+            })
+            self.assertEqual('200', headers['status'])
+            self.assertEqual('"1"', headers['etag'])
+            self.assertEqual('', body)
+
+        elif headers['status'] == '304':  # Revisions are supported
+            (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello2', headers = {
+                'If-Match' : '0'
+            })
+            self.assertEqual('409', headers['status'])
+
+            (headers, body) = DoPutRaw(_REMOTE, '/instances/%s/metadata/1024' % i, 'hello2', headers = {
+                'If-Match' : '1'
+            })
+            self.assertEqual('200', headers['status'])
+            self.assertEqual('"2"', headers['etag'])
+            self.assertEqual('', body)
+        
+        else:
+            raise Exception('Internal error')
+
+        self.assertEqual('hello2', DoGet(_REMOTE, '/instances/%s/metadata/1024' % i))
+        
--- a/Tests/Toolbox.py	Mon Mar 08 16:59:25 2021 +0100
+++ b/Tests/Toolbox.py	Fri Apr 16 17:13:53 2021 +0200
@@ -141,28 +141,43 @@
     resp, content = http.request(orthanc['Url'] + uri, method,
                                  body = body,
                                  headers = headers)
+    return (resp, content)
+
+def DoDeleteRaw(orthanc, uri, headers = {}):
+    http = httplib2.Http()
+    http.follow_redirects = False
+    _SetupCredentials(orthanc, http)
+
+    resp, content = http.request(orthanc['Url'] + uri, 'DELETE', headers = headers)
+    return (resp, content)
+
+def DoDelete(orthanc, uri, headers = {}):
+    (resp, content) = DoDeleteRaw(orthanc, uri, headers)
+    if not (resp.status in [ 200 ]):
+        raise Exception(resp.status, resp)
+    else:
+        return _DecodeJson(content)
+
+def DoPutRaw(orthanc, uri, data = {}, contentType = '', headers = {}):
+    return _DoPutOrPost(orthanc, uri, 'PUT', data, contentType, headers)
+
+def DoPut(orthanc, uri, data = {}, contentType = '', headers = {}):
+    (resp, content) = DoPutRaw(orthanc, uri, data, contentType, headers)
     if not (resp.status in [ 200, 201, 302 ]):
         raise Exception(resp.status, resp)
     else:
         return _DecodeJson(content)
 
-def DoDelete(orthanc, uri):
-    http = httplib2.Http()
-    http.follow_redirects = False
-    _SetupCredentials(orthanc, http)
-
-    resp, content = http.request(orthanc['Url'] + uri, 'DELETE')
-    if not (resp.status in [ 200 ]):
+def DoPostRaw(orthanc, uri, data = {}, contentType = '', headers = {}):
+    return _DoPutOrPost(orthanc, uri, 'POST', data, contentType, headers)
+    
+def DoPost(orthanc, uri, data = {}, contentType = '', headers = {}):
+    (resp, content) = DoPostRaw(orthanc, uri, data, contentType, headers)
+    if not (resp.status in [ 200, 201, 302 ]):
         raise Exception(resp.status, resp)
     else:
         return _DecodeJson(content)
 
-def DoPut(orthanc, uri, data = {}, contentType = ''):
-    return _DoPutOrPost(orthanc, uri, 'PUT', data, contentType, {})
-
-def DoPost(orthanc, uri, data = {}, contentType = '', headers = {}):
-    return _DoPutOrPost(orthanc, uri, 'POST', data, contentType, headers)
-
 def GetDatabasePath(filename):
     return os.path.join(os.path.dirname(__file__), '..', 'Database', filename)