From eeab68d63ac3169f78cbbb4b99cbc91cf8727787 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 8 Aug 2022 16:54:35 +0200 Subject: [PATCH] API-first with openapiprocessor and modelmapper --- build.gradle | 21 ++ doc/adr-concept.md | 15 + doc/adr/0000-00-00.adr-template.md | 39 +++ doc/adr/2022-08-08.object-mapping.md | 108 +++++++ .../net/hostsharing/hsadminng/Mapper.java | 27 ++ .../hs/hscustomer/CustomerController.java | 44 ++- .../hs/hspackage/PackageController.java | 22 +- .../rbac/rbacrole/RbacRoleController.java | 22 +- .../rbac/rbacuser/RbacUserController.java | 36 +-- src/main/resources/api-definition.yaml | 306 ++++++++++++++++++ src/main/resources/api-mappings.yaml | 14 + .../rbacrole/RbacRoleControllerRestTest.java | 2 +- .../RbacRoleRepositoryIntegrationTest.java | 4 +- .../rbacuser/RbacUserControllerRestTest.java | 4 +- 14 files changed, 602 insertions(+), 62 deletions(-) create mode 100644 doc/adr-concept.md create mode 100644 doc/adr/0000-00-00.adr-template.md create mode 100644 doc/adr/2022-08-08.object-mapping.md create mode 100644 src/main/java/net/hostsharing/hsadminng/Mapper.java create mode 100644 src/main/resources/api-definition.yaml create mode 100644 src/main/resources/api-mappings.yaml diff --git a/build.gradle b/build.gradle index f5e6a0a3..82667b4f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'org.springframework.boot' version '2.7.2' + id 'io.openapiprocessor.openapi-processor' version '2021.3' id 'io.spring.dependency-management' version '1.0.12.RELEASE' id 'com.github.jk1.dependency-license-report' version '2.1' id "org.owasp.dependencycheck" version "7.1.1" @@ -45,6 +46,8 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-ui:1.6.9' implementation 'org.liquibase:liquibase-core' implementation 'com.vladmihalcea:hibernate-types-55:2.17.1' + implementation 'org.openapitools:jackson-databind-nullable:0.2.3'// https://mvnrepository.com/artifact/org.modelmapper/modelmapper + implementation 'org.modelmapper:modelmapper:3.1.0' compileOnly 'org.projectlombok:lombok' @@ -75,11 +78,29 @@ tasks.named('test') { useJUnitPlatform() } +openapiProcessor { + spring { + processor 'io.openapiprocessor:openapi-processor-spring:2021.4' + apiPath "$projectDir/src/main/resources/api-definition.yaml" + targetDir "$projectDir/build/generated/sources/openapi" + mapping "$projectDir/src/main/resources/api-mappings.yaml" + showWarnings true + } +} +sourceSets.main.java.srcDir 'build/generated/sources/openapi' +compileJava.dependsOn('processSpring') + spotless { java { removeUnusedImports() endWithNewline() toggleOffOn() + + // target 'src/main/java**/*.java', 'src/test/java**/*.java' // not generated + target project.fileTree(project.rootDir) { + include '**/*.java' + exclude '**/generated/**/*.java' + } } } project.tasks.check.dependsOn(spotlessCheck) diff --git a/doc/adr-concept.md b/doc/adr-concept.md new file mode 100644 index 00000000..fa5f7972 --- /dev/null +++ b/doc/adr-concept.md @@ -0,0 +1,15 @@ +## ADR-Concept + +This project uses ADRs (Architecture Decision Records), see also https://adr.github.io/. + +There is a template available under [0000-00-00.adr-tempate.md](./0000-00-00.adr-tempate.md). + +It's suggested to write an ADR if any of these is true: + +- an architectural decision is hard to change, +- there is a dispute about an architectural decision, +- some unusual architectural decision was made (e.g. unusual library), +- some deeper investigation was necessary before the decision. + +ADRs should not be written for minor decisions with limited impact. + diff --git a/doc/adr/0000-00-00.adr-template.md b/doc/adr/0000-00-00.adr-template.md new file mode 100644 index 00000000..a78dfef8 --- /dev/null +++ b/doc/adr/0000-00-00.adr-template.md @@ -0,0 +1,39 @@ +# TITLE + +**Status:** +- [ ] proposed by (Proposer) +- [ ] accepted by (Participants) +- [ ] rejected by (Participants) +- [ ] superseded by (superseding ADR) + +## Context and Problem Statement + +A short description, why and under which circumstances this decision had to be made. + +### Technical Background + +Some details about the technical challenge. + +## Considered Options + +* OPTION-1 +* OPTION-... + +### OPTION-n + +A short overview about the option. + +#### Advantages + +A list of advantages. + + +#### Disadvantages + +A list of disadvantages. + + + +## Decision Outcome + +Which option was chose and why. diff --git a/doc/adr/2022-08-08.object-mapping.md b/doc/adr/2022-08-08.object-mapping.md new file mode 100644 index 00000000..58a317f9 --- /dev/null +++ b/doc/adr/2022-08-08.object-mapping.md @@ -0,0 +1,108 @@ +# Object Mapping + +**Status:** +- [x] proposed by Michael Hönnig +- [ ] accepted by (Participants) +- [ ] rejected by (Participants) +- [ ] superseded by (superseding ADR) + +## Context and Problem Statement + +Since we are using the *API first*-approach, +thus generating Java interfaces and model classes from an OpenAPI specification, +we cannot use the JPA-entities anymore at the API level, +not even if the data fields are 100% identical. + +Therefore, we need some kind of mapping strategy. + + +### Technical Background + +Java does not support duck-typing and therefore, objects of different classes have to be converted to each other, even if all data fields are identical. + +In our case, the database query is usually the slowest part of handling a request. +Therefore, for the mapper, ease of use is more important than performance, +at least as long as the mapping part does not take more than 10% of the total request. + + +## Considered Options + +* specific programmatic conversion +* using the *MapStruct* library +* using the *ModelMapper* library +* Dozer, last update from 2014 + vulnerabilities => skipped +* Orika, last update from 2019 + vulnerabilities => skipped +* JMapper + +### specific programmatic conversion + +In this solution, we would write own code to convert the objects. +This usually means 3 converters for each entity/resource pair: + +- entity -> resource +- resource -> entity +- list of entities -> list of resources + +#### Advantages + +Very flexible and fast. + +#### Disadvantages + +Huge amounts of bloat code. + + +### using the *MapStruct* library + +See https://mapstruct.org/. + +#### Advantages + +- Most popular mapping library in the Java-world. +- Actively maintained, last release 1.5.2 from Jun 18, 2022. +- very fast (see [^1]) + + +#### Disadvantages + +- Needs interface declarations with annotations. +- Looks like it causes still too much bloat code for our purposes. + + +### using the *ModelMapper* library + +See http://modelmapper.org/. + +#### Advantages + +- 1:1 mappings just need a simple method call without any bloat-code. +- Actively maintained, last release 3.1.0 from Mar 08, 2022. + +#### Disadvantages + +- could not find any, will give it a try + +### using the *JMapper* library + +See https://jmapper-framework.github.io/jmapper-core/. + +#### Advantages + +- Supports annotation-based and programmatic mapping exceptions. +- Actively maintained, last release 1.6.3 from May 27, 2022. +- very fast (see [^1]) + + +#### Disadvantages + +- needs a separate mapper instance for each mapping pair +- cannot map collections (needs `stream().map(...).collect(toList())` or similar) + + +## Decision Outcome + +We chose the option **"using the *ModelMapper* library"** because it has an acceptable performance without any bloat code. + +If it turns out to be too slow after all, "using the *JMapper* library" seems to be a good alternative. + +[^1]: https://www.baeldung.com/java-performance-mapping-frameworks diff --git a/src/main/java/net/hostsharing/hsadminng/Mapper.java b/src/main/java/net/hostsharing/hsadminng/Mapper.java new file mode 100644 index 00000000..93f7048b --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/Mapper.java @@ -0,0 +1,27 @@ +package net.hostsharing.hsadminng; + +import org.modelmapper.ModelMapper; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * A nicer API for ModelMapper. + * + * MOst + */ +public class Mapper { + private final static ModelMapper modelMapper = new ModelMapper(); + + + public static List mapList(final List source, final Class targetClass) { + return source + .stream() + .map(element -> modelMapper.map(element, targetClass)) + .collect(Collectors.toList()); + } + + public static T map(final S source, final Class targetClass) { + return modelMapper.map(source, targetClass); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java index 6088f82d..e257639a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java @@ -1,16 +1,22 @@ package net.hostsharing.hsadminng.hs.hscustomer; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.generated.api.v1.api.CustomersApi; +import net.hostsharing.hsadminng.generated.api.v1.model.CustomerResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; import javax.transaction.Transactional; import java.util.List; import java.util.UUID; +import static net.hostsharing.hsadminng.Mapper.map; +import static net.hostsharing.hsadminng.Mapper.mapList; + @RestController -public class CustomerController { +public class CustomerController implements CustomersApi { @Autowired private Context context; @@ -18,34 +24,40 @@ public class CustomerController { @Autowired private CustomerRepository customerRepository; - @GetMapping(value = "/api/customers") + @Override @Transactional - public List listCustomers( - @RequestHeader(value = "current-user") String userName, - @RequestHeader(value = "assumed-roles", required = false) String assumedRoles, - @RequestParam(required = false) String prefix + public ResponseEntity> listCustomers( + String userName, + String assumedRoles, + String prefix ) { context.setCurrentUser(userName); if (assumedRoles != null && !assumedRoles.isBlank()) { context.assumeRoles(assumedRoles); } - return customerRepository.findCustomerByOptionalPrefixLike(prefix); + return ResponseEntity.ok( + mapList( + customerRepository.findCustomerByOptionalPrefixLike(prefix), + CustomerResource.class)); } - @PostMapping(value = "/api/customers") + @Override @Transactional - public CustomerEntity addCustomer( - @RequestHeader(value = "current-user") String userName, - @RequestHeader(value = "assumed-roles", required = false) String assumedRoles, - @RequestBody CustomerEntity customer - ) { - context.setCurrentUser(userName); + public ResponseEntity addCustomer( + final String currentUser, + final String assumedRoles, + final CustomerResource customer) { + context.setCurrentUser(currentUser); if (assumedRoles != null && !assumedRoles.isBlank()) { context.assumeRoles(assumedRoles); } if (customer.getUuid() == null) { customer.setUuid(UUID.randomUUID()); } - return customerRepository.save(customer); + return ResponseEntity.ok( + map( + customerRepository.save(map(customer, CustomerEntity.class)), + CustomerResource.class)); } + } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java index 9d29f53c..eb188b21 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java @@ -1,14 +1,19 @@ package net.hostsharing.hsadminng.hs.hspackage; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.generated.api.v1.api.PackagesApi; +import net.hostsharing.hsadminng.generated.api.v1.model.PackageResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; import javax.transaction.Transactional; import java.util.List; +import static net.hostsharing.hsadminng.Mapper.mapList; + @RestController -public class PackageController { +public class PackageController implements PackagesApi { @Autowired private Context context; @@ -16,18 +21,19 @@ public class PackageController { @Autowired private PackageRepository packageRepository; - @RequestMapping(value = "/api/packages", method = RequestMethod.GET) + @Override @Transactional - public List listPackages( - @RequestHeader(value = "current-user") String userName, - @RequestHeader(value = "assumed-roles", required = false) String assumedRoles, - @RequestParam(required = false) String name + public ResponseEntity> listPackages( + String userName, + String assumedRoles, + String name ) { context.setCurrentUser(userName); if (assumedRoles != null && !assumedRoles.isBlank()) { context.assumeRoles(assumedRoles); } - return packageRepository.findAllByOptionalNameLike(name); + final var result = packageRepository.findAllByOptionalNameLike(name); + return ResponseEntity.ok(mapList(result, PackageResource.class)); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java index 7a0c3864..df3dfcf7 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java @@ -1,16 +1,20 @@ package net.hostsharing.hsadminng.rbac.rbacrole; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.generated.api.v1.api.RbacrolesApi; +import net.hostsharing.hsadminng.generated.api.v1.model.RbacRoleResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; import javax.transaction.Transactional; +import java.util.List; + +import static net.hostsharing.hsadminng.Mapper.mapList; @RestController -public class RbacRoleController { +public class RbacRoleController implements RbacrolesApi { @Autowired private Context context; @@ -18,17 +22,13 @@ public class RbacRoleController { @Autowired private RbacRoleRepository rbacRoleRepository; - @GetMapping(value = "/api/rbacroles") + @Override @Transactional - public Iterable listCustomers( - @RequestHeader(value = "current-user") String userName, - @RequestHeader(value = "assumed-roles", required = false) String assumedRoles - ) { - context.setCurrentUser(userName); + public ResponseEntity> listRoles(final String currentUser, final String assumedRoles) { + context.setCurrentUser(currentUser); if (assumedRoles != null && !assumedRoles.isBlank()) { context.assumeRoles(assumedRoles); } - return rbacRoleRepository.findAll(); + return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class)); } - } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java index f938905e..46e02a86 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java @@ -6,14 +6,20 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.generated.api.v1.api.RbacusersApi; +import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserPermissionResource; +import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserResource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.transaction.Transactional; import java.util.List; +import static net.hostsharing.hsadminng.Mapper.mapList; + @RestController -public class RbacUserController { +public class RbacUserController implements RbacusersApi { @Autowired private Context context; @@ -21,18 +27,9 @@ public class RbacUserController { @Autowired private RbacUserRepository rbacUserRepository; - @GetMapping(value = "/api/rbacusers") - @Operation(description = "List accessible RBAC users with optional filter by name.", - responses = { - @ApiResponse(responseCode = "200", - content = @Content(array = @ArraySchema( - schema = @Schema(implementation = RbacUserEntity.class)))), - @ApiResponse(responseCode = "401", - description = "if the 'current-user' cannot be identified"), - @ApiResponse(responseCode = "403", - description = "if the 'current-user' is not allowed to assume any of the roles from 'assumed-roles'") }) + @Override @Transactional - public List listUsers( + public ResponseEntity> listUsers( @RequestHeader(name = "current-user") String currentUserName, @RequestHeader(name = "assumed-roles", required = false) String assumedRoles, @RequestParam(name="name", required = false) String userName @@ -41,19 +38,12 @@ public class RbacUserController { if (assumedRoles != null && !assumedRoles.isBlank()) { context.assumeRoles(assumedRoles); } - return rbacUserRepository.findByOptionalNameLike(userName); + return ResponseEntity.ok(mapList(rbacUserRepository.findByOptionalNameLike(userName), RbacUserResource.class)); } - @GetMapping(value = "/api/rbacuser/{userName}/permissions") - @Operation(description = "List all visible permissions granted to the given user; reduced ", responses = { - @ApiResponse(responseCode = "200", - content = @Content(array = @ArraySchema( schema = @Schema(implementation = RbacUserPermission.class)))), - @ApiResponse(responseCode = "401", - description = "if the 'current-user' cannot be identified"), - @ApiResponse(responseCode = "403", - description = "if the 'current-user' is not allowed to view permissions of the given user") }) + @Override @Transactional - public List listUserPermissions( + public ResponseEntity> listUserPermissions( @RequestHeader(name = "current-user") String currentUserName, @RequestHeader(name = "assumed-roles", required = false) String assumedRoles, @PathVariable(name= "userName") String userName @@ -62,6 +52,6 @@ public class RbacUserController { if (assumedRoles != null && !assumedRoles.isBlank()) { context.assumeRoles(assumedRoles); } - return rbacUserRepository.findPermissionsOfUser(userName); + return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUser(userName), RbacUserPermissionResource.class)); } } diff --git a/src/main/resources/api-definition.yaml b/src/main/resources/api-definition.yaml new file mode 100644 index 00000000..695c090c --- /dev/null +++ b/src/main/resources/api-definition.yaml @@ -0,0 +1,306 @@ +openapi: 3.0.1 +info: + title: Hostsharing hsadmin-ng API + version: v0 +servers: + - url: http://localhost:8080 + description: Local development default URL. + +paths: + + /api/customers: + get: + summary: Returns a list of (optionally filtered) customers. + description: Returns the list of (optionally filtered) customers which are visible to the current user or any of it's assumed roles. + tags: + - customers + operationId: listCustomers + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + - name: prefix + in: query + required: false + schema: + type: string + description: Customer-prefix to filter the results. + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/Customer' + "401": + description: Not Authorized + "403": + description: Forbidden + post: + summary: Adds a new customer. + tags: + - customers + operationId: addCustomer + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + requestBody: + content: + 'application/json': + schema: + $ref: '#/components/schemas/Customer' + required: true + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: '#/components/schemas/Customer' + + /api/rbac-users: + get: + tags: + - rbacusers + description: List accessible RBAC users with optional filter by name. + operationId: listUsers + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + - name: name + in: query + required: false + schema: + type: string + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/RbacUser' + "401": + description: if the 'current-user' cannot be identified + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/RbacUser' + "403": + description: if the 'current-user' is not allowed to assume any of the roles + from 'assumed-roles' + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/RbacUser' + + /api/rbac-users/{userName}/permissions: + get: + tags: + - rbacusers + description: 'List all visible permissions granted to the given user; reduced ' + operationId: listUserPermissions + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + - name: userName + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/RbacUserPermission' + + "401": + $ref: '#/components/responses/Unauthorized' + "403": + $ref: '#/components/responses/Forbidden' + + /api/rbac-roles: + get: + tags: + - rbacroles + operationId: listRoles + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/RbacRole' + + /api/ping: + get: + tags: + - test + operationId: ping + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: string + + /api/packages: + get: + tags: + - packages + operationId: listPackages + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + - name: name + in: query + required: false + schema: + type: string + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/Package' + +components: + + parameters: + currentUser: + name: current-user + in: header + required: true + schema: + type: string + description: Identifying name of the currently logged in user. + assumedRoles: + name: assumed-roles + in: header + required: false + schema: + type: string + description: Semicolon-separated list of roles to assume. The current user needs to have the right to assume these roles. + + responses: + NotFound: + description: The specified was not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: The current user is unknown or not authorized. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Forbidden: + description: The current user or none of the assumed or roles is granted access to the . + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + schemas: + Customer: + type: object + properties: + uuid: + type: string + format: uuid + prefix: + type: string + reference: + type: integer + format: int32 + adminUserName: + type: string + RbacUser: + type: object + properties: + uuid: + type: string + format: uuid + name: + type: string + RbacUserPermission: + type: object + properties: + objectUuid: + type: string + format: uuid + objectTable: + type: string + objectIdName: + type: string + roleName: + type: string + roleUuid: + type: string + format: uuid + permissionUuid: + type: string + format: uuid + op: + type: string + RbacRole: + type: object + properties: + uuid: + type: string + format: uuid + objectUuid: + type: string + format: uuid + objectTable: + type: string + objectIdName: + type: string + roleType: + type: string + enum: + - owner + - admin + - tenant + roleName: + type: string + Package: + type: object + properties: + uuid: + type: string + format: uuid + name: + type: string + customer: + $ref: '#/components/schemas/Customer' + Error: + type: object + properties: + code: + type: string + message: + type: string + required: + - code + - message diff --git a/src/main/resources/api-mappings.yaml b/src/main/resources/api-mappings.yaml new file mode 100644 index 00000000..0172154a --- /dev/null +++ b/src/main/resources/api-mappings.yaml @@ -0,0 +1,14 @@ +openapi-processor-mapping: v2 + +options: + package-name: net.hostsharing.hsadminng.generated.api.v1 + model-name-suffix: Resource + +map: + result: org.springframework.http.ResponseEntity + + + types: + - type: array => java.util.List + - type: string:uuid => java.util.UUID + diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerRestTest.java index f80599d3..6450b8e0 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerRestTest.java @@ -36,7 +36,7 @@ class RbacRoleControllerRestTest { // when mockMvc.perform(MockMvcRequestBuilders - .get("/api/rbacroles") + .get("/api/rbac-roles") .header("current-user", "mike@hostsharing.net") .accept(MediaType.APPLICATION_JSON)) diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java index ccb5a43e..3a6492f6 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java @@ -12,6 +12,8 @@ import org.springframework.orm.jpa.JpaSystemException; import javax.persistence.EntityManager; import javax.transaction.Transactional; +import java.util.List; + import static net.hostsharing.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @@ -160,7 +162,7 @@ class RbacRoleRepositoryIntegrationTest { assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";")); } - void exactlyTheseRbacRolesAreReturned(final Iterable actualResult, final String... expectedRoleNames) { + void exactlyTheseRbacRolesAreReturned(final List actualResult, final String... expectedRoleNames) { assertThat(actualResult) .extracting(RbacRoleEntity::getRoleName) .containsExactlyInAnyOrder(expectedRoleNames); diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java index 1d8aa0d5..10641567 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java @@ -39,7 +39,7 @@ class RbacUserControllerRestTest { // when mockMvc.perform(MockMvcRequestBuilders - .get("/api/rbacusers") + .get("/api/rbac-users") .header("current-user", "mike@hostsharing.net") .accept(MediaType.APPLICATION_JSON)) @@ -59,7 +59,7 @@ class RbacUserControllerRestTest { // when mockMvc.perform(MockMvcRequestBuilders - .get("/api/rbacusers") + .get("/api/rbac-users") .param("name", "admin@aaa") .header("current-user", "mike@hostsharing.net") .accept(MediaType.APPLICATION_JSON))