From f1185cbbd8d5d23310ea6b5b1b50d134bc5e8e06 Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Thu, 27 Apr 2023 12:06:19 +0200
Subject: [PATCH] Greatly raised coverage for user & discovery service

---
 .../java/at/tuwien/config/GatewayConfig.java  |  32 ++++
 .../{service => config}/JacksonConfig.java    |   2 +-
 .../{service => config}/ReadyConfig.java      |   2 +-
 .../src/main/resources/application-docker.yml |  39 ----
 .../src/main/resources/application-local.yml  |   7 +-
 .../src/main/resources/application.yml        |   7 +-
 .../src/test/java/at/tuwien/BaseUnitTest.java |   9 +
 .../at/tuwien/MockServiceIntegrationTest.java |  37 ++++
 .../src/test/resources/schema.sql             |  25 +++
 dbrepo-metadata-service/pom.xml               |   3 +-
 .../at/tuwien/endpoints/MetadataEndpoint.java |  12 +-
 .../src/main/resources/templates/record.xml   |   2 +-
 .../endpoints/MetadataEndpointUnitTest.java   | 171 ++++++++++++++++++
 .../IdentifierServiceIntegrationTest.java     |  91 ++++++++++
 .../MetadataServiceIntegrationTest.java       | 135 ++++++++++++++
 .../src/test/resources/schema.sql             |  25 +++
 .../repository/jpa/ContainerRepository.java   |   9 +
 .../repository/jpa/DatabaseRepository.java    |   9 +
 .../repository/jpa/ImageRepository.java       |   9 +
 .../repository/jpa/RealmRepository.java       |  11 ++
 .../tuwien/repository/jpa/UserRepository.java |  11 ++
 .../service/impl/IdentifierServiceImpl.java   |   2 +-
 .../service/impl/MetadataServiceImpl.java     |   1 +
 23 files changed, 599 insertions(+), 52 deletions(-)
 create mode 100644 dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java
 rename dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/{service => config}/JacksonConfig.java (97%)
 rename dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/{service => config}/ReadyConfig.java (95%)
 delete mode 100644 dbrepo-discovery-service/rest-service/src/main/resources/application-docker.yml
 create mode 100644 dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
 create mode 100644 dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/MockServiceIntegrationTest.java
 create mode 100644 dbrepo-discovery-service/rest-service/src/test/resources/schema.sql
 create mode 100644 dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java
 create mode 100644 dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
 create mode 100644 dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java
 create mode 100644 dbrepo-metadata-service/rest-service/src/test/resources/schema.sql
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ContainerRepository.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ImageRepository.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/RealmRepository.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java

diff --git a/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java b/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java
new file mode 100644
index 0000000000..cb0c9ffc9e
--- /dev/null
+++ b/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java
@@ -0,0 +1,32 @@
+package at.tuwien.config;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.support.BasicAuthenticationInterceptor;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+
+@Getter
+@Configuration
+public class GatewayConfig {
+
+    @Value("${fda.gateway.endpoint}")
+    private String gatewayEndpoint;
+
+    @Value("${spring.rabbitmq.username}")
+    private String brokerUsername;
+
+    @Value("${spring.rabbitmq.password}")
+    private String brokerPassword;
+
+    @Bean("brokerRestTemplate")
+    public RestTemplate brokerRestTemplate() {
+        final RestTemplate restTemplate = new RestTemplate();
+        restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(gatewayEndpoint));
+        restTemplate.getInterceptors()
+                .add(new BasicAuthenticationInterceptor(brokerUsername, brokerPassword));
+        return restTemplate;
+    }
+}
diff --git a/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/service/JacksonConfig.java b/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/JacksonConfig.java
similarity index 97%
rename from dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/service/JacksonConfig.java
rename to dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/JacksonConfig.java
index 7720fff074..fba7f99cf2 100644
--- a/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/service/JacksonConfig.java
+++ b/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/JacksonConfig.java
@@ -1,4 +1,4 @@
-package at.tuwien.service;
+package at.tuwien.config;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/service/ReadyConfig.java b/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/ReadyConfig.java
similarity index 95%
rename from dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/service/ReadyConfig.java
rename to dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/ReadyConfig.java
index ee5fb357f6..0bee3b961e 100644
--- a/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/service/ReadyConfig.java
+++ b/dbrepo-discovery-service/rest-service/src/main/java/at/tuwien/config/ReadyConfig.java
@@ -1,4 +1,4 @@
-package at.tuwien.service;
+package at.tuwien.config;
 
 import com.google.common.io.Files;
 import org.springframework.beans.factory.annotation.Value;
diff --git a/dbrepo-discovery-service/rest-service/src/main/resources/application-docker.yml b/dbrepo-discovery-service/rest-service/src/main/resources/application-docker.yml
deleted file mode 100644
index 967b742f9e..0000000000
--- a/dbrepo-discovery-service/rest-service/src/main/resources/application-docker.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-app.version: '@project.version@'
-spring:
-  main.banner-mode: off
-  datasource:
-    url: jdbc:mariadb://metadata-db:3306/fda
-    driver-class-name: org.mariadb.jdbc.Driver
-    username: "${METADATA_USERNAME}"
-    password: "${METADATA_PASSWORD}"
-  jpa:
-    show-sql: false
-    database-platform: org.hibernate.dialect.MariaDBDialect
-    hibernate:
-      ddl-auto: validate
-      use-new-id-generator-mappings: false
-    open-in-view: false
-    properties:
-      hibernate:
-        default_schema: fda
-        jdbc:
-          time_zone: UTC
-  application:
-    name: discovery-service
-  cloud:
-    loadbalancer.ribbon.enabled: false
-management.endpoints.web.exposure.include: health,info,prometheus
-server.port: 9090
-logging:
-  pattern.console: "%d %highlight(%-5level) %msg%n"
-  level:
-    root: warn
-    at.tuwien.: "${LOG_LEVEL}"
-    org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug
-eureka:
-  eureka.client.register-with-eureka: false
-  eureka.client.fetch-registry: false
-  instance.hostname: discovery-service
-  client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/
-fda:
-  ready.path: /ready
\ No newline at end of file
diff --git a/dbrepo-discovery-service/rest-service/src/main/resources/application-local.yml b/dbrepo-discovery-service/rest-service/src/main/resources/application-local.yml
index 0d81e2101e..1ab259ae3f 100644
--- a/dbrepo-discovery-service/rest-service/src/main/resources/application-local.yml
+++ b/dbrepo-discovery-service/rest-service/src/main/resources/application-local.yml
@@ -20,6 +20,10 @@ spring:
           time_zone: UTC
   application:
     name: discovery-service
+  rabbitmq:
+    host: localhost
+    username: fda
+    password: fda
   cloud:
     loadbalancer.ribbon.enabled: false
     gateway:
@@ -46,4 +50,5 @@ eureka:
     secure-port-enabled: true
     secure-port: 9090
 fda:
-  ready.path: ./ready
\ No newline at end of file
+  ready.path: ./ready
+  gateway.endpoint: http://localhost:9095
\ No newline at end of file
diff --git a/dbrepo-discovery-service/rest-service/src/main/resources/application.yml b/dbrepo-discovery-service/rest-service/src/main/resources/application.yml
index 56b3f670d0..3d691f1015 100644
--- a/dbrepo-discovery-service/rest-service/src/main/resources/application.yml
+++ b/dbrepo-discovery-service/rest-service/src/main/resources/application.yml
@@ -18,6 +18,10 @@ spring:
           time_zone: UTC
   application:
     name: discovery-service
+  rabbitmq:
+    host: broker-service
+    username: "${BROKER_USERNAME}"
+    password: "${BROKER_PASSWORD}"
   cloud:
     loadbalancer.ribbon.enabled: false
 management.endpoints.web.exposure.include: health,info,prometheus
@@ -40,4 +44,5 @@ eureka:
     secure-port-enabled: true
     secure-port: 9090
 fda:
-  ready.path: /ready
\ No newline at end of file
+  ready.path: /ready
+  gateway.endpoint: "${GATEWAY_ENDPOINT}"
\ No newline at end of file
diff --git a/dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
new file mode 100644
index 0000000000..c5adb93d3d
--- /dev/null
+++ b/dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -0,0 +1,9 @@
+package at.tuwien;
+
+import at.tuwien.test.BaseTest;
+import org.springframework.test.context.TestPropertySource;
+
+@TestPropertySource(locations = "classpath:application.properties")
+public abstract class BaseUnitTest extends BaseTest {
+
+}
\ No newline at end of file
diff --git a/dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/MockServiceIntegrationTest.java b/dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/MockServiceIntegrationTest.java
new file mode 100644
index 0000000000..329ae7be23
--- /dev/null
+++ b/dbrepo-discovery-service/rest-service/src/test/java/at/tuwien/MockServiceIntegrationTest.java
@@ -0,0 +1,37 @@
+package at.tuwien;
+
+import at.tuwien.config.H2Utils;
+import at.tuwien.service.MockService;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+@Log4j2
+@SpringBootTest
+public class MockServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private H2Utils h2Utils;
+
+    @Autowired
+    private MockService mockService;
+
+    @BeforeEach
+    public void beforeEach() {
+        h2Utils.runScript("schema.sql");
+    }
+
+    @Test
+    public void mock_succeeds() {
+
+        /* test */
+        final Boolean response = mockService.mock();
+        assertTrue(response);
+    }
+
+}
diff --git a/dbrepo-discovery-service/rest-service/src/test/resources/schema.sql b/dbrepo-discovery-service/rest-service/src/test/resources/schema.sql
new file mode 100644
index 0000000000..906d8df808
--- /dev/null
+++ b/dbrepo-discovery-service/rest-service/src/test/resources/schema.sql
@@ -0,0 +1,25 @@
+CREATE SCHEMA IF NOT EXISTS `fda`;
+SET SCHEMA `fda`;
+DROP TABLE IF EXISTS fda.mdb_concepts;
+CREATE TABLE IF NOT EXISTS fda.mdb_concepts
+(
+    uri        VARCHAR(500) not null,
+    name       VARCHAR(255),
+    created    timestamp    NOT NULL DEFAULT NOW(),
+    created_by bigint,
+    PRIMARY KEY (uri)
+);
+DROP TABLE IF EXISTS fda.mdb_units;
+CREATE TABLE IF NOT EXISTS fda.mdb_units
+(
+    uri        VARCHAR(500) not null,
+    name       VARCHAR(255),
+    created    timestamp    NOT NULL DEFAULT NOW(),
+    created_by bigint,
+    PRIMARY KEY (uri)
+);
+-- Modified for H2
+-- Assume id=1 is invalid
+-- Assume id=2 is still valid token
+-- CREATE VIEW IF NOT EXISTS fda.mdb_invalid_tokens AS
+-- (SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used` FROM fda.`mdb_tokens` WHERE `id` = 1);
\ No newline at end of file
diff --git a/dbrepo-metadata-service/pom.xml b/dbrepo-metadata-service/pom.xml
index ee05f0602c..7c352ef966 100644
--- a/dbrepo-metadata-service/pom.xml
+++ b/dbrepo-metadata-service/pom.xml
@@ -196,9 +196,10 @@
                         <exclude>at/tuwien/utils/**/*</exclude>
                         <exclude>at/tuwien/seeder/**/*</exclude>
                         <exclude>at/tuwien/mapper/**/*</exclude>
+                        <exclude>at/tuwien/handlers/**/*</exclude>
                         <exclude>at/tuwien/exception/**/*</exclude>
                         <exclude>at/tuwien/config/**/*</exclude>
-                        <exclude>**/DbrepoContainerManagingApplication.class</exclude>
+                        <exclude>**/DbrepoMetadataServiceApplication.class</exclude>
                     </excludes>
                 </configuration>
                 <executions>
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MetadataEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MetadataEndpoint.java
index be5c02e7d1..728920ea26 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MetadataEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MetadataEndpoint.java
@@ -47,7 +47,7 @@ public class MetadataEndpoint {
                     description = "List containers",
                     content = {@Content(mediaType = "text/xml")}),
     })
-    public ResponseEntity<?> identify() {
+    public ResponseEntity<String> identify() {
         log.debug("endpoint identify repository");
         return identifyAlt();
     }
@@ -60,7 +60,7 @@ public class MetadataEndpoint {
                     description = "List containers",
                     content = {@Content(mediaType = "text/xml")}),
     })
-    public ResponseEntity<?> identifyAlt() {
+    public ResponseEntity<String> identifyAlt() {
         log.debug("endpoint identify repository, verb=Identify");
         final String xml = metadataService.identify();
         log.trace("identify repository resulted in xml {}", xml);
@@ -70,7 +70,7 @@ public class MetadataEndpoint {
     @GetMapping(params = "verb=ListIdentifiers", produces = "text/xml;charset=UTF-8")
     @Timed(value = "identifiers.list", description = "Time needed to list the identifiers")
     @Operation(summary = "List the identifiers")
-    public ResponseEntity<?> listIdentifiers(OaiListIdentifiersParameters parameters) {
+    public ResponseEntity<String> listIdentifiers(OaiListIdentifiersParameters parameters) {
         log.debug("endpoint list identifiers, verb=ListIdentifiers, parameters={}", parameters);
         final String xml = metadataService.listIdentifiers(parameters);
         log.trace("list identifiers resulted in xml {}", xml);
@@ -80,9 +80,9 @@ public class MetadataEndpoint {
     @GetMapping(params = "verb=GetRecord", produces = "text/xml;charset=UTF-8")
     @Timed(value = "record.find", description = "Time needed to find a record")
     @Operation(summary = "Get the record")
-    public ResponseEntity<?> getRecord(OaiRecordParameters parameters) {
+    public ResponseEntity<String> getRecord(OaiRecordParameters parameters) {
         log.debug("endpoint get record, verb=GetRecord, parameters={}", parameters);
-        if (!parameters.getMetadataPrefix().equals("oai_dc")) {
+        if (parameters.getMetadataPrefix() != null && !parameters.getMetadataPrefix().equals("oai_dc")) {
             log.trace("metadataPrefix matches oai_dc, failed to serve this format");
             return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                     .body(metadataService.error(OaiErrorType.CANNOT_DISSEMINATE_FORMAT));
@@ -105,7 +105,7 @@ public class MetadataEndpoint {
     @GetMapping(params = "verb=ListMetadataFormats", produces = "text/xml;charset=UTF-8")
     @Timed(value = "formats.list", description = "Time needed to list the metadata formats")
     @Operation(summary = "List the metadata formats")
-    public ResponseEntity<?> listMetadataFormats() {
+    public ResponseEntity<String> listMetadataFormats() {
         log.debug("endpoint list metadata formats, verb=ListMetadataFormats");
         final String xml = metadataService.listMetadataFormats();
         log.trace("list metadata formats resulted in xml {}", xml);
diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/templates/record.xml b/dbrepo-metadata-service/rest-service/src/main/resources/templates/record.xml
index dd5c7df1a0..1574f72adc 100644
--- a/dbrepo-metadata-service/rest-service/src/main/resources/templates/record.xml
+++ b/dbrepo-metadata-service/rest-service/src/main/resources/templates/record.xml
@@ -10,7 +10,7 @@
                 <dc:title>[[${title}]]</dc:title>
                 <dc:publisher>[[${publisher}]]</dc:publisher>
                 <dc:description>[[${description}]]</dc:description>
-                <dc:creator th:each="creator: ${identifier.getCreators()}">[(${creator.getLastname()})], [(${creator.getFirstname()})]</dc:creator>
+                <dc:creator th:each="creator: ${creators}">[(${creator.getLastname()})], [(${creator.getFirstname()})]</dc:creator>
             </oai_dc:dc>
         </metadata>
     </record>
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java
new file mode 100644
index 0000000000..d06533308a
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java
@@ -0,0 +1,171 @@
+package at.tuwien.endpoints;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.OaiListIdentifiersParameters;
+import at.tuwien.OaiRecordParameters;
+import at.tuwien.config.H2Utils;
+import at.tuwien.entities.identifier.Identifier;
+import at.tuwien.exception.IdentifierNotFoundException;
+import at.tuwien.repository.jpa.*;
+import at.tuwien.service.IdentifierService;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
+@Log4j2
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class MetadataEndpointUnitTest extends BaseUnitTest {
+
+    @MockBean
+    private IdentifierRepository identifierRepository;
+
+    @Autowired
+    private MetadataEndpoint metadataEndpoint;
+
+    @Autowired
+    private H2Utils h2Utils;
+
+    @BeforeEach
+    public void beforeEach() {
+        /* schema */
+        h2Utils.runScript("schema.sql");
+    }
+
+    @Test
+    public void identify_succeeds() {
+
+        /* test */
+        final ResponseEntity<String> response = metadataEndpoint.identify();
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final String body = response.getBody();
+        assertNotNull(body);
+    }
+
+    @Test
+    public void identifyAlt_succeeds() {
+
+        /* test */
+        final ResponseEntity<String> response = metadataEndpoint.identifyAlt();
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final String body = response.getBody();
+        assertNotNull(body);
+    }
+
+    @Test
+    public void listIdentifiers_succeeds() {
+        final OaiListIdentifiersParameters parameters = new OaiListIdentifiersParameters();
+
+        /* mock */
+        when(identifierRepository.findAll())
+                .thenReturn(List.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<String> response = metadataEndpoint.listIdentifiers(parameters);
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final String body = response.getBody();
+        assertNotNull(body);
+    }
+
+    @Test
+    public void getRecord_formatMissing_fails() {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+
+        /* mock */
+        when(identifierRepository.findById(IDENTIFIER_1_ID))
+                .thenReturn(Optional.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<?> response = metadataEndpoint.getRecord(parameters);
+        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+    }
+
+    @Test
+    public void getRecord_unsupportedFormat_fails() {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+        parameters.setMetadataPrefix("oai_marc");
+
+        /* mock */
+        when(identifierRepository.findById(IDENTIFIER_1_ID))
+                .thenReturn(Optional.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<?> response = metadataEndpoint.getRecord(parameters);
+        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+    }
+
+    @Test
+    public void getRecord_noIdentifier_fails() {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+        parameters.setMetadataPrefix("oai_dc");
+
+        /* mock */
+        when(identifierRepository.findById(IDENTIFIER_1_ID))
+                .thenReturn(Optional.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<?> response = metadataEndpoint.getRecord(parameters);
+        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+    }
+
+    @Test
+    public void getRecord_succeeds() {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+        parameters.setMetadataPrefix("oai_dc");
+        parameters.setIdentifier(Long.toString(1L));
+
+        /* mock */
+        when(identifierRepository.findById(IDENTIFIER_1_ID))
+                .thenReturn(Optional.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<String> response = metadataEndpoint.getRecord(parameters);
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final String body = response.getBody();
+        assertNotNull(body);
+    }
+
+    @Test
+    public void getRecord_notFound_fails() {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+        parameters.setMetadataPrefix("oai_dc");
+        parameters.setIdentifier(Long.toString(9999L));
+
+        /* mock */
+        when(identifierRepository.findById(IDENTIFIER_1_ID))
+                .thenReturn(Optional.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<?> response = metadataEndpoint.getRecord(parameters);
+        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+    }
+
+    @Test
+    public void listMetadataFormats_succeeds() {
+
+        /* mock */
+        when(identifierRepository.findById(IDENTIFIER_1_ID))
+                .thenReturn(Optional.of(IDENTIFIER_1));
+
+        /* test */
+        final ResponseEntity<String> response = metadataEndpoint.listMetadataFormats();
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final String body = response.getBody();
+        assertNotNull(body);
+    }
+
+}
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
new file mode 100644
index 0000000000..54f9367e07
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
@@ -0,0 +1,91 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.config.H2Utils;
+import at.tuwien.entities.identifier.Identifier;
+import at.tuwien.exception.IdentifierNotFoundException;
+import at.tuwien.repository.jpa.*;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@Log4j2
+@SpringBootTest
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@ExtendWith(SpringExtension.class)
+public class IdentifierServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private ImageRepository imageRepository;
+
+    @Autowired
+    private ContainerRepository containerRepository;
+
+    @Autowired
+    private DatabaseRepository databaseRepository;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private RealmRepository realmRepository;
+
+    @Autowired
+    private IdentifierRepository identifierRepository;
+
+    @Autowired
+    private IdentifierService identifierService;
+
+    @Autowired
+    private H2Utils h2Utils;
+
+    @BeforeEach
+    public void beforeEach() {
+        /* schema */
+        h2Utils.runScript("schema.sql");
+        /* metadata database */
+        imageRepository.save(IMAGE_1_SIMPLE);
+        realmRepository.save(REALM_DBREPO);
+        userRepository.save(USER_1_SIMPLE);
+        containerRepository.save(CONTAINER_1_SIMPLE);
+        databaseRepository.save(DATABASE_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+    }
+
+    @Test
+    public void findAll_succeeds() {
+
+        /* test */
+        final List<Identifier> response = identifierService.findAll();
+        assertEquals(1, response.size());
+    }
+
+    @Test
+    public void find_succeeds() throws IdentifierNotFoundException {
+
+        /* test */
+        final Identifier response = identifierService.find(IDENTIFIER_1_ID);
+        assertEquals(IDENTIFIER_1_ID, response.getId());
+        assertEquals(IDENTIFIER_1_TITLE, response.getTitle());
+    }
+
+    @Test
+    public void find_fails() {
+
+        /* test */
+        assertThrows(IdentifierNotFoundException.class, () -> {
+            identifierService.find(IDENTIFIER_2_ID);
+        });
+    }
+
+}
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java
new file mode 100644
index 0000000000..3fcaa1a838
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java
@@ -0,0 +1,135 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.OaiErrorType;
+import at.tuwien.OaiListIdentifiersParameters;
+import at.tuwien.OaiRecordParameters;
+import at.tuwien.config.H2Utils;
+import at.tuwien.exception.IdentifierNotFoundException;
+import at.tuwien.repository.jpa.*;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Log4j2
+@SpringBootTest
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@ExtendWith(SpringExtension.class)
+public class MetadataServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private ImageRepository imageRepository;
+
+    @Autowired
+    private ContainerRepository containerRepository;
+
+    @Autowired
+    private DatabaseRepository databaseRepository;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private RealmRepository realmRepository;
+
+    @Autowired
+    private IdentifierRepository identifierRepository;
+
+    @Autowired
+    private MetadataService metadataService;
+
+    @Autowired
+    private H2Utils h2Utils;
+
+    @BeforeEach
+    public void beforeEach() {
+        /* schema */
+        h2Utils.runScript("schema.sql");
+        /* metadata database */
+        imageRepository.save(IMAGE_1_SIMPLE);
+        realmRepository.save(REALM_DBREPO);
+        userRepository.save(USER_1_SIMPLE);
+        containerRepository.save(CONTAINER_1_SIMPLE);
+        databaseRepository.save(DATABASE_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+    }
+
+    @Test
+    public void identify_succeeds() {
+
+        /* test */
+        final String response = metadataService.identify();
+        assertTrue(response.contains("repositoryName"));
+        assertTrue(response.contains("baseURL"));
+        assertTrue(response.contains("adminEmail"));
+        assertTrue(response.contains("earliestDatestamp"));
+        assertTrue(response.contains("deletedRecord"));
+        assertTrue(response.contains("granularity"));
+    }
+
+    @Test
+    public void listIdentifiers_succeeds() {
+        final OaiListIdentifiersParameters parameters = new OaiListIdentifiersParameters();
+
+        /* test */
+        final String response = metadataService.listIdentifiers(parameters);
+        assertTrue(response.contains("identifier"));
+        assertTrue(response.contains("datestamp"));
+    }
+
+    @Test
+    public void listMetadataFormats_succeeds() {
+
+        /* test */
+        final String response = metadataService.listMetadataFormats();
+        assertTrue(response.contains("metadataPrefix"));
+        assertTrue(response.contains("schema"));
+        assertTrue(response.contains("metadataNamespace"));
+    }
+
+    @Test
+    public void error_succeeds() {
+
+        /* test */
+        final String response = metadataService.error(OaiErrorType.CANNOT_DISSEMINATE_FORMAT);
+        assertTrue(response.contains("error"));
+    }
+
+    @Test
+    public void getRecord_succeeds() throws IdentifierNotFoundException {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+        parameters.setIdentifier(Long.toString(1L));
+
+        /* test */
+        final String response = metadataService.getRecord(parameters);
+        assertTrue(response.contains("identifier"));
+        assertTrue(response.contains("datestamp"));
+        assertTrue(response.contains("title"));
+        assertTrue(response.contains("description"));
+        assertTrue(response.contains("publisher"));
+    }
+
+    @Test
+    public void getRecord_fails() {
+        final OaiRecordParameters parameters = new OaiRecordParameters();
+        parameters.setIdentifier(Long.toString(9999L));
+
+        /* test */
+        assertThrows(IdentifierNotFoundException.class, () -> {
+            metadataService.getRecord(parameters);
+        });
+    }
+
+}
diff --git a/dbrepo-metadata-service/rest-service/src/test/resources/schema.sql b/dbrepo-metadata-service/rest-service/src/test/resources/schema.sql
new file mode 100644
index 0000000000..906d8df808
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/test/resources/schema.sql
@@ -0,0 +1,25 @@
+CREATE SCHEMA IF NOT EXISTS `fda`;
+SET SCHEMA `fda`;
+DROP TABLE IF EXISTS fda.mdb_concepts;
+CREATE TABLE IF NOT EXISTS fda.mdb_concepts
+(
+    uri        VARCHAR(500) not null,
+    name       VARCHAR(255),
+    created    timestamp    NOT NULL DEFAULT NOW(),
+    created_by bigint,
+    PRIMARY KEY (uri)
+);
+DROP TABLE IF EXISTS fda.mdb_units;
+CREATE TABLE IF NOT EXISTS fda.mdb_units
+(
+    uri        VARCHAR(500) not null,
+    name       VARCHAR(255),
+    created    timestamp    NOT NULL DEFAULT NOW(),
+    created_by bigint,
+    PRIMARY KEY (uri)
+);
+-- Modified for H2
+-- Assume id=1 is invalid
+-- Assume id=2 is still valid token
+-- CREATE VIEW IF NOT EXISTS fda.mdb_invalid_tokens AS
+-- (SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used` FROM fda.`mdb_tokens` WHERE `id` = 1);
\ No newline at end of file
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ContainerRepository.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ContainerRepository.java
new file mode 100644
index 0000000000..d692b00041
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ContainerRepository.java
@@ -0,0 +1,9 @@
+package at.tuwien.repository.jpa;
+
+import at.tuwien.entities.container.Container;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ContainerRepository extends JpaRepository<Container, Long> {
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java
new file mode 100644
index 0000000000..ae76b3951e
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java
@@ -0,0 +1,9 @@
+package at.tuwien.repository.jpa;
+
+import at.tuwien.entities.database.Database;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface DatabaseRepository extends JpaRepository<Database, Long> {
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ImageRepository.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ImageRepository.java
new file mode 100644
index 0000000000..c033bd17dc
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/ImageRepository.java
@@ -0,0 +1,9 @@
+package at.tuwien.repository.jpa;
+
+import at.tuwien.entities.container.image.ContainerImage;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ImageRepository extends JpaRepository<ContainerImage, Long> {
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/RealmRepository.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/RealmRepository.java
new file mode 100644
index 0000000000..6a2a5443d9
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/RealmRepository.java
@@ -0,0 +1,11 @@
+package at.tuwien.repository.jpa;
+
+import at.tuwien.entities.user.Realm;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.UUID;
+
+@Repository
+public interface RealmRepository extends JpaRepository<Realm, UUID> {
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java
new file mode 100644
index 0000000000..3dbd267234
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java
@@ -0,0 +1,11 @@
+package at.tuwien.repository.jpa;
+
+import at.tuwien.entities.user.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.UUID;
+
+@Repository
+public interface UserRepository extends JpaRepository<User, UUID> {
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
index 5b87576ad9..09cf17d37b 100644
--- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
@@ -2,7 +2,7 @@ package at.tuwien.service.impl;
 
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.exception.IdentifierNotFoundException;
-import at.tuwien.repository.jpa.IdentifierRepository;
+import at.tuwien.repository.jpa.*;
 import at.tuwien.service.IdentifierService;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MetadataServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MetadataServiceImpl.java
index d197913a03..78a089213a 100644
--- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MetadataServiceImpl.java
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MetadataServiceImpl.java
@@ -74,6 +74,7 @@ public class MetadataServiceImpl implements MetadataService {
         final Identifier identifier = identifierService.find(id);
         final Context context = new Context();
         context.setVariable("identifier", identifier.getId());
+        context.setVariable("creators", identifier.getCreators());
         context.setVariable("datestamp", metadataMapper.instantToDatestamp(identifier.getCreated()));
         context.setVariable("title", identifier.getTitle());
         context.setVariable("description", identifier.getDescription());
-- 
GitLab