patchable BookingResources

This commit is contained in:
Michael Hoennig 2024-04-15 15:15:27 +02:00
parent 3e2775a8a0
commit 3a894ed72e
13 changed files with 196 additions and 107 deletions

View File

@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -122,7 +123,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
} }
}; };
@SuppressWarnings("unchecked")
final BiConsumer<HsBookingItemInsertResource, HsBookingItemEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsBookingItemInsertResource, HsBookingItemEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo())); entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
entity.putResources((Map<String, Object>) resource.getResources());
}; };
} }

View File

@ -32,7 +32,6 @@ import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
@ -122,6 +121,9 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
} }
public void putResources(Map<String, Object> entries) { public void putResources(Map<String, Object> entries) {
if ( resourcesWrapper == null ) {
resourcesWrapper = new PatchableMapWrapper(resources);
}
resourcesWrapper.assign(entries); resourcesWrapper.assign(entries);
} }

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.booking.item; package net.hostsharing.hsadminng.hs.booking.item;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.ArbitraryBookingResourcesJsonResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
@ -25,7 +24,7 @@ public class HsBookingItemEntityPatcher implements EntityPatcher<HsBookingItemPa
OptionalFromJson.of(resource.getCaption()) OptionalFromJson.of(resource.getCaption())
.ifPresent(entity::setCaption); .ifPresent(entity::setCaption);
Optional.ofNullable(resource.getResources()) Optional.ofNullable(resource.getResources())
.ifPresent(r -> entity.putResources(objectToMap(resource.getResources()))); .ifPresent(r -> entity.getResources().patch(objectToMap(resource.getResources())));
OptionalFromJson.of(resource.getValidFrom()) OptionalFromJson.of(resource.getValidFrom())
.ifPresent(entity::setValidFrom); .ifPresent(entity::setValidFrom);
OptionalFromJson.of(resource.getValidTo()) OptionalFromJson.of(resource.getValidTo())
@ -33,6 +32,9 @@ public class HsBookingItemEntityPatcher implements EntityPatcher<HsBookingItemPa
} }
static Map<String, Object> objectToMap(final Object obj) { static Map<String, Object> objectToMap(final Object obj) {
if (obj instanceof Map<?, ?>) {
return toKeyValueMap(obj);
}
return stream(obj.getClass().getDeclaredFields()) return stream(obj.getClass().getDeclaredFields())
.map(field -> { .map(field -> {
try { try {
@ -53,4 +55,9 @@ public class HsBookingItemEntityPatcher implements EntityPatcher<HsBookingItemPa
return map1; return map1;
}); });
} }
@SuppressWarnings("unchecked")
private static Map<String, Object> toKeyValueMap(final Object obj) {
return (Map<String, Object>) obj;
}
} }

View File

@ -18,13 +18,13 @@ public class PatchMap extends TreeMap<String, Object> {
stream(entries).forEach(r -> put(r.getKey(), r.getValue())); stream(entries).forEach(r -> put(r.getKey(), r.getValue()));
} }
// @SafeVarargs @SafeVarargs
// public static Map<String, Object> patchMap(final ImmutablePair<String, Object>... entries) { public static Map<String, Object> patchMap(final ImmutablePair<String, Object>... entries) {
// return new PatchMap(entries); return new PatchMap(entries);
// } }
//
// @NotNull @NotNull
// public static ImmutablePair<String, Object> entry(final String key, final Object value) { public static ImmutablePair<String, Object> entry(final String key, final Object value) {
// return new ImmutablePair<>(key, value); return new ImmutablePair<>(key, value);
// } }
} }

View File

@ -4,12 +4,9 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;
/** This class wraps another (usually persistent) map and /** This class wraps another (usually persistent) map and
@ -33,6 +30,16 @@ public class PatchableMapWrapper implements Map<String, Object> {
delegate.putAll(entries); delegate.putAll(entries);
} }
public void patch(final Map<String, Object> patch) {
patch.forEach((key, value) -> {
if (value == null) {
remove(key);
} else {
put(key, value);
}
});
}
public String toString() { public String toString() {
return "{ " return "{ "
+ ( + (

View File

@ -18,7 +18,7 @@ components:
type: string type: string
format: date format: date
resources: resources:
$ref: '#/components/schemas/ArbitraryBookingResourcesJson' $ref: '#/components/schemas/BookingResources'
required: required:
- uuid - uuid
- debitor - debitor
@ -41,7 +41,7 @@ components:
format: date format: date
nullable: true nullable: true
resources: resources:
$ref: '#/components/schemas/ArbitraryBookingResourcesJson' $ref: '#/components/schemas/BookingResources'
HsBookingItemInsert: HsBookingItemInsert:
type: object type: object
@ -64,7 +64,7 @@ components:
format: date format: date
nullable: true nullable: true
resources: resources:
$ref: '#/components/schemas/ArbitraryBookingResourcesJson' $ref: '#/components/schemas/BookingResources'
required: required:
- caption - caption
- debitorUuid - debitorUuid
@ -72,7 +72,53 @@ components:
- resources - resources
additionalProperties: false additionalProperties: false
ArbitraryBookingResourcesJson: BookingResourcesDoesNotWork:
type: object type: object
description: An object containing arbitrary JSON x-javaType: 'java.util.Map<String, Object>'
additionalProperties: true additionalProperties:
type: object
BookingResources:
anyOf:
- $ref: '#/components/schemas/ManagedServerBookingResources'
- $ref: '#/components/schemas/ManagedWebspaceBookingResources'
ManagedServerBookingResources:
type: object
properties:
caption:
type: string
minLength: 3
maxLength:
nullable: false
CPU:
type: integer
minimum: 1
maximum: 16
SSD:
type: integer
minimum: 16
maximum: 4096
HDD:
type: integer
minimum: 16
maximum: 4096
additionalProperties: false
ManagedWebspaceBookingResources:
type: object
properties:
disk:
type: integer
minimum: 1
maximum: 16
SSD:
type: integer
minimum: 16
maximum: 4096
HDD:
type: integer
minimum: 16
maximum: 4096
additionalProperties: false

View File

@ -4,8 +4,8 @@ get:
description: 'Fetch a single booking item its uuid, if visible for the current subject.' description: 'Fetch a single booking item its uuid, if visible for the current subject.'
operationId: getBookingItemByUuid operationId: getBookingItemByUuid
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: bookingItemUuid - name: bookingItemUuid
in: path in: path
required: true required: true
@ -19,12 +19,12 @@ get:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem' $ref: 'hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
$ref: './error-responses.yaml#/components/responses/Forbidden' $ref: 'error-responses.yaml#/components/responses/Forbidden'
patch: patch:
tags: tags:
@ -32,8 +32,8 @@ patch:
description: 'Updates a single booking item identified by its uuid, if permitted for the current subject.' description: 'Updates a single booking item identified by its uuid, if permitted for the current subject.'
operationId: patchBookingItem operationId: patchBookingItem
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: bookingItemUuid - name: bookingItemUuid
in: path in: path
required: true required: true
@ -44,18 +44,18 @@ patch:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-booking-item-schemas.yaml#/components/schemas/HsBookingItemPatch' $ref: 'hs-booking-item-schemas.yaml#/components/schemas/HsBookingItemPatch'
responses: responses:
"200": "200":
description: OK description: OK
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem' $ref: 'hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
$ref: './error-responses.yaml#/components/responses/Forbidden' $ref: 'error-responses.yaml#/components/responses/Forbidden'
delete: delete:
tags: tags:
@ -63,8 +63,8 @@ delete:
description: 'Delete a single booking item identified by its uuid, if permitted for the current subject.' description: 'Delete a single booking item identified by its uuid, if permitted for the current subject.'
operationId: deleteBookingIemByUuid operationId: deleteBookingIemByUuid
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: bookingItemUuid - name: bookingItemUuid
in: path in: path
required: true required: true
@ -76,8 +76,8 @@ delete:
"204": "204":
description: No Content description: No Content
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
$ref: './error-responses.yaml#/components/responses/Forbidden' $ref: 'error-responses.yaml#/components/responses/Forbidden'
"404": "404":
$ref: './error-responses.yaml#/components/responses/NotFound' $ref: 'error-responses.yaml#/components/responses/NotFound'

View File

@ -5,8 +5,8 @@ get:
- hs-booking-items - hs-booking-items
operationId: listBookingItemsByDebitorUuid operationId: listBookingItemsByDebitorUuid
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: debitorUuid - name: debitorUuid
in: query in: query
required: true required: true
@ -22,11 +22,11 @@ get:
schema: schema:
type: array type: array
items: items:
$ref: './hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem' $ref: 'hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
$ref: './error-responses.yaml#/components/responses/Forbidden' $ref: 'error-responses.yaml#/components/responses/Forbidden'
post: post:
summary: Adds a new booking item. summary: Adds a new booking item.
@ -34,25 +34,25 @@ post:
- hs-booking-items - hs-booking-items
operationId: addBookingItem operationId: addBookingItem
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
requestBody: requestBody:
description: A JSON object describing the new booking item. description: A JSON object describing the new booking item.
required: true required: true
content: content:
application/json: application/json:
schema: schema:
$ref: '/hs-booking-item-schemas.yaml#/components/schemas/HsBookingItemInsert' $ref: 'hs-booking-item-schemas.yaml#/components/schemas/HsBookingItemInsert'
responses: responses:
"201": "201":
description: Created description: Created
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem' $ref: 'hs-booking-item-schemas.yaml#/components/schemas/HsBookingItem'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
$ref: './error-responses.yaml#/components/responses/Forbidden' $ref: 'error-responses.yaml#/components/responses/Forbidden'
"409": "409":
$ref: './error-responses.yaml#/components/responses/Conflict' $ref: 'error-responses.yaml#/components/responses/Conflict'

View File

@ -11,7 +11,7 @@ paths:
# Items # Items
/api/hs/booking/items: /api/hs/booking/items:
$ref: "./hs-booking-items.yaml" $ref: "hs-booking-items.yaml"
/api/hs/booking/items/{bookingItemUuid}: /api/hs/booking/items/{bookingItemUuid}:
$ref: "./hs-booking-items-with-uuid.yaml" $ref: "hs-booking-items-with-uuid.yaml"

View File

@ -32,9 +32,9 @@ begin
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert insert
into hs_booking_item (uuid, debitoruuid, caption, validity, resources) into hs_booking_item (uuid, debitoruuid, caption, validity, resources)
values (uuid_generate_v4(), relatedDebitor.uuid, 'some ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "SDD-storage": 512 }'::jsonb), values (uuid_generate_v4(), relatedDebitor.uuid, 'some ManagedServer', daterange('20221001', null, '[]'), '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedDebitor.uuid, 'some CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "HDD-storage": 1024 }'::jsonb), (uuid_generate_v4(), relatedDebitor.uuid, 'some CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedDebitor.uuid, 'some Whatever', daterange('20240401', null, '[]'), '{ "CPUs": 1, "SDD-storage": 512, "HDD-storage": 2048 }'::jsonb); (uuid_generate_v4(), relatedDebitor.uuid, 'some Whatever', daterange('20240401', null, '[]'), '{ "CPU": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb);
end; $$; end; $$;
--// --//

View File

@ -69,23 +69,36 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"caption": "some ManagedServer", "caption": "some ManagedServer",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
"resources": { CPUs: 2, SDD-storage: 512 } "resources": {
}, "CPU": 2,
{ "SDD": 512,
"caption": "some CloudServer", "extra": 42
"validFrom": "2023-01-15", }
"validTo": "2024-04-14", },
"resources": { CPUs: 2, HDD-storage: 1024 } {
}, "caption": "some CloudServer",
{ "validFrom": "2023-01-15",
"caption": "some Whatever", "validTo": "2024-04-14",
"validFrom": "2024-04-01", "resources": {
"validTo": null, "CPU": 2,
"resources": { CPUs: 1, HDD-storage: 2048, SDD-storage: 512 } "HDD": 1024,
} "extra": 42
}
},
{
"caption": "some Whatever",
"validFrom": "2024-04-01",
"validTo": null,
"resources": {
"CPU": 1,
"HDD": 2048,
"SDD": 512,
"extra": 42
}
}
] ]
""")); """));
// @formatter:on // @formatter:on
@ -109,7 +122,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
{ {
"debitorUuid": "%s", "debitorUuid": "%s",
"caption": "some new booking", "caption": "some new booking",
"resources": { "something": 12 }, "resources": { "CPU": 12, "extra": 42 },
"validFrom": "2022-10-13" "validFrom": "2022-10-13"
} }
""".formatted(givenDebitor.getUuid())) """.formatted(givenDebitor.getUuid()))
@ -124,7 +137,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"caption": "some new booking", "caption": "some new booking",
"validFrom": "2022-10-13", "validFrom": "2022-10-13",
"validTo": null, "validTo": null,
"resources": { "something": 12 } "resources": { "CPU": 12 }
} }
""")) """))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
@ -162,7 +175,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"caption": "some CloudServer", "caption": "some CloudServer",
"validFrom": "2023-01-15", "validFrom": "2023-01-15",
"validTo": "2024-04-14", "validTo": "2024-04-14",
"resources": { CPUs: 2, HDD-storage: 1024 } "resources": { CPU: 2, HDD: 1024 }
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -207,7 +220,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"caption": "some CloudServer", "caption": "some CloudServer",
"validFrom": "2023-01-15", "validFrom": "2023-01-15",
"validTo": "2024-04-14", "validTo": "2024-04-14",
"resources": { CPUs: 2, HDD-storage: 1024 } "resources": { CPU: 2, HDD: 1024 }
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -219,7 +232,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Test @Test
void globalAdmin_canPatchAllUpdatablePropertiesOfBookingItem() { void globalAdmin_canPatchAllUpdatablePropertiesOfBookingItem() {
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111); final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1));
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -230,9 +243,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2020-06-05", "validFrom": "2020-06-05",
"validTo": "2022-12-31", "validTo": "2022-12-31",
"resources": { "resources": {
"CPUs": "4", "CPU": "4",
"HDD_storage": null, "HDD": null,
"SSD-storage": "4096" "SSD": "4096"
} }
} }
""") """)
@ -248,8 +261,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2020-06-05", "validFrom": "2020-06-05",
"validTo": "2022-12-31", "validTo": "2022-12-31",
"resources": { "resources": {
"CPUs": "2", "CPU": "4",
"SSD-storage": "2048" "SSD": "4096",
"something": 1
} }
} }
""")); // @formatter:on """)); // @formatter:on
@ -269,9 +283,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canPatchJustValidToOfArbitraryBookingItem() { void globalAdmin_canPatchJustValidToOfArbitraryBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111); final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1));
final var location = RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
@ -292,7 +306,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"validFrom": "2022-11-01", "validFrom": "2022-11-01",
"validTo": "2022-12-31", "validTo": "2022-12-31",
"resources": { "resources": {
"CPUs": 5 "something": 1
} }
} }
""")); // @formatter:on """)); // @formatter:on
@ -310,7 +324,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canNotPatchReferenceOfArbitraryBookingItem() { void globalAdmin_canNotPatchReferenceOfArbitraryBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111); final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1));
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
@ -344,7 +358,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Test @Test
void globalAdmin_canDeleteArbitraryBookingItem() { void globalAdmin_canDeleteArbitraryBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111); final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1));
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -362,7 +376,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Test @Test
void normalUser_canNotDeleteUnrelatedBookingItem() { void normalUser_canNotDeleteUnrelatedBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111); final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111, entry("something", 1));
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -378,7 +392,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
} }
} }
private HsBookingItemEntity givenSomeTemporaryBookingItemForDebitorNumber(final int debitorNumber) { private HsBookingItemEntity givenSomeTemporaryBookingItemForDebitorNumber(final int debitorNumber,
final Map.Entry<String, Integer> resources) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).get(0); final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).get(0);
@ -386,7 +401,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.debitor(givenDebitor) .debitor(givenDebitor)
.caption("some test-booking") .caption("some test-booking")
.resources(Map.ofEntries(entry("something", 1))) .resources(Map.ofEntries(resources))
.validity(Range.closedOpen( .validity(Range.closedOpen(
LocalDate.parse("2022-11-01"), LocalDate.parse("2023-03-31"))) LocalDate.parse("2022-11-01"), LocalDate.parse("2023-03-31")))
.build(); .build();

View File

@ -1,7 +1,6 @@
package net.hostsharing.hsadminng.hs.booking.item; package net.hostsharing.hsadminng.hs.booking.item;
import io.hypersistence.utils.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.ArbitraryBookingResourcesJsonResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
@ -13,11 +12,14 @@ import org.mockito.junit.jupiter.MockitoExtension;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntityPatcher.objectToMap; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntityPatcher.objectToMap;
import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR; import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR;
import static net.hostsharing.hsadminng.mapper.PatchMap.entry;
import static net.hostsharing.hsadminng.mapper.PatchMap.patchMap;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -35,15 +37,22 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
private static final LocalDate PATCHED_VALID_FROM = LocalDate.parse("2022-10-30"); private static final LocalDate PATCHED_VALID_FROM = LocalDate.parse("2022-10-30");
private static final LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31"); private static final LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31");
private static final ArbitraryBookingResourcesJsonResource INITIAL_RESOURCES = new ArbitraryBookingResourcesJsonResource() { private static final Map<String, Object> INITIAL_RESOURCES = patchMap(
Integer cpus = 1; entry("CPU", 1),
Integer hddStorage = 1024; entry("HDD", 1024),
}; entry("MEM", 64)
private static final ArbitraryBookingResourcesJsonResource PATCHED_RESOURCES = new ArbitraryBookingResourcesJsonResource() { );
Integer cpus = 2; private static final Map<String, Object> PATCH_RESOURCES = patchMap(
Integer sddStorage = 256; entry("CPU", 2),
Integer hddStorage = null; entry("HDD", null),
}; entry("SDD", 256)
);
private static final Map<String, Object> PATCHED_RESOURCES = patchMap(
entry("CPU", 2),
entry("SDD", 256),
entry("MEM", 64)
);
private static final String INITIAL_CAPTION = "initial caption"; private static final String INITIAL_CAPTION = "initial caption";
private static final String PATCHED_CAPTION = "patched caption"; private static final String PATCHED_CAPTION = "patched caption";
@ -90,9 +99,9 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
new SimpleProperty<>( new SimpleProperty<>(
"resources", "resources",
HsBookingItemPatchResource::setResources, HsBookingItemPatchResource::setResources,
PATCHED_RESOURCES, PATCH_RESOURCES,
HsBookingItemEntity::putResources, HsBookingItemEntity::putResources,
objectToMap(PATCHED_RESOURCES)) PATCHED_RESOURCES)
.notNullable(), .notNullable(),
new JsonNullableProperty<>( new JsonNullableProperty<>(
"validfrom", "validfrom",

View File

@ -157,9 +157,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then // then
allTheseBookingItemsAreReturned( allTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000212, [2022-10-01,), some ManagedServer, { CPUs: 2, SDD-storage: 512 })", "HsBookingItemEntity(D-1000212, [2022-10-01,), some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsBookingItemEntity(D-1000212, [2023-01-15,2024-04-15), some CloudServer, { CPUs: 2, HDD-storage: 1024 })", "HsBookingItemEntity(D-1000212, [2023-01-15,2024-04-15), some CloudServer, { CPU: 2, HDD: 1024, extra: 42 })",
"HsBookingItemEntity(D-1000212, [2024-04-01,), some Whatever, { CPUs: 1, HDD-storage: 2048, SDD-storage: 512 })"); "HsBookingItemEntity(D-1000212, [2024-04-01,), some Whatever, { CPU: 1, HDD: 2048, SDD: 512, extra: 42 })");
} }
@Test @Test
@ -174,9 +174,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then: // then:
exactlyTheseBookingItemsAreReturned( exactlyTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000111, [2022-10-01,), some ManagedServer, { CPUs: 2, SDD-storage: 512 })", "HsBookingItemEntity(D-1000111, [2022-10-01,), some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsBookingItemEntity(D-1000111, [2023-01-15,2024-04-15), some CloudServer, { CPUs: 2, HDD-storage: 1024 })", "HsBookingItemEntity(D-1000111, [2023-01-15,2024-04-15), some CloudServer, { CPU: 2, HDD: 1024, extra: 42 })",
"HsBookingItemEntity(D-1000111, [2024-04-01,), some Whatever, { CPUs: 1, HDD-storage: 2048, SDD-storage: 512 })"); "HsBookingItemEntity(D-1000111, [2024-04-01,), some Whatever, { CPU: 1, HDD: 2048, SDD: 512, extra: 42 })");
} }
} }