{
- "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
<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
--- /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, new Exception("Native Request or Response missing"));\r
+ }\r
+ } catch (IOException e) {\r
+ throw new HeaderException(LOCATION_HEADER, 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, Exception cause) {
+ super(String.format("Unable to set header %s in response. Cause: %s", header, 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();
+
+ HeaderException exception = new HeaderException("Header", 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 response. 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 putServiceWhenIoException_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()).isEqualTo("Unable to set header Location in response. 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