From 9f27930e95401764c76c3b766f2ac3627e95106b Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Wed, 19 Feb 2025 12:01:50 +0100
Subject: [PATCH] WIP

Signed-off-by: Martin Weise <martin.weise@tuwien.ac.at>
---
 .gitlab-ci.yml                                |   2 +-
 .../at/tuwien/endpoints/SubsetEndpoint.java   |   3 +-
 .../database/table/HistoryEventTypeDto.java   |  11 +-
 .../api/database/table/TableHistoryDto.java   |   3 +-
 .../at/tuwien/endpoints/DatabaseEndpoint.java |  39 +-
 .../at/tuwien/endpoints/TableEndpoint.java    |  17 +-
 .../at/tuwien/endpoints/UserEndpoint.java     |  23 +-
 lib/python/dbrepo/RestClient.py               | 119 ++--
 lib/python/dbrepo/api/dto.py                  |  40 +-
 lib/python/tests/test_unit_concept.py         |  35 ++
 lib/python/tests/test_unit_database.py        | 115 ++++
 lib/python/tests/test_unit_identifier.py      | 555 +++++++++++++++++-
 lib/python/tests/test_unit_jwt.py             |  65 --
 lib/python/tests/test_unit_license.py         |  12 +-
 lib/python/tests/test_unit_query.py           | 291 ++++++++-
 lib/python/tests/test_unit_rest_client.py     |  11 +
 lib/python/tests/test_unit_table.py           |  65 +-
 lib/python/tests/test_unit_view.py            |  40 ++
 18 files changed, 1185 insertions(+), 261 deletions(-)
 create mode 100644 lib/python/tests/test_unit_concept.py
 delete mode 100644 lib/python/tests/test_unit_jwt.py

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 65f52bea38..2d26738384 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -403,7 +403,7 @@ test-lib:
   script:
     - "pip install pipenv"
     - "pipenv install gunicorn && pipenv install --dev --system --deploy"
-    - cd ./lib/python/ && coverage run -m pytest tests/test_unit_analyse.py tests/test_unit_container.py tests/test_unit_database.py tests/test_unit_identifier.py tests/test_unit_license.py tests/test_unit_query.py tests/test_unit_rest_client.py tests/test_unit_table.py tests/test_unit_user.py tests/test_unit_view.py tests/test_unit_rest_client.py --junitxml=report.xml && coverage html --omit="tests/*" && coverage report --omit="tests/*" > ./coverage.txt
+    - cd ./lib/python/ && coverage run -m pytest tests/test_unit_analyse.py tests/test_unit_container.py tests/test_unit_concept.py tests/test_unit_database.py tests/test_unit_identifier.py tests/test_unit_license.py tests/test_unit_query.py tests/test_unit_rest_client.py tests/test_unit_table.py tests/test_unit_user.py tests/test_unit_view.py tests/test_unit_unit.py tests/test_integration_table.py --junitxml=report.xml && coverage html --omit="tests/*" && coverage report --omit="tests/*" > ./coverage.txt
     - "cat ./coverage.txt | grep -o 'TOTAL[^%]*%'"
   artifacts:
     when: always
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
index 90cb9846b2..7f1f516495 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
@@ -341,7 +341,8 @@ public class SubsetEndpoint extends RestEndpoint {
                                                              @RequestParam(required = false) Long size)
             throws PaginationException, DatabaseNotFoundException, RemoteUnavailableException, NotAllowedException,
             QueryNotFoundException, DatabaseUnavailableException, TableMalformedException, QueryMalformedException,
-            UserNotFoundException, MetadataServiceException, TableNotFoundException, ViewNotFoundException, ViewMalformedException {
+            UserNotFoundException, MetadataServiceException, TableNotFoundException, ViewNotFoundException,
+            ViewMalformedException {
         log.debug("endpoint get subset data, databaseId={}, subsetId={}, principal.name={} page={}, size={}",
                 databaseId, subsetId, principal != null ? principal.getName() : null, page, size);
         endpointValidator.validateDataParams(page, size);
diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/HistoryEventTypeDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/HistoryEventTypeDto.java
index 8912295add..83d8441752 100644
--- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/HistoryEventTypeDto.java
+++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/HistoryEventTypeDto.java
@@ -8,14 +8,11 @@ import lombok.Getter;
 @Schema
 public enum HistoryEventTypeDto {
 
-    @JsonProperty("read")
-    READ("read"),
+    @JsonProperty("insert")
+    INSERT("insert"),
 
-    @JsonProperty("write_own")
-    WRITE_OWN("write_own"),
-
-    @JsonProperty("write_all")
-    WRITE_ALL("write_all");
+    @JsonProperty("delete")
+    DELETE("delete");
 
     private String name;
 
diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableHistoryDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableHistoryDto.java
index 35df29430f..e6ed667235 100644
--- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableHistoryDto.java
+++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableHistoryDto.java
@@ -24,7 +24,8 @@ public class TableHistoryDto {
     private Instant timestamp;
 
     @NotNull
-    private String event;
+    @Schema(example = "INSERT")
+    private HistoryEventTypeDto event;
 
     @NotNull
     @Schema(example = "1")
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
index 41cc59ca6a..ccfd23e1ce 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
@@ -44,16 +44,16 @@ import java.util.Optional;
 public class DatabaseEndpoint extends AbstractEndpoint {
 
     private final UserService userService;
-    private final MetadataMapper databaseMapper;
+    private final MetadataMapper metadataMapper;
     private final StorageService storageService;
     private final DatabaseService databaseService;
     private final ContainerService containerService;
 
     @Autowired
-    public DatabaseEndpoint(UserService userService, MetadataMapper databaseMapper, StorageService storageService,
+    public DatabaseEndpoint(UserService userService, MetadataMapper metadataMapper, StorageService storageService,
                             DatabaseService databaseService, ContainerService containerService) {
         this.userService = userService;
-        this.databaseMapper = databaseMapper;
+        this.metadataMapper = metadataMapper;
         this.storageService = storageService;
         this.databaseService = databaseService;
         this.containerService = containerService;
@@ -100,7 +100,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
         return ResponseEntity.status(HttpStatus.OK)
                 .headers(headers)
                 .body(databases.stream()
-                        .map(databaseMapper::databaseToDatabaseBriefDto)
+                        .map(metadataMapper::databaseToDatabaseBriefDto)
                         .toList());
     }
 
@@ -166,7 +166,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
         }
         final User caller = userService.findById(getId(principal));
         return ResponseEntity.status(HttpStatus.CREATED)
-                .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto(
+                .body(metadataMapper.databaseDtoToDatabaseBriefDto(metadataMapper.databaseToDatabaseDto(
                         databaseService.create(container, data, caller, userService.findAllInternalUsers()))));
     }
 
@@ -220,7 +220,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             log.error("Failed to refresh database tables metadata: not owner");
             throw new NotAllowedException("Failed to refresh tables metadata: not owner");
         }
-        return ResponseEntity.ok(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto(
+        return ResponseEntity.ok(metadataMapper.databaseDtoToDatabaseBriefDto(metadataMapper.databaseToDatabaseDto(
                 databaseService.updateTableMetadata(database))));
     }
 
@@ -268,7 +268,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             log.error("Failed to refresh database views metadata: not owner");
             throw new NotAllowedException("Failed to refresh database views metadata: not owner");
         }
-        return ResponseEntity.ok(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto(
+        return ResponseEntity.ok(metadataMapper.databaseDtoToDatabaseBriefDto(metadataMapper.databaseToDatabaseDto(
                 databaseService.updateViewMetadata(database))));
     }
 
@@ -322,7 +322,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             throw new NotAllowedException("Failed to modify database visibility: not owner");
         }
         return ResponseEntity.accepted()
-                .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto(
+                .body(metadataMapper.databaseDtoToDatabaseBriefDto(metadataMapper.databaseToDatabaseDto(
                         databaseService.modifyVisibility(database, data))));
     }
 
@@ -378,7 +378,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             throw new NotAllowedException("Failed to transfer database: not owner");
         }
         return ResponseEntity.accepted()
-                .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto(
+                .body(metadataMapper.databaseDtoToDatabaseBriefDto(metadataMapper.databaseToDatabaseDto(
                         databaseService.modifyOwner(database, newOwner))));
     }
 
@@ -437,7 +437,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             image = storageService.getBytes(data.getKey());
         }
         return ResponseEntity.accepted()
-                .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto(
+                .body(metadataMapper.databaseDtoToDatabaseBriefDto(metadataMapper.databaseToDatabaseDto(
                         databaseService.modifyImage(database, image))));
     }
 
@@ -485,24 +485,13 @@ public class DatabaseEndpoint extends AbstractEndpoint {
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
             @ApiResponse(responseCode = "404",
-                    description = "Database, user or exchange could not be found",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "502",
-                    description = "Connection to the broker service could not be established",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "503",
-                    description = "Failed to find queue information in broker service",
+                    description = "Database could not be found",
                     content = {@Content(
                             mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
+                            schema = @Schema(implementation = ApiErrorDto.class))})
     })
     public ResponseEntity<DatabaseDto> findById(@NotNull @PathVariable("databaseId") Long databaseId,
-                                                Principal principal) throws DataServiceException,
-            DataServiceConnectionException, DatabaseNotFoundException, ExchangeNotFoundException, UserNotFoundException,
+                                                Principal principal) throws DatabaseNotFoundException,
             NotAllowedException {
         log.debug("endpoint find database, databaseId={}", databaseId);
         final Database database = databaseService.findById(databaseId);
@@ -553,7 +542,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
                             .toList());
             database.setAccesses(List.of());
         }
-        final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(database);
+        final DatabaseDto dto = metadataMapper.databaseToDatabaseDto(database);
         final HttpHeaders headers = new HttpHeaders();
         if (isSystem(principal)) {
             headers.set("X-Username", database.getContainer().getPrivilegedUsername());
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
index 08535fde69..32075fb5d8 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
@@ -1,7 +1,7 @@
 package at.tuwien.endpoints;
 
-import at.tuwien.api.database.table.TableBriefDto;
 import at.tuwien.api.database.table.CreateTableDto;
+import at.tuwien.api.database.table.TableBriefDto;
 import at.tuwien.api.database.table.TableDto;
 import at.tuwien.api.database.table.TableUpdateDto;
 import at.tuwien.api.database.table.columns.ColumnDto;
@@ -457,22 +457,11 @@ public class TableEndpoint extends AbstractEndpoint {
                     content = {@Content(
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "502",
-                    description = "Failed to establish connection with broker service",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "503",
-                    description = "Failed to obtain queue information from broker service",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
     })
     public ResponseEntity<TableDto> findById(@NotNull @PathVariable("databaseId") Long databaseId,
                                              @NotNull @PathVariable("tableId") Long tableId,
-                                             Principal principal) throws DataServiceException,
-            DataServiceConnectionException, TableNotFoundException, DatabaseNotFoundException, QueueNotFoundException,
-            UserNotFoundException, NotAllowedException, AccessNotFoundException {
+                                             Principal principal) throws TableNotFoundException,
+            DatabaseNotFoundException, UserNotFoundException, NotAllowedException {
         log.debug("endpoint find table, databaseId={}, tableId={}", databaseId, tableId);
         final Database database = databaseService.findById(databaseId);
         final Table table = tableService.findById(database, tableId);
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java
index 3636aa63a3..8f3e15529e 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java
@@ -10,8 +10,6 @@ import at.tuwien.exception.AuthServiceException;
 import at.tuwien.exception.NotAllowedException;
 import at.tuwien.exception.UserNotFoundException;
 import at.tuwien.mapper.MetadataMapper;
-import at.tuwien.service.AuthenticationService;
-import at.tuwien.service.DatabaseService;
 import at.tuwien.service.UserService;
 import io.micrometer.observation.annotation.Observed;
 import io.swagger.v3.oas.annotations.Operation;
@@ -43,17 +41,12 @@ import java.util.UUID;
 public class UserEndpoint extends AbstractEndpoint {
 
     private final UserService userService;
-    private final MetadataMapper userMapper;
-    private final DatabaseService databaseService;
-    private final AuthenticationService authenticationService;
+    private final MetadataMapper metadataMapper;
 
     @Autowired
-    public UserEndpoint(UserService userService, MetadataMapper userMapper, DatabaseService databaseService,
-                        AuthenticationService authenticationService) {
+    public UserEndpoint(UserService userService, MetadataMapper metadataMapper) {
         this.userService = userService;
-        this.userMapper = userMapper;
-        this.databaseService = databaseService;
-        this.authenticationService = authenticationService;
+        this.metadataMapper = metadataMapper;
     }
 
     @GetMapping
@@ -74,7 +67,7 @@ public class UserEndpoint extends AbstractEndpoint {
             return ResponseEntity.ok(userService.findAll()
                     .stream()
                     .filter(user -> !user.getIsInternal())
-                    .map(userMapper::userToUserBriefDto)
+                    .map(metadataMapper::userToUserBriefDto)
                     .toList());
         }
         log.trace("filter by username: {}", username);
@@ -83,7 +76,7 @@ public class UserEndpoint extends AbstractEndpoint {
             if (user.getIsInternal()) {
                 return ResponseEntity.ok(List.of());
             }
-            return ResponseEntity.ok(List.of(userMapper.userToUserBriefDto(user)));
+            return ResponseEntity.ok(List.of(metadataMapper.userToUserBriefDto(user)));
         } catch (UserNotFoundException e) {
             log.trace("filter by username {} failed: return empty list", username);
             return ResponseEntity.ok(List.of());
@@ -110,7 +103,7 @@ public class UserEndpoint extends AbstractEndpoint {
     public ResponseEntity<UserBriefDto> create(@NotNull @Valid @RequestBody CreateUserDto data) {
         log.debug("endpoint create user, data.id={}, data.username={}", data.getId(), data.getUsername());
         return ResponseEntity.status(HttpStatus.CREATED)
-                .body(userMapper.userToUserBriefDto(
+                .body(metadataMapper.userToUserBriefDto(
                         userService.create(data)));
     }
 
@@ -160,7 +153,7 @@ public class UserEndpoint extends AbstractEndpoint {
         }
         return ResponseEntity.status(HttpStatus.OK)
                 .headers(headers)
-                .body(userMapper.userToUserDto(user));
+                .body(metadataMapper.userToUserDto(user));
     }
 
     @PutMapping("/{userId}")
@@ -208,7 +201,7 @@ public class UserEndpoint extends AbstractEndpoint {
             throw new NotAllowedException("Failed to modify user: not current user " + user.getId());
         }
         return ResponseEntity.accepted()
-                .body(userMapper.userToUserBriefDto(
+                .body(metadataMapper.userToUserBriefDto(
                         userService.modify(user, data)));
     }
 
diff --git a/lib/python/dbrepo/RestClient.py b/lib/python/dbrepo/RestClient.py
index 368e3886be..11e171b056 100644
--- a/lib/python/dbrepo/RestClient.py
+++ b/lib/python/dbrepo/RestClient.py
@@ -861,7 +861,7 @@ class RestClient:
             params.append(('page', page))
             params.append(('size', size))
         if timestamp is not None:
-            params.append(('timestamp', timestamp))
+            params.append(('timestamp', timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")))
         response = self._wrapper(method="get", url=url, params=params)
         if response.status_code == 200:
             return DataFrame.from_records(response.json())
@@ -1100,15 +1100,12 @@ class RestClient:
         raise ResponseCodeError(f'Failed to delete table data: response code: {response.status_code} is not '
                                 f'202 (ACCEPTED): {response.text}')
 
-    def get_table_data_count(self, database_id: int, table_id: int, page: int = 0, size: int = 10,
-                             timestamp: datetime.datetime = None) -> int:
+    def get_table_data_count(self, database_id: int, table_id: int, timestamp: datetime.datetime = None) -> int:
         """
         Get data count of a table in a database with given database id and table id.
 
         :param database_id: The database id.
         :param table_id: The table id.
-        :param page: The result pagination number. Optional. Default: `0`.
-        :param size: The result pagination size. Optional. Default: `10`.
         :param timestamp: The query execution time. Optional.
 
         :returns: The result of the view query, if successful.
@@ -1121,15 +1118,10 @@ class RestClient:
         :raises ResponseCodeError: If something went wrong with the retrieval.
         """
         url = f'/api/database/{database_id}/table/{table_id}/data'
-        if page is not None and size is not None:
-            url += f'?page={page}&size={size}'
+        params = []
         if timestamp is not None:
-            if page is not None and size is not None:
-                url += '&'
-            else:
-                url += '?'
-            url += f'timestamp={timestamp}'
-        response = self._wrapper(method="head", url=url)
+            params.append(('timestamp', timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")))
+        response = self._wrapper(method="head", url=url, params=params)
         if response.status_code == 200:
             return int(response.headers.get('X-Count'))
         if response.status_code == 400:
@@ -1204,29 +1196,6 @@ class RestClient:
         raise ResponseCodeError(f'Failed to get database access: response code: {response.status_code} is not '
                                 f'200 (OK): {response.text}')
 
-    def check_database_access(self, database_id: int) -> bool:
-        """
-        Checks access of a view in a database with given database id and view id.
-
-        :param database_id: The database id.
-
-        :returns: The access type, if successful.
-
-        :raises ForbiddenError: If something went wrong with the authorization.
-        :raises NotExistsError: If the container does not exist.
-        :raises ResponseCodeError: If something went wrong with the retrieval.
-        """
-        url = f'/api/database/{database_id}/access'
-        response = self._wrapper(method="get", url=url)
-        if response.status_code == 200:
-            return True
-        if response.status_code == 403:
-            return False
-        if response.status_code == 404:
-            raise NotExistsError(f'Failed to check database access: not found')
-        raise ResponseCodeError(f'Failed to check database access: response code: {response.status_code} is not '
-                                f'200 (OK): {response.text}')
-
     def create_database_access(self, database_id: int, user_id: str, type: AccessType) -> AccessType:
         """
         Create access to a database with given database id and user id.
@@ -1355,14 +1324,13 @@ class RestClient:
         :raises ResponseCodeError: If something went wrong with the retrieval.
         """
         url = f'/api/database/{database_id}/subset'
+        params = []
         if page is not None and size is not None:
-            url += f'?page={page}&size={size}'
-            if timestamp is not None:
-                url += f'&timestamp={timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")}'
-        else:
-            if timestamp is not None:
-                url += f'?timestamp={timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")}'
-        response = self._wrapper(method="post", url=url, headers={"Accept": "application/json"},
+            params.append(('page', page))
+            params.append(('size', size))
+        if timestamp is not None:
+            params.append(('timestamp', timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")))
+        response = self._wrapper(method="post", url=url, headers={"Accept": "application/json"}, params=params,
                                  payload=ExecuteQuery(statement=query))
         if response.status_code == 201:
             logging.info(f'Created subset with id: {response.headers["X-Id"]}')
@@ -1418,14 +1386,12 @@ class RestClient:
         raise ResponseCodeError(f'Failed to get query data: response code: {response.status_code} is not '
                                 f'200 (OK): {response.text}')
 
-    def get_subset_data_count(self, database_id: int, subset_id: int, page: int = 0, size: int = 10) -> int:
+    def get_subset_data_count(self, database_id: int, subset_id: int) -> int:
         """
         Re-executes a query in a database with given database id and query id and only counts the results.
 
         :param database_id: The database id.
         :param subset_id: The subset id.
-        :param page: The result pagination number. Optional. Default: `0`.
-        :param size: The result pagination size. Optional. Default: `10`.
 
         :returns: The result set, if successful.
 
@@ -1436,8 +1402,6 @@ class RestClient:
         :raises ResponseCodeError: If something went wrong with the retrieval.
         """
         url = f'/api/database/{database_id}/subset/{subset_id}/data'
-        if page is not None and size is not None:
-            url += f'?page={page}&size={size}'
         response = self._wrapper(method="head", url=url)
         if response.status_code == 200:
             return int(response.headers.get('X-Count'))
@@ -1546,13 +1510,13 @@ class RestClient:
         raise ResponseCodeError(f'Failed to update query: response code: {response.status_code} is not '
                                 f'202 (ACCEPTED): {response.text}')
 
-    def create_identifier(self, database_id: int, type: IdentifierType, titles: List[SaveIdentifierTitle],
+    def create_identifier(self, database_id: int, type: IdentifierType, titles: List[CreateIdentifierTitle],
                           publisher: str, creators: List[CreateIdentifierCreator], publication_year: int,
-                          descriptions: List[SaveIdentifierDescription] = None,
-                          funders: List[SaveIdentifierFunder] = None, licenses: List[License] = None,
+                          descriptions: List[CreateIdentifierDescription] = None,
+                          funders: List[CreateIdentifierFunder] = None, licenses: List[License] = None,
                           language: Language = None, subset_id: int = None, view_id: int = None, table_id: int = None,
                           publication_day: int = None, publication_month: int = None,
-                          related_identifiers: List[SaveRelatedIdentifier] = None) -> Identifier:
+                          related_identifiers: List[CreateRelatedIdentifier] = None) -> Identifier:
         """
         Create an identifier draft.
 
@@ -1585,7 +1549,7 @@ class RestClient:
         url = f'/api/identifier'
         payload = CreateIdentifier(database_id=database_id, type=type, titles=titles, publisher=publisher,
                                    creators=creators, publication_year=publication_year, descriptions=descriptions,
-                                   funders=funders, licenses=licenses, language=language, subset_id=subset_id,
+                                   funders=funders, licenses=licenses, language=language, query_id=subset_id,
                                    view_id=view_id, table_id=table_id, publication_day=publication_day,
                                    publication_month=publication_month, related_identifiers=related_identifiers)
         response = self._wrapper(method="post", url=url, force_auth=True, payload=payload)
@@ -1606,15 +1570,15 @@ class RestClient:
         raise ResponseCodeError(f'Failed to create identifier: response code: {response.status_code} is not '
                                 f'201 (CREATED): {response.text}')
 
-    def save_identifier(self, identifier_id: int, database_id: int, type: IdentifierType,
-                        titles: List[SaveIdentifierTitle], publisher: str, creators: List[CreateIdentifierCreator],
-                        publication_year: int, descriptions: List[SaveIdentifierDescription] = None,
-                        funders: List[SaveIdentifierFunder] = None, licenses: List[License] = None,
-                        language: Language = None, subset_id: int = None, view_id: int = None, table_id: int = None,
-                        publication_day: int = None, publication_month: int = None,
-                        related_identifiers: List[SaveRelatedIdentifier] = None) -> Identifier:
+    def update_identifier(self, identifier_id: int, database_id: int, type: IdentifierType,
+                          titles: List[SaveIdentifierTitle], publisher: str, creators: List[SaveIdentifierCreator],
+                          publication_year: int, descriptions: List[SaveIdentifierDescription] = None,
+                          funders: List[SaveIdentifierFunder] = None, licenses: List[License] = None,
+                          language: Language = None, subset_id: int = None, view_id: int = None, table_id: int = None,
+                          publication_day: int = None, publication_month: int = None,
+                          related_identifiers: List[SaveRelatedIdentifier] = None) -> Identifier:
         """
-        Save an existing identifier and update the metadata attached to it.
+        Update an existing identifier and update the metadata attached to it.
 
         :param identifier_id: The identifier id.
         :param database_id: The database id of the created identifier.
@@ -1644,11 +1608,12 @@ class RestClient:
         :raises ResponseCodeError: If something went wrong with the creation of the identifier.
         """
         url = f'/api/identifier/{identifier_id}'
-        payload = CreateIdentifier(database_id=database_id, type=type, titles=titles, publisher=publisher,
-                                   creators=creators, publication_year=publication_year, descriptions=descriptions,
-                                   funders=funders, licenses=licenses, language=language, subset_id=subset_id,
-                                   view_id=view_id, table_id=table_id, publication_day=publication_day,
-                                   publication_month=publication_month, related_identifiers=related_identifiers)
+        payload = IdentifierSave(id=identifier_id, database_id=database_id, type=type, titles=titles,
+                                 publisher=publisher, creators=creators, publication_year=publication_year,
+                                 descriptions=descriptions, funders=funders, licenses=licenses, language=language,
+                                 query_id=subset_id, view_id=view_id, table_id=table_id,
+                                 publication_day=publication_day, publication_month=publication_month,
+                                 related_identifiers=related_identifiers)
         response = self._wrapper(method="put", url=url, force_auth=True, payload=payload)
         if response.status_code == 202:
             body = response.json()
@@ -1730,7 +1695,8 @@ class RestClient:
                                 f'200 (OK): {response.text}')
 
     def get_identifiers(self, database_id: int = None, subset_id: int = None, view_id: int = None,
-                        table_id: int = None) -> List[Identifier] | str:
+                        table_id: int = None, type: IdentifierType = None, status: IdentifierStatusType = None) -> List[
+                                                                                                                       Identifier] | str:
         """
         Get list of identifiers, filter by the remaining optional arguments.
 
@@ -1738,6 +1704,8 @@ class RestClient:
         :param subset_id: The subset id. Optional. Requires `database_id` to be set.
         :param view_id: The view id. Optional. Requires `database_id` to be set.
         :param table_id: The table id. Optional. Requires `database_id` to be set.
+        :param type: The identifier type. Optional.
+        :param status: The identifier status. Optional.
 
         :returns: List of identifiers, if successful.
 
@@ -1746,28 +1714,33 @@ class RestClient:
         :raises ResponseCodeError: If something went wrong with the retrieval of the identifiers.
         """
         url = f'/api/identifiers'
+        params = []
         if database_id is not None:
-            url += f'?dbid={database_id}'
+            params.append(('dbid', database_id))
         if subset_id is not None:
             if database_id is None:
                 raise RequestError(f'Filtering by subset_id requires database_id to be set')
-            url += f'&qid={subset_id}'
+            params.append(('qid', subset_id))
         if view_id is not None:
             if database_id is None:
                 raise RequestError(f'Filtering by view_id requires database_id to be set')
-            url += f'&vid={view_id}'
+            params.append(('vid', view_id))
         if table_id is not None:
             if database_id is None:
                 raise RequestError(f'Filtering by table_id requires database_id to be set')
-            url += f'&tid={table_id}'
-        response = self._wrapper(method="get", url=url, headers={'Accept': 'application/json'})
+            params.append(('tid', table_id))
+        if type is not None:
+            params.append(('type', type))
+        if status is not None:
+            params.append(('status', status))
+        response = self._wrapper(method="get", url=url, params=params, headers={'Accept': 'application/json'})
         if response.status_code == 200:
             body = response.json()
             return TypeAdapter(List[Identifier]).validate_python(body)
         if response.status_code == 404:
             raise NotExistsError(f'Failed to get identifiers: requested style is not known')
         if response.status_code == 406:
-            raise MalformedError(
+            raise FormatNotAvailable(
                 f'Failed to get identifiers: accept header must be application/json or application/ld+json')
         raise ResponseCodeError(f'Failed to get identifiers: response code: {response.status_code} is not '
                                 f'200 (OK): {response.text}')
diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py
index 27801a9482..9e042b966d 100644
--- a/lib/python/dbrepo/api/dto.py
+++ b/lib/python/dbrepo/api/dto.py
@@ -409,12 +409,16 @@ class IdentifierTitle(BaseModel):
     type: Optional[TitleType] = None
 
 
-class SaveIdentifierTitle(BaseModel):
+class CreateIdentifierTitle(BaseModel):
     title: str
     language: Optional[Language] = None
     type: Optional[TitleType] = None
 
 
+class SaveIdentifierTitle(CreateIdentifierTitle):
+    id: int
+
+
 class IdentifierDescription(BaseModel):
     id: int
     description: str
@@ -422,12 +426,16 @@ class IdentifierDescription(BaseModel):
     type: Optional[DescriptionType] = None
 
 
-class SaveIdentifierDescription(BaseModel):
+class CreateIdentifierDescription(BaseModel):
     description: str
     language: Optional[Language] = None
     type: Optional[DescriptionType] = None
 
 
+class SaveIdentifierDescription(CreateIdentifierDescription):
+    id: int
+
+
 class IdentifierFunder(BaseModel):
     id: int
     funder_name: str
@@ -438,7 +446,7 @@ class IdentifierFunder(BaseModel):
     award_title: Optional[str] = None
 
 
-class SaveIdentifierFunder(BaseModel):
+class CreateIdentifierFunder(BaseModel):
     funder_name: str
     funder_identifier: Optional[str] = None
     funder_identifier_type: Optional[str] = None
@@ -447,6 +455,10 @@ class SaveIdentifierFunder(BaseModel):
     award_title: Optional[str] = None
 
 
+class SaveIdentifierFunder(CreateIdentifierFunder):
+    id: int
+
+
 class License(BaseModel):
     identifier: str
     uri: str
@@ -577,6 +589,10 @@ class CreateIdentifierCreator(BaseModel):
     affiliation_identifier_scheme_uri: Optional[str] = None
 
 
+class SaveIdentifierCreator(CreateIdentifierCreator):
+    id: int
+
+
 class RelatedIdentifier(BaseModel):
     id: int
     value: str
@@ -584,21 +600,25 @@ class RelatedIdentifier(BaseModel):
     relation: RelatedIdentifierRelation
 
 
-class SaveRelatedIdentifier(BaseModel):
+class CreateRelatedIdentifier(BaseModel):
     value: str
     type: RelatedIdentifierType
     relation: RelatedIdentifierRelation
 
 
+class SaveRelatedIdentifier(CreateRelatedIdentifier):
+    id: int
+
+
 class CreateIdentifier(BaseModel):
     database_id: int
     type: IdentifierType
     creators: List[CreateIdentifierCreator]
     publication_year: int
     publisher: str
-    titles: List[SaveIdentifierTitle]
-    descriptions: List[SaveIdentifierDescription]
-    funders: Optional[List[SaveIdentifierFunder]] = field(default_factory=list)
+    titles: List[CreateIdentifierTitle]
+    descriptions: List[CreateIdentifierDescription]
+    funders: Optional[List[CreateIdentifierFunder]] = field(default_factory=list)
     doi: Optional[str] = None
     language: Optional[str] = None
     licenses: Optional[List[License]] = field(default_factory=list)
@@ -608,13 +628,17 @@ class CreateIdentifier(BaseModel):
     query: Optional[str] = None
     query_normalized: Optional[str] = None
     execution: Optional[str] = None
-    related_identifiers: Optional[List[SaveRelatedIdentifier]] = field(default_factory=list)
+    related_identifiers: Optional[List[CreateRelatedIdentifier]] = field(default_factory=list)
     result_hash: Optional[str] = None
     result_number: Optional[int] = None
     publication_day: Optional[int] = None
     publication_month: Optional[int] = None
 
 
+class IdentifierSave(CreateIdentifier):
+    id: int
+
+
 class Identifier(BaseModel):
     id: int
     database_id: int
diff --git a/lib/python/tests/test_unit_concept.py b/lib/python/tests/test_unit_concept.py
new file mode 100644
index 0000000000..54104dada8
--- /dev/null
+++ b/lib/python/tests/test_unit_concept.py
@@ -0,0 +1,35 @@
+import unittest
+
+import requests_mock
+
+from dbrepo.RestClient import RestClient
+from dbrepo.api.dto import ConceptBrief
+from dbrepo.api.exceptions import ResponseCodeError
+
+
+class ContainerUnitTest(unittest.TestCase):
+
+    def test_get_concepts_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            exp = [ConceptBrief(id=1,
+                                uri="http://dbpedia.org/page/Category:Precipitation",
+                                name="Precipitation")]
+            # mock
+            mock.get('/api/concept', json=[exp[0].model_dump()])
+            # test
+            response = RestClient().get_concepts()
+            self.assertEqual(exp, response)
+
+    def test_get_concepts_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/concept', status_code=202)
+            # test
+            try:
+                RestClient().get_concepts()
+            except ResponseCodeError:
+                pass
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lib/python/tests/test_unit_database.py b/lib/python/tests/test_unit_database.py
index 0eb4675433..952b27c61f 100644
--- a/lib/python/tests/test_unit_database.py
+++ b/lib/python/tests/test_unit_database.py
@@ -642,6 +642,16 @@ class DatabaseUnitTest(unittest.TestCase):
             except NotExistsError:
                 pass
 
+    def test_get_database_access_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/access', status_code=202)
+            # test
+            try:
+                response = RestClient().get_database_access(database_id=1)
+            except ResponseCodeError:
+                pass
+
     def test_create_database_access_succeeds(self):
         exp = DatabaseAccess(type=AccessType.READ,
                              user=UserBrief(id='abdbf897-e599-4e5a-a3f0-7529884ea011', username='other'))
@@ -702,6 +712,42 @@ class DatabaseUnitTest(unittest.TestCase):
             except NotExistsError:
                 pass
 
+    def test_create_database_access_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=502)
+            # test
+            try:
+                response = RestClient(username="a", password="b").create_database_access(database_id=1,
+                                                                                         type=AccessType.READ,
+                                                                                         user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ServiceConnectionError:
+                pass
+
+    def test_create_database_access_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=503)
+            # test
+            try:
+                response = RestClient(username="a", password="b").create_database_access(database_id=1,
+                                                                                         type=AccessType.READ,
+                                                                                         user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ServiceError:
+                pass
+
+    def test_create_database_access_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=200)
+            # test
+            try:
+                response = RestClient(username="a", password="b").create_database_access(database_id=1,
+                                                                                         type=AccessType.READ,
+                                                                                         user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ResponseCodeError:
+                pass
+
     def test_update_database_access_succeeds(self):
         exp = DatabaseAccess(type=AccessType.READ,
                              user=UserBrief(id='abdbf897-e599-4e5a-a3f0-7529884ea011', username='other'))
@@ -751,6 +797,42 @@ class DatabaseUnitTest(unittest.TestCase):
             except NotExistsError:
                 pass
 
+    def test_update_database_access_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=502)
+            # test
+            try:
+                response = RestClient(username="a", password="b").update_database_access(database_id=1,
+                                                                                         type=AccessType.READ,
+                                                                                         user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ServiceConnectionError:
+                pass
+
+    def test_update_database_access_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=503)
+            # test
+            try:
+                response = RestClient(username="a", password="b").update_database_access(database_id=1,
+                                                                                         type=AccessType.READ,
+                                                                                         user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ServiceError:
+                pass
+
+    def test_update_database_access_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=200)
+            # test
+            try:
+                response = RestClient(username="a", password="b").update_database_access(database_id=1,
+                                                                                         type=AccessType.READ,
+                                                                                         user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ResponseCodeError:
+                pass
+
     def test_update_database_access_anonymous_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
@@ -804,6 +886,39 @@ class DatabaseUnitTest(unittest.TestCase):
             except NotExistsError:
                 pass
 
+    def test_delete_database_access_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.delete('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=502)
+            # test
+            try:
+                RestClient(username="a", password="b").delete_database_access(database_id=1,
+                                                                              user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ServiceConnectionError:
+                pass
+
+    def test_delete_database_access_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.delete('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=503)
+            # test
+            try:
+                RestClient(username="a", password="b").delete_database_access(database_id=1,
+                                                                              user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ServiceError:
+                pass
+
+    def test_delete_database_access_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.delete('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', status_code=200)
+            # test
+            try:
+                RestClient(username="a", password="b").delete_database_access(database_id=1,
+                                                                              user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
+            except ResponseCodeError:
+                pass
+
     def test_delete_database_access_anonymous_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
diff --git a/lib/python/tests/test_unit_identifier.py b/lib/python/tests/test_unit_identifier.py
index 137f26aab2..36630e3df0 100644
--- a/lib/python/tests/test_unit_identifier.py
+++ b/lib/python/tests/test_unit_identifier.py
@@ -6,8 +6,10 @@ from dbrepo.RestClient import RestClient
 from dbrepo.api.dto import Identifier, IdentifierType, SaveIdentifierTitle, Creator, IdentifierTitle, \
     IdentifierDescription, SaveIdentifierDescription, Language, SaveIdentifierFunder, SaveRelatedIdentifier, \
     RelatedIdentifierRelation, RelatedIdentifierType, IdentifierFunder, RelatedIdentifier, UserBrief, \
-    IdentifierStatusType, CreateIdentifierCreator
-from dbrepo.api.exceptions import MalformedError, ForbiddenError, NotExistsError, AuthenticationError
+    IdentifierStatusType, CreateIdentifierCreator, CreateIdentifierTitle, CreateIdentifierFunder, \
+    CreateRelatedIdentifier, CreateIdentifierDescription, SaveIdentifierCreator
+from dbrepo.api.exceptions import MalformedError, ForbiddenError, NotExistsError, AuthenticationError, \
+    ServiceConnectionError, ServiceError, ResponseCodeError, FormatNotAvailable, RequestError
 
 
 class IdentifierUnitTest(unittest.TestCase):
@@ -36,14 +38,14 @@ class IdentifierUnitTest(unittest.TestCase):
             client = RestClient(username="a", password="b")
             response = client.create_identifier(
                 database_id=1, type=IdentifierType.VIEW,
-                titles=[SaveIdentifierTitle(title='Test Title')],
+                titles=[CreateIdentifierTitle(title='Test Title')],
                 publisher='TU Wien', publication_year=2024,
                 language=Language.EN,
-                funders=[SaveIdentifierFunder(funder_name='FWF')],
-                related_identifiers=[SaveRelatedIdentifier(value='10.12345/abc',
-                                                           relation=RelatedIdentifierRelation.CITES,
-                                                           type=RelatedIdentifierType.DOI)],
-                descriptions=[SaveIdentifierDescription(description='Test Description')],
+                funders=[CreateIdentifierFunder(funder_name='FWF')],
+                related_identifiers=[CreateRelatedIdentifier(value='10.12345/abc',
+                                                             relation=RelatedIdentifierRelation.CITES,
+                                                             type=RelatedIdentifierType.DOI)],
+                descriptions=[CreateIdentifierDescription(description='Test Description')],
                 creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
             self.assertEqual(exp, response)
 
@@ -54,10 +56,10 @@ class IdentifierUnitTest(unittest.TestCase):
             # test
             try:
                 client = RestClient(username="a", password="b")
-                response = client.create_identifier(
+                client.create_identifier(
                     database_id=1, type=IdentifierType.VIEW,
-                    titles=[SaveIdentifierTitle(title='Test Title')],
-                    descriptions=[SaveIdentifierDescription(description='Test')],
+                    titles=[CreateIdentifierTitle(title='Test Title')],
+                    descriptions=[CreateIdentifierDescription(description='Test')],
                     publisher='TU Wien', publication_year=2024,
                     creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
             except MalformedError:
@@ -70,10 +72,10 @@ class IdentifierUnitTest(unittest.TestCase):
             # test
             try:
                 client = RestClient(username="a", password="b")
-                response = client.create_identifier(
+                client.create_identifier(
                     database_id=1, type=IdentifierType.VIEW,
-                    titles=[SaveIdentifierTitle(title='Test Title')],
-                    descriptions=[SaveIdentifierDescription(description='Test')],
+                    titles=[CreateIdentifierTitle(title='Test Title')],
+                    descriptions=[CreateIdentifierDescription(description='Test')],
                     publisher='TU Wien', publication_year=2024,
                     creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
             except ForbiddenError:
@@ -86,31 +88,76 @@ class IdentifierUnitTest(unittest.TestCase):
             # test
             try:
                 client = RestClient(username="a", password="b")
-                response = client.create_identifier(
+                client.create_identifier(
                     database_id=1, type=IdentifierType.VIEW,
-                    titles=[SaveIdentifierTitle(title='Test Title')],
-                    descriptions=[SaveIdentifierDescription(description='Test')],
+                    titles=[CreateIdentifierTitle(title='Test Title')],
+                    descriptions=[CreateIdentifierDescription(description='Test')],
                     publisher='TU Wien', publication_year=2024,
                     creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
             except NotExistsError:
                 pass
 
-    def test_create_identifier_anonymous_fails(self):
+    def test_create_identifier_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/identifier', status_code=502)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.create_identifier(
+                    database_id=1, type=IdentifierType.VIEW,
+                    titles=[CreateIdentifierTitle(title='Test Title')],
+                    descriptions=[CreateIdentifierDescription(description='Test')],
+                    publisher='TU Wien', publication_year=2024,
+                    creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
+            except ServiceConnectionError:
+                pass
+
+    def test_create_identifier_503_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
             mock.post('/api/identifier', status_code=503)
             # test
             try:
-                response = RestClient().create_identifier(
+                client = RestClient(username="a", password="b")
+                client.create_identifier(
+                    database_id=1, type=IdentifierType.VIEW,
+                    titles=[CreateIdentifierTitle(title='Test Title')],
+                    descriptions=[CreateIdentifierDescription(description='Test')],
+                    publisher='TU Wien', publication_year=2024,
+                    creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
+            except ServiceError:
+                pass
+
+    def test_create_identifier_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/identifier', status_code=200)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.create_identifier(
                     database_id=1, type=IdentifierType.VIEW,
-                    titles=[SaveIdentifierTitle(title='Test Title')],
-                    descriptions=[SaveIdentifierDescription(description='Test')],
+                    titles=[CreateIdentifierTitle(title='Test Title')],
+                    descriptions=[CreateIdentifierDescription(description='Test')],
                     publisher='TU Wien', publication_year=2024,
                     creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
-            except AuthenticationError:
+            except ResponseCodeError:
                 pass
 
-    def test_get_identifiers_succeeds(self):
+    def test_create_identifier_anonymous_fails(self):
+        # test
+        try:
+            RestClient().create_identifier(
+                database_id=1, type=IdentifierType.VIEW,
+                titles=[CreateIdentifierTitle(title='Test Title')],
+                descriptions=[CreateIdentifierDescription(description='Test')],
+                publisher='TU Wien', publication_year=2024,
+                creators=[CreateIdentifierCreator(creator_name='Carberry, Josiah')])
+        except AuthenticationError:
+            pass
+
+    def test_get_identifiers_view_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = [Identifier(id=10,
                               database_id=1,
@@ -131,9 +178,469 @@ class IdentifierUnitTest(unittest.TestCase):
             # mock
             mock.get('/api/identifiers', json=[exp[0].model_dump()], headers={"Accept": "application/json"})
             # test
-            response = RestClient().get_identifiers()
+            response = RestClient().get_identifiers(database_id=1, view_id=32, type=IdentifierType.VIEW,
+                                                    status=IdentifierStatusType.PUBLISHED)
+            self.assertEqual(exp, response)
+
+    def test_get_identifiers_subset_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            exp = []
+            # mock
+            mock.get('/api/identifiers', json=[], headers={"Accept": "application/json"})
+            # test
+            response = RestClient().get_identifiers(database_id=1, subset_id=2)
+            self.assertEqual(exp, response)
+
+    def test_get_identifiers_table_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            exp = []
+            # mock
+            mock.get('/api/identifiers', json=[], headers={"Accept": "application/json"})
+            # test
+            response = RestClient().get_identifiers(database_id=1, table_id=3)
             self.assertEqual(exp, response)
 
+    def test_get_identifiers_view_param_database_fails(self):
+        # test
+        try:
+            RestClient().get_identifiers(view_id=1)
+        except RequestError:
+            pass
+
+    def test_get_identifiers_subset_param_database_fails(self):
+        # test
+        try:
+            RestClient().get_identifiers(subset_id=1)
+        except RequestError:
+            pass
+
+    def test_get_identifiers_table_param_database_fails(self):
+        # test
+        try:
+            RestClient().get_identifiers(table_id=1)
+        except RequestError:
+            pass
+
+    def test_get_identifiers_404_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/identifiers', status_code=404)
+            # test
+            try:
+                RestClient().get_identifiers()
+            except NotExistsError:
+                pass
+
+    def test_get_identifiers_406_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/identifiers', status_code=406)
+            # test
+            try:
+                RestClient().get_identifiers()
+            except FormatNotAvailable:
+                pass
+
+    def test_get_identifiers_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/identifiers', status_code=202)
+            # test
+            try:
+                RestClient().get_identifiers()
+            except ResponseCodeError:
+                pass
+
+    def test_update_identifier_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10', json=exp.model_dump(), status_code=202)
+            # test
+            client = RestClient(username="a", password="b")
+            response = client.update_identifier(identifier_id=10,
+                                                database_id=1, type=IdentifierType.VIEW,
+                                                titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                                publisher='TU Wien', publication_year=2024,
+                                                language=Language.EN,
+                                                funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                                related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                           value='10.12345/abc',
+                                                                                           relation=RelatedIdentifierRelation.CITES,
+                                                                                           type=RelatedIdentifierType.DOI)],
+                                                descriptions=[SaveIdentifierDescription(id=2,
+                                                                                        description='Test Description')],
+                                                creators=[SaveIdentifierCreator(id=30,
+                                                                                creator_name='Carberry, Josiah')])
+            self.assertEqual(exp, response)
+
+    def test_update_identifier_400_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/identifier/10', status_code=400)
+            # test
+            client = RestClient(username="a", password="b")
+            try:
+                client.update_identifier(identifier_id=10,
+                                         database_id=1, type=IdentifierType.VIEW,
+                                         titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                         publisher='TU Wien', publication_year=2024,
+                                         language=Language.EN,
+                                         funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                         related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                    value='10.12345/abc',
+                                                                                    relation=RelatedIdentifierRelation.CITES,
+                                                                                    type=RelatedIdentifierType.DOI)],
+                                         descriptions=[SaveIdentifierDescription(id=2,
+                                                                                 description='Test Description')],
+                                         creators=[SaveIdentifierCreator(id=30,
+                                                                         creator_name='Carberry, Josiah')])
+            except MalformedError:
+                pass
+
+    def test_update_identifier_403_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/identifier/10', status_code=403)
+            # test
+            client = RestClient(username="a", password="b")
+            try:
+                client.update_identifier(identifier_id=10,
+                                         database_id=1, type=IdentifierType.VIEW,
+                                         titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                         publisher='TU Wien', publication_year=2024,
+                                         language=Language.EN,
+                                         funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                         related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                    value='10.12345/abc',
+                                                                                    relation=RelatedIdentifierRelation.CITES,
+                                                                                    type=RelatedIdentifierType.DOI)],
+                                         descriptions=[SaveIdentifierDescription(id=2,
+                                                                                 description='Test Description')],
+                                         creators=[SaveIdentifierCreator(id=30,
+                                                                         creator_name='Carberry, Josiah')])
+            except ForbiddenError:
+                pass
+
+    def test_update_identifier_404_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/identifier/10', status_code=404)
+            # test
+            client = RestClient(username="a", password="b")
+            try:
+                client.update_identifier(identifier_id=10,
+                                         database_id=1, type=IdentifierType.VIEW,
+                                         titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                         publisher='TU Wien', publication_year=2024,
+                                         language=Language.EN,
+                                         funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                         related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                    value='10.12345/abc',
+                                                                                    relation=RelatedIdentifierRelation.CITES,
+                                                                                    type=RelatedIdentifierType.DOI)],
+                                         descriptions=[SaveIdentifierDescription(id=2,
+                                                                                 description='Test Description')],
+                                         creators=[SaveIdentifierCreator(id=30,
+                                                                         creator_name='Carberry, Josiah')])
+            except NotExistsError:
+                pass
+
+    def test_update_identifier_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/identifier/10', status_code=502)
+            # test
+            client = RestClient(username="a", password="b")
+            try:
+                client.update_identifier(identifier_id=10,
+                                         database_id=1, type=IdentifierType.VIEW,
+                                         titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                         publisher='TU Wien', publication_year=2024,
+                                         language=Language.EN,
+                                         funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                         related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                    value='10.12345/abc',
+                                                                                    relation=RelatedIdentifierRelation.CITES,
+                                                                                    type=RelatedIdentifierType.DOI)],
+                                         descriptions=[SaveIdentifierDescription(id=2,
+                                                                                 description='Test Description')],
+                                         creators=[SaveIdentifierCreator(id=30,
+                                                                         creator_name='Carberry, Josiah')])
+            except ServiceConnectionError:
+                pass
+
+    def test_update_identifier_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/identifier/10', status_code=503)
+            # test
+            client = RestClient(username="a", password="b")
+            try:
+                client.update_identifier(identifier_id=10,
+                                         database_id=1, type=IdentifierType.VIEW,
+                                         titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                         publisher='TU Wien', publication_year=2024,
+                                         language=Language.EN,
+                                         funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                         related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                    value='10.12345/abc',
+                                                                                    relation=RelatedIdentifierRelation.CITES,
+                                                                                    type=RelatedIdentifierType.DOI)],
+                                         descriptions=[SaveIdentifierDescription(id=2,
+                                                                                 description='Test Description')],
+                                         creators=[SaveIdentifierCreator(id=30,
+                                                                         creator_name='Carberry, Josiah')])
+            except ServiceError:
+                pass
+
+    def test_update_identifier_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/identifier/10', status_code=200)
+            # test
+            client = RestClient(username="a", password="b")
+            try:
+                client.update_identifier(identifier_id=10,
+                                         database_id=1, type=IdentifierType.VIEW,
+                                         titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                         publisher='TU Wien', publication_year=2024,
+                                         language=Language.EN,
+                                         funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                         related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                    value='10.12345/abc',
+                                                                                    relation=RelatedIdentifierRelation.CITES,
+                                                                                    type=RelatedIdentifierType.DOI)],
+                                         descriptions=[SaveIdentifierDescription(id=2,
+                                                                                 description='Test Description')],
+                                         creators=[SaveIdentifierCreator(id=30,
+                                                                         creator_name='Carberry, Josiah')])
+            except ResponseCodeError:
+                pass
+
+    def test_update_identifier_anonymous_fails(self):
+        # test
+        try:
+            RestClient().update_identifier(identifier_id=10,
+                                           database_id=1, type=IdentifierType.VIEW,
+                                           titles=[SaveIdentifierTitle(id=10, title='Test Title')],
+                                           publisher='TU Wien', publication_year=2024,
+                                           language=Language.EN,
+                                           funders=[SaveIdentifierFunder(id=2, funder_name='FWF')],
+                                           related_identifiers=[SaveRelatedIdentifier(id=2,
+                                                                                      value='10.12345/abc',
+                                                                                      relation=RelatedIdentifierRelation.CITES,
+                                                                                      type=RelatedIdentifierType.DOI)],
+                                           descriptions=[SaveIdentifierDescription(id=2,
+                                                                                   description='Test Description')],
+                                           creators=[SaveIdentifierCreator(id=30,
+                                                                           creator_name='Carberry, Josiah')])
+        except AuthenticationError:
+            pass
+
+    def test_publish_identifier_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=202)
+            # test
+            client = RestClient(username="a", password="b")
+            response = client.publish_identifier(identifier_id=10)
+            self.assertEqual(exp, response)
+
+    def test_publish_identifier_400_fails(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=400)
+            # test
+            try:
+                RestClient(username="a", password="b").publish_identifier(identifier_id=10)
+            except MalformedError:
+                pass
+
+    def test_publish_identifier_403_fails(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=403)
+            # test
+            try:
+                RestClient(username="a", password="b").publish_identifier(identifier_id=10)
+            except ForbiddenError:
+                pass
+
+    def test_publish_identifier_404_fails(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=404)
+            # test
+            try:
+                RestClient(username="a", password="b").publish_identifier(identifier_id=10)
+            except NotExistsError:
+                pass
+
+    def test_publish_identifier_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=502)
+            # test
+            try:
+                RestClient(username="a", password="b").publish_identifier(identifier_id=10)
+            except ServiceConnectionError:
+                pass
+
+    def test_publish_identifier_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=503)
+            # test
+            try:
+                RestClient(username="a", password="b").publish_identifier(identifier_id=10)
+            except ServiceError:
+                pass
+
+    def test_publish_identifier_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            exp = Identifier(id=10,
+                             database_id=1,
+                             view_id=32,
+                             publication_year=2024,
+                             publisher='TU Wien',
+                             type=IdentifierType.VIEW,
+                             language=Language.EN,
+                             descriptions=[IdentifierDescription(id=2, description='Test Description')],
+                             titles=[IdentifierTitle(id=3, title='Test Title')],
+                             funders=[IdentifierFunder(id=4, funder_name='FWF')],
+                             related_identifiers=[
+                                 RelatedIdentifier(id=7, value='10.12345/abc', relation=RelatedIdentifierRelation.CITES,
+                                                   type=RelatedIdentifierType.DOI)],
+                             creators=[Creator(id=5, creator_name='Carberry, Josiah')],
+                             status=IdentifierStatusType.PUBLISHED,
+                             owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'))
+            # mock
+            mock.put('/api/identifier/10/publish', json=exp.model_dump(), status_code=200)
+            # test
+            try:
+                RestClient(username="a", password="b").publish_identifier(identifier_id=10)
+            except ResponseCodeError:
+                pass
+
+    def test_publish_identifier_anonymous_fails(self):
+        # test
+        try:
+            RestClient().publish_identifier(identifier_id=10)
+        except AuthenticationError:
+            pass
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/lib/python/tests/test_unit_jwt.py b/lib/python/tests/test_unit_jwt.py
deleted file mode 100644
index 4a0748fe38..0000000000
--- a/lib/python/tests/test_unit_jwt.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import json
-from unittest import TestCase, main
-
-import requests_mock
-
-from dbrepo.RestClient import RestClient
-from dbrepo.api.dto import JwtAuth
-
-
-class DatabaseUnitTest(TestCase):
-
-    def test_get_jwt_auth_succeeds(self):
-        exp = JwtAuth(access_token='eyABC',
-                      refresh_token='ey123',
-                      id_token='eyXYZ',
-                      expires_in=3600,
-                      refresh_expires_in=36000,
-                      not_before_policy=0,
-                      scope='openid',
-                      session_state='4604e4b1-2163-42c3-806d-3be2e426c3a5',
-                      token_type='Bearer')
-        with requests_mock.Mocker() as mock:
-            # mock
-            mock.post('/api/user/token', json=exp.model_dump(), status_code=202)
-            # test
-            response = RestClient().get_jwt_auth(username='foo', password='bar')
-            self.assertEqual(exp, response)
-
-    def test_get_jwt_auth_empty_succeeds(self):
-        exp = JwtAuth(access_token='eyABC',
-                      refresh_token='ey123',
-                      id_token='eyXYZ',
-                      expires_in=3600,
-                      refresh_expires_in=36000,
-                      not_before_policy=0,
-                      scope='openid',
-                      session_state='4604e4b1-2163-42c3-806d-3be2e426c3a5',
-                      token_type='Bearer')
-        with requests_mock.Mocker() as mock:
-            # mock
-            mock.post('/api/user/token', json=exp.model_dump(), status_code=202)
-            # test
-            response = RestClient().get_jwt_auth()
-            self.assertEqual(exp, response)
-
-    def test_refresh_jwt_auth_succeeds(self):
-        exp = JwtAuth(access_token='eyABC',
-                      refresh_token='ey123',
-                      id_token='eyXYZ',
-                      expires_in=3600,
-                      refresh_expires_in=36000,
-                      not_before_policy=0,
-                      scope='openid',
-                      session_state='4604e4b1-2163-42c3-806d-3be2e426c3a5',
-                      token_type='Bearer')
-        with requests_mock.Mocker() as mock:
-            # mock
-            mock.put('/api/user/token', json=exp.model_dump(), status_code=202)
-            # test
-            response = RestClient().refresh_jwt_auth(refresh_token='ey123')
-            self.assertEqual(exp, response)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/lib/python/tests/test_unit_license.py b/lib/python/tests/test_unit_license.py
index f64ed76d31..b2bb3a79f3 100644
--- a/lib/python/tests/test_unit_license.py
+++ b/lib/python/tests/test_unit_license.py
@@ -3,8 +3,8 @@ import unittest
 import requests_mock
 
 from dbrepo.RestClient import RestClient
-
 from dbrepo.api.dto import License
+from dbrepo.api.exceptions import ResponseCodeError
 
 
 class LicenseUnitTest(unittest.TestCase):
@@ -27,6 +27,16 @@ class LicenseUnitTest(unittest.TestCase):
             response = RestClient().get_licenses()
             self.assertEqual(exp, response)
 
+    def test_get_licenses_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/license', status_code=202)
+            # test
+            try:
+                RestClient().get_licenses()
+            except ResponseCodeError:
+                pass
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/lib/python/tests/test_unit_query.py b/lib/python/tests/test_unit_query.py
index 31b4182a4b..415a35b86c 100644
--- a/lib/python/tests/test_unit_query.py
+++ b/lib/python/tests/test_unit_query.py
@@ -1,14 +1,14 @@
+import datetime
 import json
 import unittest
 
 import requests_mock
-import datetime
-
-from dbrepo.RestClient import RestClient
 from pandas import DataFrame
 
-from dbrepo.api.dto import Query, User, UserAttributes, QueryType, UserBrief
-from dbrepo.api.exceptions import MalformedError, NotExistsError, ForbiddenError
+from dbrepo.RestClient import RestClient
+from dbrepo.api.dto import Query, QueryType, UserBrief
+from dbrepo.api.exceptions import MalformedError, NotExistsError, ForbiddenError, QueryStoreError, FormatNotAvailable, \
+    ServiceError, ResponseCodeError, AuthenticationError
 
 
 class QueryUnitTest(unittest.TestCase):
@@ -22,6 +22,7 @@ class QueryUnitTest(unittest.TestCase):
             # test
             client = RestClient(username="a", password="b")
             response = client.create_subset(database_id=1, page=0, size=10,
+                                            timestamp=datetime.datetime(2024, 1, 1, 0, 0, 0, 0, datetime.timezone.utc),
                                             query="SELECT id, username FROM some_table WHERE id IN (1,2)")
             self.assertTrue(DataFrame.equals(df, response))
 
@@ -32,8 +33,8 @@ class QueryUnitTest(unittest.TestCase):
             # test
             try:
                 client = RestClient(username="a", password="b")
-                response = client.create_subset(database_id=1,
-                                                query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
             except MalformedError:
                 pass
 
@@ -44,8 +45,8 @@ class QueryUnitTest(unittest.TestCase):
             # test
             try:
                 client = RestClient(username="a", password="b")
-                response = client.create_subset(database_id=1,
-                                                query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
             except ForbiddenError:
                 pass
 
@@ -56,11 +57,59 @@ class QueryUnitTest(unittest.TestCase):
             # test
             try:
                 client = RestClient(username="a", password="b")
-                response = client.create_subset(database_id=1,
-                                                query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
             except NotExistsError:
                 pass
 
+    def test_create_subset_417_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/subset', status_code=417)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+            except QueryStoreError:
+                pass
+
+    def test_create_subset_501_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/subset', status_code=501)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+            except FormatNotAvailable:
+                pass
+
+    def test_create_subset_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/subset', status_code=503)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+            except ServiceError:
+                pass
+
+    def test_create_subset_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.post('/api/database/1/subset', status_code=200)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.create_subset(database_id=1,
+                                     query="SELECT id, username FROM some_table WHERE id IN (1,2)")
+            except ResponseCodeError:
+                pass
+
     def test_create_subset_anonymous_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}]
@@ -74,7 +123,7 @@ class QueryUnitTest(unittest.TestCase):
                                             query="SELECT id, username FROM some_table WHERE id IN (1,2)")
             self.assertTrue(DataFrame.equals(df, response))
 
-    def test_find_query_succeeds(self):
+    def test_get_subset_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = Query(id=6,
                         owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'),
@@ -94,26 +143,56 @@ class QueryUnitTest(unittest.TestCase):
             response = RestClient().get_subset(database_id=1, subset_id=6)
             self.assertEqual(exp, response)
 
-    def test_find_query_403_fails(self):
+    def test_get_subset_403_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
             mock.get('/api/database/1/subset/6', status_code=403)
             # test
             try:
-                response = RestClient().get_subset(database_id=1, subset_id=6)
+                RestClient().get_subset(database_id=1, subset_id=6)
             except ForbiddenError:
                 pass
 
-    def test_find_query_404_fails(self):
+    def test_get_subset_404_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
             mock.get('/api/database/1/subset/6', status_code=404)
             # test
             try:
-                response = RestClient().get_subset(database_id=1, subset_id=6)
+                RestClient().get_subset(database_id=1, subset_id=6)
             except NotExistsError:
                 pass
 
+    def test_get_subset_406_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset/6', status_code=406)
+            # test
+            try:
+                RestClient().get_subset(database_id=1, subset_id=6)
+            except FormatNotAvailable:
+                pass
+
+    def test_get_subset_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset/6', status_code=503)
+            # test
+            try:
+                RestClient().get_subset(database_id=1, subset_id=6)
+            except ServiceError:
+                pass
+
+    def test_get_subset_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset/6', status_code=202)
+            # test
+            try:
+                RestClient().get_subset(database_id=1, subset_id=6)
+            except ResponseCodeError:
+                pass
+
     def test_get_queries_empty_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = []
@@ -123,6 +202,94 @@ class QueryUnitTest(unittest.TestCase):
             response = RestClient().get_queries(database_id=1)
             self.assertEqual(exp, response)
 
+    def test_update_subset_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            exp = Query(id=6,
+                        owner=UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise'),
+                        execution=datetime.datetime(2024, 1, 1, 0, 0, 0, 0, datetime.timezone.utc),
+                        query='SELECT id, username FROM some_table WHERE id IN (1,2)',
+                        query_normalized='SELECT id, username FROM some_table WHERE id IN (1,2)',
+                        type=QueryType.QUERY,
+                        database_id=1,
+                        query_hash='da5ff66c4a57683171e2ffcec25298ee684680d1e03633cd286f9067d6924ad8',
+                        result_hash='464740ba612225913bb15b26f13377707949b55e65288e89c3f8b4c6469aecb4',
+                        is_persisted=True,
+                        result_number=None,
+                        identifiers=[])
+            # mock
+            mock.put('/api/database/1/subset/6', json=exp.model_dump(), status_code=202)
+            # test
+            response = RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6,
+                                                                                persist=True)
+            self.assertEqual(exp, response)
+
+    def test_update_subset_400_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/subset/6', status_code=400)
+            # test
+            try:
+                RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6, persist=True)
+            except MalformedError:
+                pass
+
+    def test_update_subset_403_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/subset/6', status_code=403)
+            # test
+            try:
+                RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6, persist=True)
+            except ForbiddenError:
+                pass
+
+    def test_update_subset_404_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/subset/6', status_code=404)
+            # test
+            try:
+                RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6, persist=True)
+            except NotExistsError:
+                pass
+
+    def test_update_subset_417_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/subset/6', status_code=417)
+            # test
+            try:
+                RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6, persist=True)
+            except QueryStoreError:
+                pass
+
+    def test_update_subset_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/subset/6', status_code=503)
+            # test
+            try:
+                RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6, persist=True)
+            except ServiceError:
+                pass
+
+    def test_update_subset_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/subset/6', status_code=200)
+            # test
+            try:
+                RestClient(username='foo', password='bar').update_subset(database_id=1, subset_id=6, persist=True)
+            except ResponseCodeError:
+                pass
+
+    def test_update_subset_anonymous_fails(self):
+        # test
+        try:
+            RestClient().update_subset(database_id=1, subset_id=6, persist=True)
+        except AuthenticationError:
+            pass
+
     def test_get_queries_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = [Query(id=6,
@@ -149,7 +316,7 @@ class QueryUnitTest(unittest.TestCase):
             mock.get('/api/database/1/subset', status_code=403)
             # test
             try:
-                response = RestClient().get_queries(database_id=1)
+                RestClient().get_queries(database_id=1)
             except ForbiddenError:
                 pass
 
@@ -159,10 +326,30 @@ class QueryUnitTest(unittest.TestCase):
             mock.get('/api/database/1/subset', status_code=404)
             # test
             try:
-                response = RestClient().get_queries(database_id=1)
+                RestClient().get_queries(database_id=1)
             except NotExistsError:
                 pass
 
+    def test_get_queries_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset', status_code=503)
+            # test
+            try:
+                RestClient().get_queries(database_id=1)
+            except ServiceError:
+                pass
+
+    def test_get_queries_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset', status_code=202)
+            # test
+            try:
+                RestClient().get_queries(database_id=1)
+            except ResponseCodeError:
+                pass
+
     def test_get_subset_data_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}]
@@ -184,13 +371,23 @@ class QueryUnitTest(unittest.TestCase):
             self.assertEqual(df.shape, response.shape)
             self.assertTrue(DataFrame.equals(df, response))
 
+    def test_get_subset_data_400_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset/6/data', status_code=400)
+            # test
+            try:
+                RestClient().get_subset_data(database_id=1, subset_id=6)
+            except MalformedError:
+                pass
+
     def test_get_subset_data_403_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
             mock.get('/api/database/1/subset/6/data', status_code=403)
             # test
             try:
-                response = RestClient().get_subset_data(database_id=1, subset_id=6)
+                RestClient().get_subset_data(database_id=1, subset_id=6)
             except ForbiddenError:
                 pass
 
@@ -200,10 +397,30 @@ class QueryUnitTest(unittest.TestCase):
             mock.get('/api/database/1/subset/6/data', status_code=404)
             # test
             try:
-                response = RestClient().get_subset_data(database_id=1, subset_id=6)
+                RestClient().get_subset_data(database_id=1, subset_id=6)
             except NotExistsError:
                 pass
 
+    def test_get_subset_data_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset/6/data', status_code=503)
+            # test
+            try:
+                RestClient().get_subset_data(database_id=1, subset_id=6)
+            except ServiceError:
+                pass
+
+    def test_get_subset_data_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/database/1/subset/6/data', status_code=202)
+            # test
+            try:
+                RestClient().get_subset_data(database_id=1, subset_id=6)
+            except ResponseCodeError:
+                pass
+
     def test_get_subset_data_count_succeeds(self):
         with requests_mock.Mocker() as mock:
             exp = 2
@@ -213,13 +430,23 @@ class QueryUnitTest(unittest.TestCase):
             response = RestClient().get_subset_data_count(database_id=1, subset_id=6)
             self.assertEqual(exp, response)
 
+    def test_get_subset_data_count_400_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/subset/6/data', status_code=400)
+            # test
+            try:
+                RestClient().get_subset_data_count(database_id=1, subset_id=6)
+            except MalformedError:
+                pass
+
     def test_get_subset_data_count_403_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
             mock.head('/api/database/1/subset/6/data', status_code=403)
             # test
             try:
-                response = RestClient().get_subset_data_count(database_id=1, subset_id=6)
+                RestClient().get_subset_data_count(database_id=1, subset_id=6)
             except ForbiddenError:
                 pass
 
@@ -229,10 +456,30 @@ class QueryUnitTest(unittest.TestCase):
             mock.head('/api/database/1/subset/6/data', status_code=404)
             # test
             try:
-                response = RestClient().get_subset_data_count(database_id=1, subset_id=6)
+                RestClient().get_subset_data_count(database_id=1, subset_id=6)
             except NotExistsError:
                 pass
 
+    def test_get_subset_data_count_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/subset/6/data', status_code=503)
+            # test
+            try:
+                RestClient().get_subset_data_count(database_id=1, subset_id=6)
+            except ServiceError:
+                pass
+
+    def test_get_subset_data_count_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/subset/6/data', status_code=202)
+            # test
+            try:
+                RestClient().get_subset_data_count(database_id=1, subset_id=6)
+            except ResponseCodeError:
+                pass
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/lib/python/tests/test_unit_rest_client.py b/lib/python/tests/test_unit_rest_client.py
index 0c52cfd33e..e4eef89066 100644
--- a/lib/python/tests/test_unit_rest_client.py
+++ b/lib/python/tests/test_unit_rest_client.py
@@ -1,6 +1,8 @@
 import os
 import unittest
 
+import requests_mock
+
 from dbrepo.RestClient import RestClient
 
 
@@ -12,6 +14,15 @@ class RestClientUnitTest(unittest.TestCase):
         response = RestClient()
         self.assertTrue(response.secure)
 
+    def test_constructor_token_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.get('/api/user', json=[])
+            # test
+            client = RestClient(password='bar')
+            client.get_users()
+            self.assertEqual('bar', client.password)
+
     def test_whoami_anonymous_succeeds(self):
         # test
         response = RestClient().whoami()
diff --git a/lib/python/tests/test_unit_table.py b/lib/python/tests/test_unit_table.py
index 998a1bc4f0..3518439b8c 100644
--- a/lib/python/tests/test_unit_table.py
+++ b/lib/python/tests/test_unit_table.py
@@ -491,7 +491,8 @@ class TableUnitTest(unittest.TestCase):
             # mock
             mock.head('/api/database/1/table/9/data', headers={'X-Count': str(exp)})
             # test
-            response = RestClient().get_table_data_count(database_id=1, table_id=9)
+            response = RestClient().get_table_data_count(database_id=1, table_id=9,
+                                                         timestamp=datetime.datetime(2024, 1, 1, 0, 0, 0, 0))
             self.assertEqual(exp, response)
 
     def test_get_table_data_count_400_fails(self):
@@ -524,6 +525,26 @@ class TableUnitTest(unittest.TestCase):
             except NotExistsError:
                 pass
 
+    def test_get_table_data_count_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/table/9/data', status_code=503)
+            # test
+            try:
+                response = RestClient().get_table_data_count(database_id=1, table_id=9)
+            except ServiceError:
+                pass
+
+    def test_get_table_data_count_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/table/9/data', status_code=202)
+            # test
+            try:
+                response = RestClient().get_table_data_count(database_id=1, table_id=9)
+            except ResponseCodeError:
+                pass
+
     def test_get_table_data_count_not_countable_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
@@ -770,10 +791,7 @@ class TableUnitTest(unittest.TestCase):
                          database_id=1,
                          table_id=2,
                          internal_name="id",
-                         auto_generated=True,
-                         is_primary_key=True,
                          type=ColumnType.BIGINT,
-                         is_public=True,
                          concept=ConceptBrief(id=2,
                                               uri="http://dbpedia.org/page/Category:Precipitation",
                                               name="Precipitation"),
@@ -829,6 +847,45 @@ class TableUnitTest(unittest.TestCase):
             except NotExistsError:
                 pass
 
+    def test_update_table_column_502_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/table/2/column/1', status_code=502)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.update_table_column(database_id=1, table_id=2, column_id=1,
+                                           unit_uri="http://www.wikidata.org/entity/Q119856947",
+                                           concept_uri="http://dbpedia.org/page/Category:Precipitation")
+            except ServiceConnectionError:
+                pass
+
+    def test_update_table_column_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/table/2/column/1', status_code=503)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.update_table_column(database_id=1, table_id=2, column_id=1,
+                                           unit_uri="http://www.wikidata.org/entity/Q119856947",
+                                           concept_uri="http://dbpedia.org/page/Category:Precipitation")
+            except ServiceError:
+                pass
+
+    def test_update_table_column_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.put('/api/database/1/table/2/column/1', status_code=200)
+            # test
+            try:
+                client = RestClient(username="a", password="b")
+                client.update_table_column(database_id=1, table_id=2, column_id=1,
+                                           unit_uri="http://www.wikidata.org/entity/Q119856947",
+                                           concept_uri="http://dbpedia.org/page/Category:Precipitation")
+            except ResponseCodeError:
+                pass
+
     def test_update_table_column_anonymous_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
diff --git a/lib/python/tests/test_unit_view.py b/lib/python/tests/test_unit_view.py
index 842c2d7388..7a8bebe4e0 100644
--- a/lib/python/tests/test_unit_view.py
+++ b/lib/python/tests/test_unit_view.py
@@ -504,6 +504,46 @@ class ViewUnitTest(unittest.TestCase):
             except ForbiddenError:
                 pass
 
+    def test_get_view_data_count_404_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/view/3/data', status_code=404)
+            # test
+            try:
+                response = RestClient().get_view_data_count(database_id=1, view_id=3)
+            except NotExistsError:
+                pass
+
+    def test_get_view_data_count_409_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/view/3/data', status_code=409)
+            # test
+            try:
+                response = RestClient().get_view_data_count(database_id=1, view_id=3)
+            except ExternalSystemError:
+                pass
+
+    def test_get_view_data_count_503_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/view/3/data', status_code=503)
+            # test
+            try:
+                response = RestClient().get_view_data_count(database_id=1, view_id=3)
+            except ServiceError:
+                pass
+
+    def test_get_view_data_count_unknown_fails(self):
+        with requests_mock.Mocker() as mock:
+            # mock
+            mock.head('/api/database/1/view/3/data', status_code=202)
+            # test
+            try:
+                response = RestClient().get_view_data_count(database_id=1, view_id=3)
+            except ResponseCodeError:
+                pass
+
 
 if __name__ == "__main__":
     unittest.main()
-- 
GitLab