BookingItem validity start date today (#62)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #62 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
62867a4cac
commit
04d9b43301
@ -16,6 +16,7 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
|
|||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@ -130,9 +131,8 @@ 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(LocalDate.now(), resource.getValidTo()));
|
||||||
entity.putResources(KeyValueMap.from(resource.getResources()));
|
entity.putResources(KeyValueMap.from(resource.getResources()));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,6 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
|||||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
import org.hibernate.annotations.NotFound;
|
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
|
||||||
import org.hibernate.annotations.Type;
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import jakarta.persistence.CascadeType;
|
||||||
@ -101,7 +99,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
@Builder.Default
|
@Builder.Default
|
||||||
@Type(PostgreSQLRangeType.class)
|
@Type(PostgreSQLRangeType.class)
|
||||||
@Column(name = "validity", columnDefinition = "daterange")
|
@Column(name = "validity", columnDefinition = "daterange")
|
||||||
private Range<LocalDate> validity = Range.emptyRange(LocalDate.class);
|
private Range<LocalDate> validity = Range.closedInfinite(LocalDate.now());
|
||||||
|
|
||||||
@Column(name = "caption")
|
@Column(name = "caption")
|
||||||
private String caption;
|
private String caption;
|
||||||
|
@ -62,10 +62,6 @@ components:
|
|||||||
minLength: 3
|
minLength: 3
|
||||||
maxLength: 80
|
maxLength: 80
|
||||||
nullable: false
|
nullable: false
|
||||||
validFrom:
|
|
||||||
type: string
|
|
||||||
format: date
|
|
||||||
nullable: false
|
|
||||||
validTo:
|
validTo:
|
||||||
type: string
|
type: string
|
||||||
format: date
|
format: date
|
||||||
|
@ -144,13 +144,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body("""
|
.body("""
|
||||||
{
|
{
|
||||||
"projectUuid": "%s",
|
"projectUuid": "{projectUuid}",
|
||||||
"type": "MANAGED_SERVER",
|
"type": "MANAGED_SERVER",
|
||||||
"caption": "some new booking",
|
"caption": "some new booking",
|
||||||
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 },
|
"validTo": "{validTo}",
|
||||||
"validFrom": "2022-10-13"
|
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
|
||||||
}
|
}
|
||||||
""".formatted(givenProject.getUuid()))
|
"""
|
||||||
|
.replace("{projectUuid}", givenProject.getUuid().toString())
|
||||||
|
.replace("{validTo}", LocalDate.now().plusMonths(1).toString())
|
||||||
|
)
|
||||||
.port(port)
|
.port(port)
|
||||||
.when()
|
.when()
|
||||||
.post("http://localhost/api/hs/booking/items")
|
.post("http://localhost/api/hs/booking/items")
|
||||||
@ -161,11 +164,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
{
|
{
|
||||||
"type": "MANAGED_SERVER",
|
"type": "MANAGED_SERVER",
|
||||||
"caption": "some new booking",
|
"caption": "some new booking",
|
||||||
"validFrom": "2022-10-13",
|
"validFrom": "{today}",
|
||||||
"validTo": null,
|
"validTo": "{todayPlus1Month}",
|
||||||
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
|
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
|
||||||
}
|
}
|
||||||
"""))
|
"""
|
||||||
|
.replace("{today}", LocalDate.now().toString())
|
||||||
|
.replace("{todayPlus1Month}", LocalDate.now().plusMonths(1).toString()))
|
||||||
|
)
|
||||||
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/booking/items/[^/]*"))
|
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/booking/items/[^/]*"))
|
||||||
.extract().header("Location"); // @formatter:on
|
.extract().header("Location"); // @formatter:on
|
||||||
|
|
||||||
@ -236,7 +242,6 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(3)
|
@Order(3)
|
||||||
// TODO.impl: For unknown reason this test fails in about 50%, not finding the uuid (404).
|
|
||||||
void projectAdmin_canGetRelatedBookingItem() {
|
void projectAdmin_canGetRelatedBookingItem() {
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
final var givenBookingItem = bookingItemRepo.findByCaption("separate ManagedServer").stream()
|
final var givenBookingItem = bookingItemRepo.findByCaption("separate ManagedServer").stream()
|
||||||
|
@ -0,0 +1,171 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository;
|
||||||
|
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
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.context.annotation.Import;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.EntityManagerFactory;
|
||||||
|
import jakarta.persistence.SynchronizationType;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
|
||||||
|
import static org.hamcrest.Matchers.matchesRegex;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@WebMvcTest(HsBookingItemController.class)
|
||||||
|
@Import(Mapper.class)
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
class HsBookingItemControllerRestTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MockMvc mockMvc;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
Context contextMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
EntityManagerFactory emf;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
HsBookingProjectRepository bookingProjectRepo;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
HsBookingItemRepository bookingItemRepo;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init() {
|
||||||
|
when(emf.createEntityManager()).thenReturn(em);
|
||||||
|
when(emf.createEntityManager(any(Map.class))).thenReturn(em);
|
||||||
|
when(emf.createEntityManager(any(SynchronizationType.class))).thenReturn(em);
|
||||||
|
when(emf.createEntityManager(any(SynchronizationType.class), any(Map.class))).thenReturn(em);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class AddBookingItem {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void globalAdmin_canAddValidBookingItem() throws Exception {
|
||||||
|
|
||||||
|
final var givenProjectUuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
// given
|
||||||
|
when(em.find(HsBookingProjectEntity.class, givenProjectUuid)).thenAnswer(invocation ->
|
||||||
|
HsBookingProjectEntity.builder()
|
||||||
|
.uuid(invocation.getArgument(1))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
when(bookingItemRepo.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
// when
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders
|
||||||
|
.post("/api/hs/booking/items")
|
||||||
|
.header("current-user", "superuser-alex@hostsharing.net")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{
|
||||||
|
"projectUuid": "{projectUuid}",
|
||||||
|
"type": "MANAGED_SERVER",
|
||||||
|
"caption": "some new booking",
|
||||||
|
"validTo": "{validTo}",
|
||||||
|
"garbage": "should not be accepted",
|
||||||
|
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.replace("{projectUuid}", givenProjectUuid.toString())
|
||||||
|
.replace("{validTo}", LocalDate.now().plusMonths(1).toString())
|
||||||
|
)
|
||||||
|
.accept(MediaType.APPLICATION_JSON))
|
||||||
|
|
||||||
|
// then
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$", lenientlyEquals("""
|
||||||
|
{
|
||||||
|
"type": "MANAGED_SERVER",
|
||||||
|
"caption": "some new booking",
|
||||||
|
"validFrom": "{today}",
|
||||||
|
"validTo": "{todayPlus1Month}",
|
||||||
|
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.replace("{today}", LocalDate.now().toString())
|
||||||
|
.replace("{todayPlus1Month}", LocalDate.now().plusMonths(1).toString()))
|
||||||
|
))
|
||||||
|
.andExpect(header().string("Location", matchesRegex("http://localhost/api/hs/booking/items/[^/]*")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void globalAdmin_canNotAddInvalidBookingItem() throws Exception {
|
||||||
|
|
||||||
|
final var givenProjectUuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
// given
|
||||||
|
when(em.find(HsBookingProjectEntity.class, givenProjectUuid)).thenAnswer(invocation ->
|
||||||
|
HsBookingProjectEntity.builder()
|
||||||
|
.uuid(invocation.getArgument(1))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
when(bookingItemRepo.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
// when
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders
|
||||||
|
.post("/api/hs/booking/items")
|
||||||
|
.header("current-user", "superuser-alex@hostsharing.net")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{
|
||||||
|
"projectUuid": "{projectUuid}",
|
||||||
|
"type": "MANAGED_SERVER",
|
||||||
|
"caption": "some new booking",
|
||||||
|
"validFrom": "{validFrom}",
|
||||||
|
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.replace("{projectUuid}", givenProjectUuid.toString())
|
||||||
|
.replace("{validFrom}", LocalDate.now().plusMonths(1).toString())
|
||||||
|
)
|
||||||
|
.accept(MediaType.APPLICATION_JSON))
|
||||||
|
|
||||||
|
// then
|
||||||
|
// TODO.test: MockMvc does not seem to validate additionalProperties=false
|
||||||
|
// .andExpect(status().is4xxClientError())
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$", lenientlyEquals("""
|
||||||
|
{
|
||||||
|
"type": "MANAGED_SERVER",
|
||||||
|
"caption": "some new booking",
|
||||||
|
"validFrom": "{today}",
|
||||||
|
"validTo": null,
|
||||||
|
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 }
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.replace("{today}", LocalDate.now().toString())
|
||||||
|
.replace("{todayPlus1Month}", LocalDate.now().plusMonths(1).toString()))
|
||||||
|
))
|
||||||
|
.andExpect(header().string("Location", matchesRegex("http://localhost/api/hs/booking/items/[^/]*")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,12 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item;
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.Month;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
@ -14,6 +18,8 @@ class HsBookingItemEntityUnitTest {
|
|||||||
public static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-01-01");
|
public static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-01-01");
|
||||||
public static final LocalDate GIVEN_VALID_TO = LocalDate.parse("2030-12-31");
|
public static final LocalDate GIVEN_VALID_TO = LocalDate.parse("2030-12-31");
|
||||||
|
|
||||||
|
private MockedStatic<LocalDate> localDateMockedStatic = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS);
|
||||||
|
|
||||||
final HsBookingItemEntity givenBookingItem = HsBookingItemEntity.builder()
|
final HsBookingItemEntity givenBookingItem = HsBookingItemEntity.builder()
|
||||||
.project(TEST_PROJECT)
|
.project(TEST_PROJECT)
|
||||||
.type(HsBookingItemType.CLOUD_SERVER)
|
.type(HsBookingItemType.CLOUD_SERVER)
|
||||||
@ -25,6 +31,24 @@ class HsBookingItemEntityUnitTest {
|
|||||||
.validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO))
|
.validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
localDateMockedStatic.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validityStartsToday() {
|
||||||
|
// given
|
||||||
|
final var fakedToday = LocalDate.of(2024, Month.MAY, 1);
|
||||||
|
localDateMockedStatic.when(LocalDate::now).thenReturn(fakedToday);
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var newBookingItem = HsBookingItemEntity.builder().build();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(newBookingItem.getValidity().toString()).isEqualTo("Range{lower=2024-05-01, upper=null, mask=82, clazz=class java.time.LocalDate}");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
||||||
final var result = givenBookingItem.toString();
|
final var result = givenBookingItem.toString();
|
||||||
|
@ -24,8 +24,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
|
||||||
|
Loading…
Reference in New Issue
Block a user