WORKDIR /opt/app/enrichment-coordinator-service
RUN mkdir -p /var/log/enrichment-coordinator-service
RUN mkdir -p /opt/app/enrichment-coordinator-service/etc/cert/
+RUN mkdir -p /var/enrichment-coordinator-service
+RUN chmod -R 777 /var/enrichment-coordinator-service
EXPOSE 8083 8434
trust-store-used: false
trust-store-password: policy_agent
trust-store: /opt/app/enrichment-coordinator-service/etc/cert/truststore.jks
+ vardata-directory: /var/enrichment-coordinator-service
import com.fasterxml.jackson.databind.ObjectMapper;
+import java.lang.invoke.MethodHandles;
+
import org.apache.catalina.connector.Connector;
import org.oransc.enrichment.configuration.ApplicationConfig;
import org.oransc.enrichment.repository.EiJobs;
import org.oransc.enrichment.repository.EiProducers;
import org.oransc.enrichment.repository.EiTypes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
private int httpPort = 0;
private final ApplicationConfig applicationConfig = new ApplicationConfig();
+ private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Bean
public ObjectMapper mapper() {
@Bean
public EiJobs eiJobs() {
- return new EiJobs();
+ EiJobs jobs = new EiJobs(getApplicationConfig());
+ try {
+ jobs.restoreJobsFromDatabase();
+ } catch (Exception e) {
+ logger.error("Could not restore jobs from database: {}", e.getMessage());
+ }
+ return jobs;
}
@Bean
@Value("${app.filepath}")
private String localConfigurationFilePath;
+ @Getter
+ @Value("${app.vardata-directory}")
+ private String vardataDirectory;
+
@Value("${server.ssl.key-store-type}")
private String sslKeyStoreType = "";
package org.oransc.enrichment.repository;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapterFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
+import java.util.ServiceLoader;
import java.util.Vector;
+import org.oransc.enrichment.configuration.ApplicationConfig;
import org.oransc.enrichment.exceptions.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.FileSystemUtils;
/**
* Dynamic representation of all existing EI jobs.
private MultiMap<EiJob> jobsByType = new MultiMap<>();
private MultiMap<EiJob> jobsByOwner = new MultiMap<>();
+ private final Gson gson;
+
+ private final ApplicationConfig config;
+ private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public EiJobs(ApplicationConfig config) {
+ this.config = config;
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ ServiceLoader.load(TypeAdapterFactory.class).forEach(gsonBuilder::registerTypeAdapterFactory);
+ this.gson = gsonBuilder.create();
+ }
+
+ public synchronized void restoreJobsFromDatabase() throws IOException {
+ File dbDir = new File(getDatabaseDirectory());
+ for (File file : dbDir.listFiles()) {
+ String json = Files.readString(file.toPath());
+ EiJob job = gson.fromJson(json, EiJob.class);
+ this.put(job, false);
+ }
+ }
public synchronized void put(EiJob job) {
- allEiJobs.put(job.id(), job);
- jobsByType.put(job.typeId(), job.id(), job);
- jobsByOwner.put(job.owner(), job.id(), job);
+ this.put(job, true);
}
public synchronized Collection<EiJob> getJobs() {
this.allEiJobs.remove(job.id());
jobsByType.remove(job.typeId(), job.id());
jobsByOwner.remove(job.owner(), job.id());
+
+ try {
+ Files.delete(getPath(job));
+ } catch (IOException e) {
+ logger.warn("Could not remove file: {}", e.getMessage());
+ }
+
}
public synchronized int size() {
this.allEiJobs.clear();
this.jobsByType.clear();
jobsByOwner.clear();
+ try {
+ FileSystemUtils.deleteRecursively(Path.of(getDatabaseDirectory()));
+ } catch (IOException e) {
+ logger.warn("Could not delete database : {}", e.getMessage());
+ }
+ }
+
+ private void put(EiJob job, boolean storePersistently) {
+ allEiJobs.put(job.id(), job);
+ jobsByType.put(job.typeId(), job.id(), job);
+ jobsByOwner.put(job.owner(), job.id(), job);
+ if (storePersistently) {
+ storeJobInFile(job);
+ }
+ }
+
+ private void storeJobInFile(EiJob job) {
+ try {
+ Files.createDirectories(Paths.get(getDatabaseDirectory()));
+ try (PrintStream out = new PrintStream(new FileOutputStream(getFile(job)))) {
+ out.print(gson.toJson(job));
+ }
+ } catch (Exception e) {
+ logger.warn("Could not save job: {} {}", job.id(), e.getMessage());
+ }
+ }
+
+ private File getFile(EiJob job) {
+ return getPath(job).toFile();
+ }
+
+ private Path getPath(EiJob job) {
+ return Path.of(getDatabaseDirectory(), job.id());
+ }
+
+ private String getDatabaseDirectory() {
+ return config.getVardataDirectory() + "/database";
}
}
@TestPropertySource(
properties = { //
"server.ssl.key-store=./config/keystore.jks", //
- "app.webclient.trust-store=./config/truststore.jks"})
+ "app.webclient.trust-store=./config/truststore.jks", //
+ "app.vardata-directory=./target"})
class ApplicationTest {
private final String EI_TYPE_ID = "typeId";
private final String EI_PRODUCER_ID = "producerId";
assertThat(resp.getBody()).contains("hunky dory");
}
+ @Test
+ void testEiJobDatabase() throws Exception {
+ putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
+ putEiJob(EI_TYPE_ID, "jobId1");
+ putEiJob(EI_TYPE_ID, "jobId2");
+
+ assertThat(this.eiJobs.size()).isEqualTo(2);
+
+ {
+ // Restore the jobs
+ EiJobs jobs = new EiJobs(this.applicationConfig);
+ jobs.restoreJobsFromDatabase();
+ assertThat(jobs.size()).isEqualTo(2);
+ jobs.remove("jobId1");
+ jobs.remove("jobId2");
+ }
+ {
+ // Restore the jobs, no jobs in database
+ EiJobs jobs = new EiJobs(this.applicationConfig);
+ jobs.restoreJobsFromDatabase();
+ assertThat(jobs.size()).isEqualTo(0);
+ }
+
+ this.eiJobs.remove("jobId1"); // removing a job when the db file is gone
+ assertThat(this.eiJobs.size()).isEqualTo(1);
+ }
+
private void deleteEiProducer(String eiProducerId) {
String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId;
restClient().deleteForEntity(url).block();
{
- "openapi": "3.0.0",
- "info": {
- "title": "rAPP Catalogue API",
- "description": "The Non RT-RIC Service Catalogue provides a way for services to register themselves for other services to discover.",
- "version": "1.0.0"
- },
- "paths": {
- "/services": {
- "get": {
- "summary": "Service names",
- "deprecated": false,
- "operationId": "getServiceNamesUsingGET",
- "responses": {
- "200": {
- "description": "Service names",
- "content": {
- "application/json": {
- "schema": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "openapi": "3.0.0",
+ "info": {
+ "title": "rAPP Catalogue API",
+ "description": "The Non RT-RIC Service Catalogue provides a way for services to register themselves for other services to discover.",
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "/"
+ }
+ ],
+ "paths": {
+ "/services": {
+ "get": {
+ "summary": "Services",
+ "deprecated": false,
+ "operationId": "getServices",
+ "responses": {
+ "200": {
+ "description": "Services",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/service"
+ }
+ }
+ }
+ }
+ }
},
- "example": [
- "DroneIdentifier",
- "Collector"
+ "tags": [
+ "rAPP Catalogue API"
]
- }
}
- },
- "401": {
- "description": "Unauthorized"
- },
- "403": {
- "description": "Forbidden"
- },
- "404": {
- "description": "Not used"
- }
},
- "tags": [
- "rAPP Catalogue API"
- ]
- }
- },
- "/services/{serviceName}": {
- "get": {
- "summary": "Individual Service",
- "deprecated": false,
- "operationId": "getIndividualServiceUsingGET",
- "responses": {
- "200": {
- "description": "EI Job",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/service"
- }
- }
+ "/services/{serviceName}": {
+ "get": {
+ "summary": "Individual Service",
+ "deprecated": false,
+ "operationId": "getIndividualService",
+ "responses": {
+ "200": {
+ "description": "Service",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/service"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Service is not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error_information"
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "in": "path",
+ "name": "serviceName",
+ "description": "serviceName",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "example": "DroneIdentifier"
+ }
+ ],
+ "tags": [
+ "rAPP Catalogue API"
+ ]
+ },
+ "put": {
+ "summary": "Create or update a Service",
+ "deprecated": false,
+ "operationId": "putIndividualService",
+ "responses": {
+ "200": {
+ "description": "Service updated"
+ },
+ "201": {
+ "description": "Service created",
+ "headers": {
+ "Location": {
+ "schema": {
+ "type": "string"
+ },
+ "description": "URL to the created Service"
+ }
+ }
+ },
+ "400": {
+ "description": "Provided service is not correct",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error_information"
+ },
+ "example": {
+ "detail": "Service is missing required property: version",
+ "status": 400
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "serviceName",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "example": "DroneIdentifier"
+ }
+ ],
+ "requestBody": {
+ "description": "Service to create/update",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/inputService"
+ }
+ }
+ }
+ },
+ "tags": [
+ "rAPP Catalogue API"
+ ]
+ },
+ "delete": {
+ "summary": "Remove a Service from the catalogue",
+ "deprecated": false,
+ "operationId": "deleteIndividualService",
+ "responses": {
+ "204": {
+ "description": "Service deleted"
+ }
+ },
+ "parameters": [
+ {
+ "name": "serviceName",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "example": "DroneIdentifier"
+ }
+ ],
+ "tags": [
+ "rAPP Catalogue API"
+ ]
}
- },
- "401": {
- "description": "Unauthorized"
- },
- "403": {
- "description": "Forbidden"
- },
- "404": {
- "description": "Service is not found",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/error_information"
+ }
+ },
+ "components": {
+ "schemas": {
+ "inputService": {
+ "description": "A Service to register",
+ "type": "object",
+ "title": "inputService",
+ "required": [
+ "version"
+ ],
+ "properties": {
+ "version": {
+ "description": "Version of the Service",
+ "type": "string",
+ "example": "1.0.0"
+ },
+ "display_name": {
+ "description": "Display name for the Service",
+ "type": "string",
+ "example": "Drone Identifier"
+ },
+ "description": {
+ "description": "Description of the Service",
+ "type": "string",
+ "example": "Detects if a UE is a drone"
+ }
}
- }
- }
- }
- },
- "parameters": [
- {
- "in": "path",
- "name": "serviceName",
- "description": "serviceName",
- "schema": {
- "type": "string"
},
- "required": true,
- "example": "DroneIdentifier"
- }
- ],
- "tags": [
- "rAPP Catalogue API"
- ]
- },
- "put": {
- "summary": "Create or update a Service",
- "deprecated": false,
- "operationId": "putIndividualServiceUsingPUT",
- "responses": {
- "200": {
- "description": "Service updated"
- },
- "201": {
- "description": "Service created"
- },
- "401": {
- "description": "Unauthorized"
- },
- "403": {
- "description": "Forbidden"
- },
- "404": {
- "description": "Provided service is not correct",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/error_information"
- },
- "example": {
- "detail": "Service is missing required property version",
- "status": 404
+ "service": {
+ "description": "A Service",
+ "type": "object",
+ "title": "service",
+ "required": [
+ "name",
+ "version",
+ "registrationDate"
+ ],
+ "properties": {
+ "name": {
+ "description": "Unique identifier of the Service",
+ "type": "string",
+ "example": "DroneIdentifier"
+ },
+ "version": {
+ "description": "Version of the Service",
+ "type": "string",
+ "example": "1.0.0"
+ },
+ "display_name": {
+ "description": "Display name for the Service",
+ "type": "string",
+ "example": "Drone Identifier"
+ },
+ "description": {
+ "description": "Description of the Service",
+ "type": "string",
+ "example": "Detects if a UE is a drone"
+ },
+ "registrationDate": {
+ "description": "Date when the Service was registered in the catalogue",
+ "type": "string",
+ "example": "2020-11-03"
+ }
}
- }
- }
- }
- },
- "parameters": [
- {
- "name": "serviceName",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
},
- "example": "DroneIdentifier"
- }
- ],
- "requestBody": {
- "description": "Service to create/update",
- "required": true,
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/service"
- }
- }
- }
- },
- "tags": [
- "rAPP Catalogue API"
- ]
- },
- "delete": {
- "summary": "Remove a Service from the catalogue",
- "deprecated": false,
- "operationId": "deleteIndividualServiceUsingDELETE",
- "responses": {
- "200": {
- "description": "Not used"
- },
- "204": {
- "description": "Job deleted"
- },
- "401": {
- "description": "Unauthorized"
- },
- "403": {
- "description": "Forbidden"
- },
- "404": {
- "description": "Service is not found",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/error_information"
+ "error_information": {
+ "description": "Problem as defined in https://tools.ietf.org/html/rfc7807",
+ "type": "object",
+ "title": "error_information",
+ "properties": {
+ "detail": {
+ "description": "A human-readable explanation specific to this occurrence of the problem.",
+ "type": "string",
+ "example": "Service not found"
+ },
+ "status": {
+ "format": "int32",
+ "description": "The HTTP status code for this occurrence of the problem.",
+ "type": "integer",
+ "example": 404
+ }
}
- }
}
- }
- },
- "parameters": [
- {
- "name": "serviceName",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "example": "DroneIdentifier"
- }
- ],
- "tags": [
- "rAPP Catalogue API"
- ]
- }
- }
- },
- "components": {
- "schemas": {
- "service": {
- "description": "A Service",
- "type": "object",
- "title": "service",
- "required": [
- "version"
- ],
- "properties": {
- "version": {
- "description": "Version of the Service",
- "type": "string",
- "example": "1.0.0"
- },
- "display_name": {
- "description": "Display name for the Service",
- "type": "string",
- "example": "Drone Identifier"
- },
- "description": {
- "description": "Description of the Service",
- "type": "string",
- "example": "Detects if a UE is a drone"
- }
- }
- },
- "error_information": {
- "description": "Problem as defined in https://tools.ietf.org/html/rfc7807",
- "type": "object",
- "title": "error_information",
- "properties": {
- "detail": {
- "description": "A human-readable explanation specific to this occurrence of the problem.",
- "type": "string",
- "example": "Service not found"
- },
- "status": {
- "format": "int32",
- "description": "The HTTP status code generated by the origin server for this occurrence of the problem.",
- "type": "integer",
- "example": 404
- }
}
- }
}
- }
-}
\ No newline at end of file
+}
get:
tags:
- rAPP Catalogue API
- summary: Service names
- operationId: getServiceNamesUsingGET
+ summary: Services
+ operationId: getServices
responses:
200:
- description: Service names
+ description: Services
content:
application/json:
schema:
type: array
items:
- type: string
- example:
- - DroneIdentifier
- - Collector
- 401:
- description: Unauthorized
- 403:
- description: Forbidden
- 404:
- description: Not used
+ $ref: '#/components/schemas/service'
deprecated: false
/services/{serviceName}:
get:
tags:
- rAPP Catalogue API
summary: Individual Service
- operationId: getIndividualServiceUsingGET
+ operationId: getIndividualService
parameters:
- name: serviceName
in: path
example: DroneIdentifier
responses:
200:
- description: EI Job
+ description: Service
content:
application/json:
schema:
$ref: '#/components/schemas/service'
- 401:
- description: Unauthorized
- 403:
- description: Forbidden
404:
description: Service is not found
content:
tags:
- rAPP Catalogue API
summary: Create or update a Service
- operationId: putIndividualServiceUsingPUT
+ operationId: putIndividualService
parameters:
- name: serviceName
in: path
content:
application/json:
schema:
- $ref: '#/components/schemas/service'
+ $ref: '#/components/schemas/inputService'
required: true
responses:
200:
description: Service updated
201:
description: Service created
- 401:
- description: Unauthorized
- 403:
- description: Forbidden
- 404:
+ headers:
+ Location:
+ description: URL to the created Service
+ style: simple
+ explode: false
+ schema:
+ type: string
+ 400:
description: Provided service is not correct
content:
application/json:
schema:
$ref: '#/components/schemas/error_information'
example:
- detail: Service is missing required property version
- status: 404
+ detail: 'Service is missing required property: version'
+ status: 400
deprecated: false
delete:
tags:
- rAPP Catalogue API
summary: Remove a Service from the catalogue
- operationId: deleteIndividualServiceUsingDELETE
+ operationId: deleteIndividualService
parameters:
- name: serviceName
in: path
type: string
example: DroneIdentifier
responses:
- 200:
- description: Not used
204:
- description: Job deleted
- 401:
- description: Unauthorized
- 403:
- description: Forbidden
- 404:
- description: Service is not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/error_information'
+ description: Service deleted
deprecated: false
components:
schemas:
+ inputService:
+ title: inputService
+ required:
+ - version
+ type: object
+ properties:
+ version:
+ type: string
+ description: Version of the Service
+ example: 1.0.0
+ display_name:
+ type: string
+ description: Display name for the Service
+ example: Drone Identifier
+ description:
+ type: string
+ description: Description of the Service
+ example: Detects if a UE is a drone
+ description: A Service to register
service:
title: service
required:
+ - name
+ - registrationDate
- version
type: object
properties:
+ name:
+ type: string
+ description: Unique identifier of the Service
+ example: DroneIdentifier
version:
type: string
description: Version of the Service
type: string
description: Description of the Service
example: Detects if a UE is a drone
+ registrationDate:
+ type: string
+ description: Date when the Service was registered in the catalogue
+ example: 2020-11-03
description: A Service
error_information:
title: error_information
example: Service not found
status:
type: integer
- description: The HTTP status code generated by the origin server for this
- occurrence of the problem.
+ description: The HTTP status code for this occurrence of the problem.
format: int32
example: 404
description: Problem as defined in https://tools.ietf.org/html/rfc7807
<jackson-databind-nullable.version>0.2.1</jackson-databind-nullable.version>\r
<openapi-generator-maven-plugin.version>4.3.1</openapi-generator-maven-plugin.version>\r
<swagger-codegen-maven-plugin.version>3.0.11</swagger-codegen-maven-plugin.version>\r
+ <jacoco-maven-plugin.version>0.8.6</jacoco-maven-plugin.version>\r
<docker-maven-plugin.version>0.30.0</docker-maven-plugin.version>\r
</properties>\r
\r
<artifactId>junit-jupiter-api</artifactId>\r
<scope>test</scope>\r
</dependency>\r
+ <dependency>\r
+ <groupId>org.mockito</groupId>\r
+ <artifactId>mockito-junit-jupiter</artifactId>\r
+ <scope>test</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.mockito</groupId>\r
+ <artifactId>mockito-core</artifactId>\r
+ <scope>test</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.junit.jupiter</groupId>\r
+ <artifactId>junit-jupiter-engine</artifactId>\r
+ <scope>test</scope>\r
+ </dependency>\r
</dependencies>\r
\r
<build>\r
</execution>\r
</executions>\r
</plugin>\r
+ <plugin>\r
+ <groupId>org.jacoco</groupId>\r
+ <artifactId>jacoco-maven-plugin</artifactId>\r
+ <version>${jacoco-maven-plugin.version}</version>\r
+ <executions>\r
+ <execution>\r
+ <id>default-prepare-agent</id>\r
+ <goals>\r
+ <goal>prepare-agent</goal>\r
+ </goals>\r
+ </execution>\r
+ <execution>\r
+ <id>default-report</id>\r
+ <phase>prepare-package</phase>\r
+ <goals>\r
+ <goal>report</goal>\r
+ </goals>\r
+ </execution>\r
+ </executions>\r
+ </plugin>\r
<plugin>\r
<groupId>io.fabric8</groupId>\r
<artifactId>docker-maven-plugin</artifactId>\r
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.rappcatalogue.api;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
+import static org.springframework.http.HttpStatus.NOT_FOUND;
+
+import org.oransc.rappcatalogue.exception.HeaderException;
+import org.oransc.rappcatalogue.exception.InvalidServiceException;
+import org.oransc.rappcatalogue.exception.ServiceNotFoundException;
+import org.oransc.rappcatalogue.model.ErrorInformation;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@ControllerAdvice
+public class GeneralRappCatalogueControllerAdvisor extends ResponseEntityExceptionHandler {
+ @ExceptionHandler(InvalidServiceException.class)
+ public ResponseEntity<Object> handleInvalidServiceException(
+ InvalidServiceException ex) {
+
+ return new ResponseEntity<>(getErrorInformation(ex, BAD_REQUEST), BAD_REQUEST);
+ }
+
+ @ExceptionHandler(ServiceNotFoundException.class)
+ public ResponseEntity<Object> handleServiceNotFoundException(
+ ServiceNotFoundException ex) {
+
+ return new ResponseEntity<>(getErrorInformation(ex, NOT_FOUND), NOT_FOUND);
+ }
+
+ @ExceptionHandler(HeaderException.class)
+ public ResponseEntity<Object> handleHeaderException(
+ HeaderException ex) {
+
+ return new ResponseEntity<>(getErrorInformation(ex, INTERNAL_SERVER_ERROR), INTERNAL_SERVER_ERROR);
+ }
+
+ private ErrorInformation getErrorInformation(Exception cause, HttpStatus status) {
+ ErrorInformation errorInfo = new ErrorInformation();
+ errorInfo.setDetail(cause.getMessage());
+ errorInfo.setStatus(status.value());
+ return errorInfo;
+ }
+}
+/*-\r
+ * ========================LICENSE_START=================================\r
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.\r
+ * ======================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ========================LICENSE_END===================================\r
+ */\r
+\r
package org.oransc.rappcatalogue.api;\r
\r
-import java.util.Arrays;\r
+import java.io.IOException;\r
+import java.sql.Date;\r
+import java.util.ArrayList;\r
import java.util.List;\r
-\r
+import java.util.Optional;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+import org.oransc.rappcatalogue.exception.HeaderException;\r
+import org.oransc.rappcatalogue.exception.InvalidServiceException;\r
+import org.oransc.rappcatalogue.exception.ServiceNotFoundException;\r
+import org.oransc.rappcatalogue.model.InputService;\r
+import org.oransc.rappcatalogue.model.Service;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
import org.springframework.http.HttpStatus;\r
import org.springframework.http.ResponseEntity;\r
+import org.springframework.web.context.request.NativeWebRequest;\r
\r
@org.springframework.stereotype.Service\r
public class ServicesApiDelegateImpl implements ServicesApiDelegate {\r
\r
+ private static final String LOCATION_HEADER = "Location";\r
+\r
+ @Autowired\r
+ private NativeWebRequest nativeWebRequest;\r
+\r
+ private ConcurrentHashMap<String, Service> registeredServices = new ConcurrentHashMap<>();\r
+\r
+ ServicesApiDelegateImpl(NativeWebRequest nativeWebRequest) {\r
+ this.nativeWebRequest = nativeWebRequest;\r
+ }\r
+\r
+ @Override\r
+ public Optional<NativeWebRequest> getRequest() {\r
+ return Optional.of(nativeWebRequest);\r
+ }\r
+\r
@Override\r
- public ResponseEntity<Void> deleteIndividualServiceUsingDELETE(String serviceName) {\r
- return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);\r
+ public ResponseEntity<Service> getIndividualService(String serviceName) {\r
+ Service service = registeredServices.get(serviceName);\r
+ if (service != null) {\r
+ return ResponseEntity.ok(service);\r
+ } else {\r
+ throw new ServiceNotFoundException(serviceName);\r
+ }\r
}\r
\r
- // @Override\r
- // public ResponseEntity<Service> getIndividualServiceUsingGET(String serviceName) {\r
- // return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);\r
+ @Override\r
+ public ResponseEntity<List<Service>> getServices() {\r
+ return ResponseEntity.ok(new ArrayList<>(registeredServices.values()));\r
+ }\r
+\r
+ @Override\r
+ public ResponseEntity<Void> putIndividualService(String serviceName, InputService inputService) {\r
+ if (isServiceValid(inputService)) {\r
+ if (registeredServices.put(serviceName, createService(serviceName, inputService)) == null) {\r
+ try {\r
+ getRequest().ifPresent(request -> addLocationHeaderToResponse(serviceName, request));\r
+ } catch (Exception e) {\r
+ registeredServices.remove(serviceName);\r
+ throw e;\r
+ }\r
+ return new ResponseEntity<>(HttpStatus.CREATED);\r
+ } else {\r
+ return new ResponseEntity<>(HttpStatus.OK);\r
+ }\r
+ } else {\r
+ throw new InvalidServiceException();\r
+ }\r
+ }\r
\r
- // }\r
+ private void addLocationHeaderToResponse(String serviceName, NativeWebRequest request) {\r
+ try {\r
+ HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class);\r
+ HttpServletResponse nativeResponse = request.getNativeResponse(HttpServletResponse.class);\r
+ if (nativeRequest != null && nativeResponse != null) {\r
+ StringBuffer requestURL = nativeRequest.getRequestURL();\r
+ nativeResponse.addHeader(LOCATION_HEADER, requestURL.toString());\r
+ nativeResponse.getWriter().print("");\r
+ } else {\r
+ throw new HeaderException(LOCATION_HEADER, serviceName,\r
+ new Exception("Native Request or Response missing"));\r
+ }\r
+ } catch (IOException e) {\r
+ throw new HeaderException(LOCATION_HEADER, serviceName, e);\r
+ }\r
+ }\r
\r
@Override\r
- public ResponseEntity<List<String>> getServiceNamesUsingGET() {\r
- List<String> services = Arrays.asList("a", "b");\r
- return ResponseEntity.ok(services);\r
+ public ResponseEntity<Void> deleteIndividualService(String serviceName) {\r
+ registeredServices.remove(serviceName);\r
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);\r
}\r
\r
- // @Override\r
- // public ResponseEntity<Void> putIndividualServiceUsingPUT(String serviceName, Service service) {\r
- // return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);\r
+ /*\r
+ * java:S2589: Boolean expressions should not be gratuitous.\r
+ * Even though the version property is marked as @NotNull, it might be null coming from the client, hence the null\r
+ * check is needed.\r
+ */\r
+ @SuppressWarnings("java:S2589")\r
+ private boolean isServiceValid(InputService service) {\r
+ String version = service.getVersion();\r
+ return version != null && !version.isBlank();\r
+ }\r
\r
- // }\r
+ private Service createService(String serviceName, InputService inputService) {\r
+ Service service = new Service();\r
+ service.setName(serviceName);\r
+ service.setDescription(inputService.getDescription());\r
+ service.setDisplayName(inputService.getDisplayName());\r
+ service.setVersion(inputService.getVersion());\r
+ service.setRegistrationDate(getTodaysDate());\r
+ return service;\r
+ }\r
+\r
+ private String getTodaysDate() {\r
+ long millis = System.currentTimeMillis();\r
+ Date date = new Date(millis);\r
+ return date.toString();\r
+ }\r
}\r
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.rappcatalogue.exception;
+
+public class HeaderException extends RuntimeException {
+
+ private static final long serialVersionUID = -7798178963078284655L;
+
+ public HeaderException(String header, String serviceName, Exception cause) {
+ super(String.format("Unable to set header %s in put response for service %s. Cause: %s", header, serviceName,
+ cause.getMessage()));
+ }
+
+}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.rappcatalogue.exception;
+
+public class InvalidServiceException extends RuntimeException {
+ private static final long serialVersionUID = 3849219105170316564L;
+
+ public InvalidServiceException() {
+ super("Service is missing required property: version");
+ }
+}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.rappcatalogue.exception;
+
+public class ServiceNotFoundException extends RuntimeException {
+ private static final long serialVersionUID = 6579271315716003988L;
+
+ public ServiceNotFoundException(String serviceName) {
+ super(String.format("Service %s not found", serviceName));
+ }
+}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.rappcatalogue.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
+import static org.springframework.http.HttpStatus.NOT_FOUND;
+
+import org.junit.jupiter.api.Test;
+import org.oransc.rappcatalogue.exception.HeaderException;
+import org.oransc.rappcatalogue.exception.InvalidServiceException;
+import org.oransc.rappcatalogue.exception.ServiceNotFoundException;
+import org.oransc.rappcatalogue.model.ErrorInformation;
+import org.springframework.http.ResponseEntity;
+
+class GeneralRappCatalogueControllerAdvisorTest {
+
+ @Test
+ void handleInvalidServiceException_shouldReturnBadRequestWithMessage() {
+ GeneralRappCatalogueControllerAdvisor advisorUnderTest = new GeneralRappCatalogueControllerAdvisor();
+
+ InvalidServiceException exception = new InvalidServiceException();
+
+ ResponseEntity<Object> response = advisorUnderTest.handleInvalidServiceException(exception);
+
+ assertThat(response.getStatusCode()).isEqualTo(BAD_REQUEST);
+ ErrorInformation body = (ErrorInformation) response.getBody();
+ assertThat(body.getStatus()).isEqualTo(BAD_REQUEST.value());
+ assertThat(body.getDetail()).isEqualTo("Service is missing required property: version");
+ }
+
+ @Test
+ void handleServiceNotFoundException_shouldReturnNotFoundWithMessage() {
+ GeneralRappCatalogueControllerAdvisor advisorUnderTest = new GeneralRappCatalogueControllerAdvisor();
+
+ ServiceNotFoundException exception = new ServiceNotFoundException("Name");
+
+ ResponseEntity<Object> response = advisorUnderTest.handleServiceNotFoundException(exception);
+
+ assertThat(response.getStatusCode()).isEqualTo(NOT_FOUND);
+ ErrorInformation body = (ErrorInformation) response.getBody();
+ assertThat(body.getStatus()).isEqualTo(NOT_FOUND.value());
+ assertThat(body.getDetail()).isEqualTo("Service Name not found");
+ }
+
+ @Test
+ void handleHeaderException_shouldReturnInternalServerErrorWithMessage() {
+ GeneralRappCatalogueControllerAdvisor advisorUnderTest = new GeneralRappCatalogueControllerAdvisor();
+
+ String serviceName = "Service";
+ HeaderException exception = new HeaderException("Header", serviceName, new Exception("Cause"));
+
+ ResponseEntity<Object> response = advisorUnderTest.handleHeaderException(exception);
+
+ assertThat(response.getStatusCode()).isEqualTo(INTERNAL_SERVER_ERROR);
+ ErrorInformation body = (ErrorInformation) response.getBody();
+ assertThat(body.getStatus()).isEqualTo(INTERNAL_SERVER_ERROR.value());
+ assertThat(body.getDetail())
+ .isEqualTo("Unable to set header Header in put response for service " + serviceName + ". Cause: Cause");
+ }
+}
package org.oransc.rappcatalogue.api;\r
\r
import static org.assertj.core.api.Assertions.assertThat;\r
+import static org.junit.jupiter.api.Assertions.assertThrows;\r
+import static org.mockito.Mockito.mock;\r
+import static org.mockito.Mockito.verify;\r
+import static org.mockito.Mockito.when;\r
+import static org.springframework.http.HttpStatus.CREATED;\r
+import static org.springframework.http.HttpStatus.NO_CONTENT;\r
+import static org.springframework.http.HttpStatus.OK;\r
\r
+import java.io.IOException;\r
+import java.io.PrintWriter;\r
+import java.sql.Date;\r
import java.util.Arrays;\r
import java.util.List;\r
-\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
import org.junit.jupiter.api.Test;\r
import org.junit.jupiter.api.extension.ExtendWith;\r
-import org.springframework.http.HttpStatus;\r
+import org.mockito.Mock;\r
+import org.mockito.junit.jupiter.MockitoExtension;\r
+import org.oransc.rappcatalogue.exception.HeaderException;\r
+import org.oransc.rappcatalogue.exception.InvalidServiceException;\r
+import org.oransc.rappcatalogue.exception.ServiceNotFoundException;\r
+import org.oransc.rappcatalogue.model.InputService;\r
+import org.oransc.rappcatalogue.model.Service;\r
import org.springframework.http.ResponseEntity;\r
-import org.springframework.test.context.junit.jupiter.SpringExtension;\r
+import org.springframework.web.context.request.NativeWebRequest;\r
\r
-@ExtendWith(SpringExtension.class)\r
+@ExtendWith(MockitoExtension.class)\r
class ServicesApiDelegateImplTest {\r
\r
+ @Mock\r
+ NativeWebRequest webRequestMock;\r
+\r
+ private static final String INVALID_SERVICE_MESSAGE = "Service is missing required property: version";\r
+ private static final String SERVICE_NAME = "Service Name";\r
+ private static final String SERVICE_DESCRIPTION = "description";\r
+ private static final String SERVICE_VERSION = "1.0";\r
+ private static final String SERVICE_DISPLAY_NAME = "Display Name";\r
+\r
+ @Test\r
+ void getAddedService_shouldReturnService() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(webRequestMock);\r
+\r
+ InputService service = new InputService();\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setVersion(SERVICE_VERSION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ whenPrintResponseShouldWork();\r
+\r
+ delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+\r
+ ResponseEntity<Service> response = delegateUnderTest.getIndividualService(SERVICE_NAME);\r
+\r
+ assertThat(response.getStatusCode()).isEqualTo(OK);\r
+ assertThat(response.getBody().getName()).isEqualTo(SERVICE_NAME);\r
+ }\r
+\r
+ @Test\r
+ void getMissingService_shouldThrowException() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(null);\r
+\r
+ Exception exception = assertThrows(ServiceNotFoundException.class, () -> {\r
+ delegateUnderTest.getIndividualService(SERVICE_NAME);\r
+ });\r
+\r
+ String expectedMessage = "Service " + SERVICE_NAME + " not found";\r
+ String actualMessage = exception.getMessage();\r
+\r
+ assertThat(actualMessage).isEqualTo(expectedMessage);\r
+ }\r
+\r
+ @Test\r
+ void putNewValidService_shouldBeCreatedAndRegisteredAndUrlToNewServiceAddedToLocationHeaderInResponse() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(webRequestMock);\r
+\r
+ InputService service = new InputService();\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setVersion(SERVICE_VERSION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ String urlToCreatedService = "URL to created Service";\r
+ HttpServletResponse servletResponseMock = whenPrintResponseShouldWork(urlToCreatedService);\r
+\r
+ ResponseEntity<Void> putResponse = delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+\r
+ assertThat(putResponse.getStatusCode()).isEqualTo(CREATED);\r
+ verify(servletResponseMock).addHeader("Location", urlToCreatedService);\r
+\r
+ ResponseEntity<Service> getResponse = delegateUnderTest.getIndividualService(SERVICE_NAME);\r
+\r
+ assertThat(getResponse.getStatusCode()).isEqualTo(OK);\r
+ Service body = getResponse.getBody();\r
+ assertThat(body.getName()).isEqualTo(SERVICE_NAME);\r
+ assertThat(body.getRegistrationDate()).isEqualTo(getTodaysDate());\r
+ }\r
+\r
+ @Test\r
+ void putModifiedService_shouldBeModified() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(webRequestMock);\r
+\r
+ InputService service = new InputService();\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setVersion(SERVICE_VERSION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ whenPrintResponseShouldWork();\r
+\r
+ delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+\r
+ String newDescription = "New description";\r
+ service.setDescription(newDescription);\r
+ ResponseEntity<Void> putResponse = delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+\r
+ assertThat(putResponse.getStatusCode()).isEqualTo(OK);\r
+\r
+ ResponseEntity<Service> getResponse = delegateUnderTest.getIndividualService(SERVICE_NAME);\r
+\r
+ assertThat(getResponse.getStatusCode()).isEqualTo(OK);\r
+ assertThat(getResponse.getBody().getDescription()).isEqualTo(newDescription);\r
+ }\r
+\r
+ @Test\r
+ void putServiceWithVersionNull_shouldThrowException() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(null);\r
+\r
+ InputService service = new InputService();\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ Exception exception = assertThrows(InvalidServiceException.class, () -> {\r
+ delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+ });\r
+\r
+ assertThat(exception.getMessage()).isEqualTo(INVALID_SERVICE_MESSAGE);\r
+ }\r
+\r
+ @Test\r
+ void putServiceWithBlankVersion_shouldThrowException() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(null);\r
+\r
+ InputService service = new InputService();\r
+ service.setVersion("");\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ Exception exception = assertThrows(InvalidServiceException.class, () -> {\r
+ delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+ });\r
+\r
+ assertThat(exception.getMessage()).isEqualTo(INVALID_SERVICE_MESSAGE);\r
+ }\r
+\r
@Test\r
- void putValidService_shouldBeOk() {\r
- ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl();\r
+ void putServiceWhenIoExceptionAddingHeader_shouldThrowExceptionAndNoServiceCreated() throws Exception {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(webRequestMock);\r
+\r
+ whenGetRequestUrlThenReturnUrl();\r
+ HttpServletResponse servletResponseMock = mock(HttpServletResponse.class);\r
+ when(webRequestMock.getNativeResponse(HttpServletResponse.class)).thenReturn(servletResponseMock);\r
+ when(servletResponseMock.getWriter()).thenThrow(new IOException("Error"));\r
\r
- ResponseEntity<List<String>> response = delegateUnderTest.getServiceNamesUsingGET();\r
+ InputService service = new InputService();\r
+ service.setVersion("1.0");\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ Exception exception = assertThrows(HeaderException.class, () -> {\r
+ delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+ });\r
+\r
+ assertThat(exception.getMessage())\r
+ .isEqualTo("Unable to set header Location in put response for service " + SERVICE_NAME + ". Cause: Error");\r
+\r
+ ResponseEntity<List<Service>> response = delegateUnderTest.getServices();\r
+ assertThat(response.getBody()).isEmpty();\r
}\r
\r
@Test\r
- void getServices_shouldProvideArrayOfServices() throws Exception {\r
- ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl();\r
+ void getServices_shouldProvideArrayOfAddedServiceNames() throws Exception {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(webRequestMock);\r
+\r
+ InputService service1 = new InputService();\r
+ service1.setDescription("description 1");\r
+ service1.setVersion(SERVICE_VERSION);\r
+ service1.setDisplayName("Display Name 1");\r
+\r
+ InputService service2 = new InputService();\r
+ service2.setDescription("description 2");\r
+ service2.setVersion(SERVICE_VERSION);\r
+ service2.setDisplayName("Display Name 2");\r
+\r
+ whenPrintResponseShouldWork();\r
+\r
+ String serviceName1 = "Service Name 1";\r
+ delegateUnderTest.putIndividualService(serviceName1, service1);\r
+ String serviceName2 = "Service Name 2";\r
+ delegateUnderTest.putIndividualService(serviceName2, service2);\r
+\r
+ ResponseEntity<List<Service>> response = delegateUnderTest.getServices();\r
+\r
+ assertThat(response.getStatusCode()).isEqualTo(OK);\r
+ List<Service> services = response.getBody();\r
+ assertThat(services).hasSize(2);\r
+ List<String> expectedServiceNames = Arrays.asList(serviceName1, serviceName2);\r
+ assertThat(expectedServiceNames).contains(services.get(0).getName()) //\r
+ .contains(services.get(1).getName());\r
+ }\r
\r
- ResponseEntity<List<String>> response = delegateUnderTest.getServiceNamesUsingGET();\r
+ @Test\r
+ void deleteService_shouldBeOk() {\r
+ ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(webRequestMock);\r
+\r
+ InputService service = new InputService();\r
+ service.setDescription(SERVICE_DESCRIPTION);\r
+ service.setVersion(SERVICE_VERSION);\r
+ service.setDisplayName(SERVICE_DISPLAY_NAME);\r
+\r
+ whenPrintResponseShouldWork();\r
+\r
+ delegateUnderTest.putIndividualService(SERVICE_NAME, service);\r
+\r
+ ResponseEntity<List<Service>> servicesResponse = delegateUnderTest.getServices();\r
+\r
+ assertThat(servicesResponse.getBody()).hasSize(1);\r
+\r
+ ResponseEntity<Void> deleteResponse = delegateUnderTest.deleteIndividualService(SERVICE_NAME);\r
+\r
+ assertThat(deleteResponse.getStatusCode()).isEqualTo(NO_CONTENT);\r
+\r
+ servicesResponse = delegateUnderTest.getServices();\r
+\r
+ assertThat(servicesResponse.getBody()).isEmpty();\r
+ }\r
+\r
+ private void whenGetRequestUrlThenReturnUrl() {\r
+ whenGetRequestUrlThenReturnUrl("URL");\r
+ }\r
+\r
+ private void whenGetRequestUrlThenReturnUrl(String url) {\r
+ HttpServletRequest servletRequestMock = mock(HttpServletRequest.class);\r
+ when(webRequestMock.getNativeRequest(HttpServletRequest.class)).thenReturn(servletRequestMock);\r
+ when(servletRequestMock.getRequestURL()).thenReturn(new StringBuffer(url));\r
+ }\r
+\r
+ private HttpServletResponse whenPrintResponseShouldWork() {\r
+ return whenPrintResponseShouldWork("URL");\r
+ }\r
+\r
+ private HttpServletResponse whenPrintResponseShouldWork(String url) {\r
+ whenGetRequestUrlThenReturnUrl(url);\r
+ HttpServletResponse servletResponseMock = mock(HttpServletResponse.class);\r
+ when(webRequestMock.getNativeResponse(HttpServletResponse.class)).thenReturn(servletResponseMock);\r
+ PrintWriter printWriterMock = mock(PrintWriter.class);\r
+ try {\r
+ when(servletResponseMock.getWriter()).thenReturn(printWriterMock);\r
+ } catch (IOException e) {\r
+ // Nothing\r
+ }\r
+ return servletResponseMock;\r
+ }\r
\r
- assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);\r
- assertThat(response.getBody()).isEqualTo(Arrays.asList("a", "b"));\r
+ private String getTodaysDate() {\r
+ long millis = System.currentTimeMillis();\r
+ Date date = new Date(millis);\r
+ return date.toString();\r
}\r
}\r