Compare commits
3 Commits
1b7c6c7abe
...
91b59cfd25
Author | SHA1 | Date | |
---|---|---|---|
|
91b59cfd25 | ||
|
992e9f4b74 | ||
04d9b43301 |
@ -16,6 +16,7 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
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) -> {
|
||||
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
|
||||
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
|
||||
entity.putResources(KeyValueMap.from(resource.getResources()));
|
||||
};
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
||||
@Builder.Default
|
||||
@Type(PostgreSQLRangeType.class)
|
||||
@Column(name = "validity", columnDefinition = "daterange")
|
||||
private Range<LocalDate> validity = Range.emptyRange(LocalDate.class);
|
||||
private Range<LocalDate> validity = Range.closedInfinite(LocalDate.now());
|
||||
|
||||
@Column(name = "caption")
|
||||
private String caption;
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||
|
||||
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
@ -15,12 +13,16 @@ class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
|
||||
|
||||
integerProperty("CPUs") .min( 1) .max( 32) .required(),
|
||||
integerProperty("RAM").unit("GB") .min( 1) .max( 128) .required(),
|
||||
integerProperty("SSD").unit("GB") .min( 25) .max( 1000) .step(25).required(),
|
||||
integerProperty("SSD").unit("GB") .min( 0) .max( 1000) .step(25).required(), // (1)
|
||||
integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0),
|
||||
integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(),
|
||||
|
||||
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
|
||||
// @formatter:on
|
||||
);
|
||||
|
||||
// (q) We do have pre-existing CloudServers without SSD, just HDD, thus SSD starts with min=0.
|
||||
// TODO.impl: Validation that SSD+HDD is at minimum 25 GB is missing.
|
||||
// e.g. validationGroup("SSD", "HDD").min(0);
|
||||
}
|
||||
}
|
||||
|
@ -62,10 +62,6 @@ components:
|
||||
minLength: 3
|
||||
maxLength: 80
|
||||
nullable: false
|
||||
validFrom:
|
||||
type: string
|
||||
format: date
|
||||
nullable: false
|
||||
validTo:
|
||||
type: string
|
||||
format: date
|
||||
|
@ -144,13 +144,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"projectUuid": "%s",
|
||||
"projectUuid": "{projectUuid}",
|
||||
"type": "MANAGED_SERVER",
|
||||
"caption": "some new booking",
|
||||
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 },
|
||||
"validFrom": "2022-10-13"
|
||||
"validTo": "{validTo}",
|
||||
"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)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/booking/items")
|
||||
@ -161,11 +164,14 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
{
|
||||
"type": "MANAGED_SERVER",
|
||||
"caption": "some new booking",
|
||||
"validFrom": "2022-10-13",
|
||||
"validTo": null,
|
||||
"validFrom": "{today}",
|
||||
"validTo": "{todayPlus1Month}",
|
||||
"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/[^/]*"))
|
||||
.extract().header("Location"); // @formatter:on
|
||||
|
||||
@ -236,7 +242,6 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
// TODO.impl: For unknown reason this test fails in about 50%, not finding the uuid (404).
|
||||
void projectAdmin_canGetRelatedBookingItem() {
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
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;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.Map;
|
||||
|
||||
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_TO = LocalDate.parse("2030-12-31");
|
||||
|
||||
private MockedStatic<LocalDate> localDateMockedStatic = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS);
|
||||
|
||||
final HsBookingItemEntity givenBookingItem = HsBookingItemEntity.builder()
|
||||
.project(TEST_PROJECT)
|
||||
.type(HsBookingItemType.CLOUD_SERVER)
|
||||
@ -25,6 +31,24 @@ class HsBookingItemEntityUnitTest {
|
||||
.validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO))
|
||||
.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
|
||||
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
||||
final var result = givenBookingItem.toString();
|
||||
|
@ -58,7 +58,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
||||
"{type=boolean, propertyName=active, required=false, defaultValue=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, required=true, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=false}",
|
||||
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=false}",
|
||||
"{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, isTotalsValidator=false}");
|
||||
|
Loading…
Reference in New Issue
Block a user