From 0e4602aac66b13c33bcb0f2f930c83c3f217316f Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 9 Aug 2022 17:51:50 +0200 Subject: [PATCH] add updatePackage (description) using JsonNullableModule and HTTP-to-DB test with RestAssured --- build.gradle | 14 +- .../hsadminng/HsadminNgApplication.java | 1 - .../config/JsonObjectMapperConfiguration.java | 18 ++ .../hs/hspackage/PackageController.java | 26 +++ .../hsadminng/hs/hspackage/PackageEntity.java | 8 +- .../hs/hspackage/PackageRepository.java | 4 + .../rbac/rbacuser/RbacUserController.java | 5 - src/main/resources/api-definition.yaml | 48 ++++- src/main/resources/api-mappings.yaml | 3 + .../2022-07-29-070-hs-package-rbac.sql | 3 +- .../2022-07-29-070-hs-package-test-data.sql | 4 +- .../changelog/2022-07-29-070-hs-package.sql | 5 +- .../PackageControllerAcceptanceTest.java | 192 ++++++++++++++++++ .../hspackage/PackageControllerRestTest.java | 162 +++++++++++---- .../hsadminng/hs/hspackage/TestPackage.java | 2 +- 15 files changed, 437 insertions(+), 58 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java diff --git a/build.gradle b/build.gradle index 82667b4f..194e415d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'org.springframework.boot' version '2.7.2' - id 'io.openapiprocessor.openapi-processor' version '2021.3' + id 'io.openapiprocessor.openapi-processor' version '2022.2' 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" @@ -46,7 +46,7 @@ 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.openapitools:jackson-databind-nullable:0.2.3' implementation 'org.modelmapper:modelmapper:3.1.0' compileOnly 'org.projectlombok:lombok' @@ -62,6 +62,7 @@ dependencies { testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.testcontainers:postgresql' testImplementation 'com.tngtech.archunit:archunit-junit5:1.0.0-rc1' + testImplementation 'io.rest-assured:spring-mock-mvc' } dependencyManagement { @@ -80,11 +81,12 @@ tasks.named('test') { openapiProcessor { spring { - processor 'io.openapiprocessor:openapi-processor-spring:2021.4' + processor 'io.openapiprocessor:openapi-processor-spring:2022.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 + openApiNullable true } } sourceSets.main.java.srcDir 'build/generated/sources/openapi' @@ -92,12 +94,12 @@ compileJava.dependsOn('processSpring') spotless { java { - removeUnusedImports() + // removeUnusedImports() TODO: reactivate once it can deal with multi-line-strings + indentWithSpaces(4) endWithNewline() toggleOffOn() - // target 'src/main/java**/*.java', 'src/test/java**/*.java' // not generated - target project.fileTree(project.rootDir) { + target fileTree(rootDir) { include '**/*.java' exclude '**/generated/**/*.java' } diff --git a/src/main/java/net/hostsharing/hsadminng/HsadminNgApplication.java b/src/main/java/net/hostsharing/hsadminng/HsadminNgApplication.java index 20764d4e..af29526b 100644 --- a/src/main/java/net/hostsharing/hsadminng/HsadminNgApplication.java +++ b/src/main/java/net/hostsharing/hsadminng/HsadminNgApplication.java @@ -9,5 +9,4 @@ public class HsadminNgApplication { public static void main(String[] args) { SpringApplication.run(HsadminNgApplication.class, args); } - } diff --git a/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java b/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java new file mode 100644 index 00000000..dcd6af6e --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java @@ -0,0 +1,18 @@ +package net.hostsharing.hsadminng.config; + +import org.openapitools.jackson.nullable.JsonNullableModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +@Configuration +public class JsonObjectMapperConfiguration { + + @Bean + @Primary + public Jackson2ObjectMapperBuilder customObjectMapper() { + return new Jackson2ObjectMapperBuilder() + .modules(new JsonNullableModule()); + } +} 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 eb188b21..527923a2 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java @@ -3,13 +3,16 @@ 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 net.hostsharing.hsadminng.generated.api.v1.model.PackageUpdateResource; import org.springframework.beans.factory.annotation.Autowired; 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 @@ -36,4 +39,27 @@ public class PackageController implements PackagesApi { return ResponseEntity.ok(mapList(result, PackageResource.class)); } + @Override + @Transactional + public ResponseEntity updatePackage( + final String currentUser, + final String assumedRoles, + final UUID packageUuid, + final PackageUpdateResource body) { + + context.setCurrentUser(currentUser); + if (assumedRoles != null && !assumedRoles.isBlank()) { + context.assumeRoles(assumedRoles); + } + final var current = packageRepository.findByUuid(packageUuid); + if (body.getDescription() != null) { + body.getDescription().ifPresent(current::setDescription); + } else { + body.toString(); + } + final var saved = packageRepository.save(current); + final var mapped = map(saved, PackageResource.class); + return ResponseEntity.ok(mapped); + } + } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageEntity.java index 18117473..ff87a851 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.hspackage; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import net.hostsharing.hsadminng.hs.hscustomer.CustomerEntity; import javax.persistence.*; @@ -11,15 +12,18 @@ import java.util.UUID; @Entity @Table(name = "package_rv") @Getter +@Setter @NoArgsConstructor @AllArgsConstructor public class PackageEntity { private @Id UUID uuid; - private String name; - @ManyToOne(optional = false) @JoinColumn(name = "customeruuid") private CustomerEntity customer; + + private String name; + + private String description; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepository.java index bafe0882..5059412b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepository.java @@ -10,4 +10,8 @@ public interface PackageRepository extends Repository { @Query("SELECT p FROM PackageEntity p WHERE :name is null or p.name like concat(:name, '%')") List findAllByOptionalNameLike(final String name); + + PackageEntity findByUuid(UUID packageUuid); + + PackageEntity save(PackageEntity current); } 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 46e02a86..b05ef93f 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java @@ -1,10 +1,5 @@ package net.hostsharing.hsadminng.rbac.rbacuser; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.ArraySchema; -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; diff --git a/src/main/resources/api-definition.yaml b/src/main/resources/api-definition.yaml index 695c090c..09c2ffe8 100644 --- a/src/main/resources/api-definition.yaml +++ b/src/main/resources/api-definition.yaml @@ -182,6 +182,40 @@ paths: type: array items: $ref: '#/components/schemas/Package' + "401": + $ref: '#/components/responses/Unauthorized' + "403": + $ref: '#/components/responses/Forbidden' + /api/packages/{packageUUID}: + patch: + tags: + - packages + operationId: updatePackage + parameters: + - $ref: '#/components/parameters/currentUser' + - $ref: '#/components/parameters/assumedRoles' + - name: packageUUID + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + 'application/json': + schema: + $ref: '#/components/schemas/PackageUpdate' + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: '#/components/schemas/Package' + "401": + $ref: '#/components/responses/Unauthorized' + "403": + $ref: '#/components/responses/Forbidden' components: @@ -290,10 +324,20 @@ components: uuid: type: string format: uuid - name: - type: string customer: $ref: '#/components/schemas/Customer' + name: + type: string + description: + type: string + maxLength: 80 + PackageUpdate: + type: object + properties: + description: + type: string + maxLength: 80 + nullable: true Error: type: object properties: diff --git a/src/main/resources/api-mappings.yaml b/src/main/resources/api-mappings.yaml index 0172154a..260edebf 100644 --- a/src/main/resources/api-mappings.yaml +++ b/src/main/resources/api-mappings.yaml @@ -12,3 +12,6 @@ map: - type: array => java.util.List - type: string:uuid => java.util.UUID + paths: + /api/packages/{packageUUID}: + null: org.openapitools.jackson.nullable.JsonNullable diff --git a/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql b/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql index 6e2c0eeb..689f6c19 100644 --- a/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql +++ b/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql @@ -188,6 +188,7 @@ drop view if exists package_rv; create or replace view package_rv as select target.* from package as target - where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'package', currentSubjectIds())); + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'package', currentSubjectIds())) + order by target.name; grant all privileges on package_rv to restricted; --// diff --git a/src/main/resources/db/changelog/2022-07-29-070-hs-package-test-data.sql b/src/main/resources/db/changelog/2022-07-29-070-hs-package-test-data.sql index 36877bd9..f2374930 100644 --- a/src/main/resources/db/changelog/2022-07-29-070-hs-package-test-data.sql +++ b/src/main/resources/db/changelog/2022-07-29-070-hs-package-test-data.sql @@ -37,8 +37,8 @@ create or replace procedure createPackageTestData( set local hsadminng.currentTask to currentTask; insert - into package (name, customerUuid) - values (pacName, cust.uuid) + into package (customerUuid, name, description) + values (cust.uuid, pacName, 'Here can add your own description of package ' || pacName || '.') returning * into pac; call grantRoleToUser( diff --git a/src/main/resources/db/changelog/2022-07-29-070-hs-package.sql b/src/main/resources/db/changelog/2022-07-29-070-hs-package.sql index 95b925d5..5f7ba39e 100644 --- a/src/main/resources/db/changelog/2022-07-29-070-hs-package.sql +++ b/src/main/resources/db/changelog/2022-07-29-070-hs-package.sql @@ -7,7 +7,8 @@ create table if not exists package ( uuid uuid unique references RbacObject (uuid), - name character varying(5), - customerUuid uuid references customer (uuid) + customerUuid uuid references customer (uuid), + name varchar(5), + description varchar(80) ); --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java new file mode 100644 index 00000000..7b45e791 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java @@ -0,0 +1,192 @@ +package net.hostsharing.hsadminng.hs.hspackage; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import net.hostsharing.hsadminng.HsadminNgApplication; +import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; + +import javax.transaction.Transactional; +import java.util.UUID; + +import static java.lang.String.format; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +@SpringBootTest( + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = HsadminNgApplication.class +) +// classes = { PackageController.class, JsonObjectMapperConfiguration.class }, +@Import(JsonObjectMapperConfiguration.class) +@Transactional +class PackageControllerAcceptanceTest { + + @LocalServerPort + private Integer port; + + @Nested + class ListPackages { + + @Test + void withoutNameParameter() throws Exception { + // @formatter:off + RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .when() + .get("http://localhost:" + port + "/api/packages") + .then().assertThat() + .statusCode(200) + .contentType("application/json") + .body("[0].name", is("aaa00")) + .body("[0].customer.reference", is(10000)) + .body("[1].name", is("aaa01")) + .body("[1].customer.reference", is(10000)) + .body("[2].name", is("aaa02")) + .body("[2].customer.reference", is(10000)); + // @formatter:on + } + + @Test + void withNameParameter() throws Exception { + // @formatter:off + RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .when() + .get("http://localhost:" + port + "/api/packages?name=aaa01") + .then().assertThat() + .statusCode(200) + .contentType("application/json") + .body("[0].name", is("aaa01")) + .body("[0].customer.reference", is(10000)); + // @formatter:on + } + } + + @Nested + class UpdatePackage { + + @Test + void withDescriptionUpdatesDescription() throws Exception { + + assumeThat(getDescriptionOfPackage("aaa00")) + .isEqualTo("Here can add your own description of package aaa00."); + + final var randomDescription = RandomStringUtils.randomAlphanumeric(80); + + // @formatter:off + RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .contentType(ContentType.JSON) + .body(format(""" + { + "description": "%s" + } + """, randomDescription)) + .when() + .patch("http://localhost:" + port + "/api/packages/" + getUuidOfPackage("aaa00")) + .then() + .assertThat() + .statusCode(200) + .contentType("application/json") + .body("name", is("aaa00")) + .body("description", is(randomDescription)); + // @formatter:on + + } + + @Test + void withNullDescriptionUpdatesDescriptionToNull() throws Exception { + + assumeThat(getDescriptionOfPackage("aaa01")) + .isEqualTo("Here can add your own description of package aaa01."); + + // @formatter:off + RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .contentType(ContentType.JSON) + .body(""" + { + "description": null + } + """) + .when() + .patch("http://localhost:" + port + "/api/packages/" + getUuidOfPackage("aaa01")) + .then() + .assertThat() + .statusCode(200) + .contentType("application/json") + .body("name", is("aaa01")) + .body("description", equalTo(null)); + // @formatter:on + } + + @Test + void withoutDescriptionDoesNothing() throws Exception { + + assumeThat(getDescriptionOfPackage("aaa02")) + .isEqualTo("Here can add your own description of package aaa02."); + + // @formatter:off + RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .contentType(ContentType.JSON) + .body("{}") + .when() + .patch("http://localhost:" + port + "/api/packages/" + getUuidOfPackage("aaa02")) + .then().assertThat() + .statusCode(200) + .contentType("application/json") + .body("name", is("aaa02")) + .body("description", is("Here can add your own description of package aaa02.")); // unchanged + // @formatter:on + } + } + + UUID getUuidOfPackage(final String packageName) { + // @formatter:off + return UUID.fromString(RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .when() + .get("http://localhost:" + port + "/api/packages?name=" + packageName) + .then() + .statusCode(200) + .contentType("application/json") + .extract().path("[0].uuid")); + // @formatter:om + } + + String getDescriptionOfPackage(final String packageName) { + // @formatter:off + return RestAssured + .given() + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#aaa.admin") + .when() + .get("http://localhost:" + port + "/api/packages?name=" + packageName) + .then() + .statusCode(200) + .contentType("application/json") + .extract().path("[0].description"); + // @formatter:om + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java index 92ebd1ef..e9b1008b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java @@ -1,11 +1,14 @@ package net.hostsharing.hsadminng.hs.hspackage; +import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; import net.hostsharing.hsadminng.context.Context; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -13,12 +16,15 @@ import java.util.List; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(PackageController.class) +@ContextConfiguration(classes = { PackageController.class, JsonObjectMapperConfiguration.class }) class PackageControllerRestTest { @Autowired @@ -28,51 +34,135 @@ class PackageControllerRestTest { @MockBean PackageRepository packageRepositoryMock; - @Test - void listPackagesWithoutNameParameter() throws Exception { + // @Autowired + // ObjectMapper objectMapper; + // + // @Autowired + // private Jackson2ObjectMapperBuilder jacksonObjectMapper; + // + // @Autowired + // private PackageController restController; - // given - final var givenPacs = List.of(TestPackage.xxx00, TestPackage.xxx01, TestPackage.xxx02); - when(packageRepositoryMock.findAllByOptionalNameLike(null)).thenReturn(givenPacs); + // @Before + // public void init(){ + // + // objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + // objectMapper.registerModule(new JsonNullableModule()); + // } - // when - final var pacs = mockMvc.perform(MockMvcRequestBuilders - .get("/api/packages") - .header("current-user", "mike@hostsharing.net") - .header("assumed-roles", "customer#xxx.admin") - .accept(MediaType.APPLICATION_JSON)) + @Nested + class ListPackages { - // then - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(3))) - .andExpect(jsonPath("$[0].name", is("xxx00"))) - .andExpect(jsonPath("$[1].uuid", is(TestPackage.xxx01.getUuid().toString()))) - .andExpect(jsonPath("$[2].customer.prefix", is("xxx"))); + @Test + void withoutNameParameter() throws Exception { - verify(contextMock).setCurrentUser("mike@hostsharing.net"); - verify(contextMock).assumeRoles("customer#xxx.admin"); + // given + final var givenPacs = List.of(TestPackage.xxx00, TestPackage.xxx01, TestPackage.xxx02); + when(packageRepositoryMock.findAllByOptionalNameLike(null)).thenReturn(givenPacs); + + // when + final var pacs = mockMvc.perform(MockMvcRequestBuilders + .get("/api/packages") + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#xxx.admin") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$[0].name", is("xxx00"))) + .andExpect(jsonPath("$[1].uuid", is(TestPackage.xxx01.getUuid().toString()))) + .andExpect(jsonPath("$[2].customer.prefix", is("xxx"))); + + verify(contextMock).setCurrentUser("mike@hostsharing.net"); + verify(contextMock).assumeRoles("customer#xxx.admin"); + } + + @Test + void withNameParameter() throws Exception { + + // given + final var givenPacs = List.of(TestPackage.xxx01); + when(packageRepositoryMock.findAllByOptionalNameLike("xxx01")).thenReturn(givenPacs); + + // when + final var pacs = mockMvc.perform(MockMvcRequestBuilders + .get("/api/packages?name=xxx01") + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#xxx.admin") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].name", is("xxx01"))); + + verify(contextMock).setCurrentUser("mike@hostsharing.net"); + verify(contextMock).assumeRoles("customer#xxx.admin"); + } } - @Test - void listPackagesWithNameParameter() throws Exception { + @Nested + class updatePackage { - // given - final var givenPacs = List.of(TestPackage.xxx01); - when(packageRepositoryMock.findAllByOptionalNameLike("xxx01")).thenReturn(givenPacs); + @Test + void withDescriptionUpdatesDescription() throws Exception { - // when - final var pacs = mockMvc.perform(MockMvcRequestBuilders - .get("/api/packages?name=xxx01") - .header("current-user", "mike@hostsharing.net") - .header("assumed-roles", "customer#xxx.admin") - .accept(MediaType.APPLICATION_JSON)) + // given + final var givenPac = TestPackage.xxx01; + when(packageRepositoryMock.findByUuid(givenPac.getUuid())).thenReturn(givenPac); + when(packageRepositoryMock.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - // then - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].name", is("xxx01"))); + // when + final var pacs = mockMvc.perform(MockMvcRequestBuilders + .patch("/api/packages/" + givenPac.getUuid().toString()) + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#xxx.admin") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "description": "some description" + } + """) + .accept(MediaType.APPLICATION_JSON)) - verify(contextMock).setCurrentUser("mike@hostsharing.net"); - verify(contextMock).assumeRoles("customer#xxx.admin"); + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("description", is("some description"))); + + verify(contextMock).setCurrentUser("mike@hostsharing.net"); + verify(contextMock).assumeRoles("customer#xxx.admin"); + verify(packageRepositoryMock).save(argThat(entity -> + entity.getDescription().equals("some description") && + entity.getUuid().equals(givenPac.getUuid()))); + } + + @Test + void withoutDescriptionDoesNothing() throws Exception { + + // given + final var givenPac = TestPackage.xxx01; + when(packageRepositoryMock.findByUuid(givenPac.getUuid())).thenReturn(givenPac); + when(packageRepositoryMock.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + + // when + final var pacs = mockMvc.perform(MockMvcRequestBuilders + .patch("/api/packages/" + givenPac.getUuid().toString()) + .header("current-user", "mike@hostsharing.net") + .header("assumed-roles", "customer#xxx.admin") + .contentType(MediaType.APPLICATION_JSON) + .content("{}") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("description", is(givenPac.getDescription()))); + + verify(contextMock).setCurrentUser("mike@hostsharing.net"); + verify(contextMock).assumeRoles("customer#xxx.admin"); + verify(packageRepositoryMock).save(argThat(entity -> + entity.getDescription() == givenPac.getDescription() && + entity.getUuid().equals(givenPac.getUuid()))); + } } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/TestPackage.java b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/TestPackage.java index f2fde56f..0444a836 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/TestPackage.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/TestPackage.java @@ -12,6 +12,6 @@ public class TestPackage { public static final PackageEntity xxx02 = hsPackage(TestCustomer.xxx, "xxx02"); public static PackageEntity hsPackage(final CustomerEntity customer, final String name) { - return new PackageEntity(randomUUID(), name, customer); + return new PackageEntity(randomUUID(), customer, name, "initial description of package " + name); } }