diff --git a/src/test/java/com/slotline/api/ReservationApiTest.java b/src/test/java/com/slotline/api/ReservationApiTest.java new file mode 100644 index 0000000..d3a40a5 --- /dev/null +++ b/src/test/java/com/slotline/api/ReservationApiTest.java @@ -0,0 +1,119 @@ +package com.slotline.api; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.slotline.reservation.dto.ReservationRequest; +import com.slotline.resource.dto.ResourceRequest; +import com.slotline.user.dto.LoginRequest; +import com.slotline.user.dto.RegisterRequest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class ReservationApiTest { + + private static final Instant SLOT_START = Instant.parse("2026-09-01T10:00:00Z"); + private static final Instant SLOT_END = Instant.parse("2026-09-01T11:00:00Z"); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void happyPathBookingThenConflictThenCancelThenRebook() throws Exception { + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(json(new RegisterRequest("alice", "password123")))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.role").value("USER")); + + String userToken = login("alice", "password123"); + String adminToken = login("admin", "admin12345"); + + mockMvc.perform(post("/api/resources") + .header("Authorization", "Bearer " + userToken) + .contentType(MediaType.APPLICATION_JSON) + .content(json(new ResourceRequest("Court 1", "Blocked for USER")))) + .andExpect(status().isForbidden()); + + MvcResult created = mockMvc.perform(post("/api/resources") + .header("Authorization", "Bearer " + adminToken) + .contentType(MediaType.APPLICATION_JSON) + .content(json(new ResourceRequest("Court 1", "Outdoor tennis court")))) + .andExpect(status().isCreated()) + .andReturn(); + long resourceId = tree(created).get("id").asLong(); + + ReservationRequest slot = new ReservationRequest(resourceId, SLOT_START, SLOT_END); + + mockMvc.perform(post("/api/reservations") + .contentType(MediaType.APPLICATION_JSON) + .content(json(slot))) + .andExpect(status().isUnauthorized()); + + MvcResult reservation = mockMvc.perform(post("/api/reservations") + .header("Authorization", "Bearer " + userToken) + .contentType(MediaType.APPLICATION_JSON) + .content(json(slot))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.status").value("ACTIVE")) + .andReturn(); + long reservationId = tree(reservation).get("id").asLong(); + + mockMvc.perform(post("/api/reservations") + .header("Authorization", "Bearer " + userToken) + .contentType(MediaType.APPLICATION_JSON) + .content(json(slot))) + .andExpect(status().isConflict()); + + mockMvc.perform(post("/api/reservations/" + reservationId + "/cancel") + .header("Authorization", "Bearer " + userToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("CANCELLED")); + + mockMvc.perform(post("/api/reservations") + .header("Authorization", "Bearer " + userToken) + .contentType(MediaType.APPLICATION_JSON) + .content(json(slot))) + .andExpect(status().isCreated()); + + mockMvc.perform(get("/api/resources/" + resourceId + "/reservations") + .header("Authorization", "Bearer " + userToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(1)); + } + + private String login(String username, String password) throws Exception { + MvcResult result = mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(json(new LoginRequest(username, password)))) + .andExpect(status().isOk()) + .andReturn(); + return tree(result).get("token").asText(); + } + + private String json(Object payload) throws Exception { + return objectMapper.writeValueAsString(payload); + } + + private JsonNode tree(MvcResult result) throws Exception { + return objectMapper.readTree(result.getResponse().getContentAsString()); + } +}