Skip to content

Commit 02cc75c

Browse files
authored
Feature/conluz 128 (#129)
* [conluz-128] Added instruction to use context7 * [conluz-128] Implemented endpoint to get plan by id
1 parent a31528a commit 02cc75c

File tree

5 files changed

+227
-1
lines changed

5 files changed

+227
-1
lines changed

CLAUDE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,7 @@ With the app running:
149149
- All new code must have automated tests
150150
- Architecture tests are enforced via ArchUnit (see `src/test/java/org/lucoenergia/conluz/architecture/`)
151151
- when injecting beans, always use the interface. This also applies to integration tests
152-
- when creating tests over services that has an interface, always use the name of the interface + "Test" for naming them
152+
- when creating tests over services that has an interface, always use the name of the interface + "Test" for naming them
153+
154+
## General Notes
155+
- Automatically use context7 for code generation and library documentation.

src/main/java/org/lucoenergia/conluz/domain/production/plant/get/GetPlantService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lucoenergia.conluz.domain.production.plant.get;
22

33
import org.lucoenergia.conluz.domain.production.plant.Plant;
4+
import org.lucoenergia.conluz.domain.shared.PlantId;
45
import org.lucoenergia.conluz.domain.shared.pagination.PagedRequest;
56
import org.lucoenergia.conluz.domain.shared.pagination.PagedResult;
67

@@ -16,4 +17,12 @@ public interface GetPlantService {
1617
* @return a paged result of plants
1718
*/
1819
PagedResult<Plant> findAll(PagedRequest pagedRequest);
20+
21+
/**
22+
* Find a plant by its ID.
23+
*
24+
* @param id the plant ID
25+
* @return the plant
26+
*/
27+
Plant findById(PlantId id);
1928
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.lucoenergia.conluz.infrastructure.production.plant.get;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
6+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
7+
import org.lucoenergia.conluz.domain.production.plant.Plant;
8+
import org.lucoenergia.conluz.domain.production.plant.get.GetPlantService;
9+
import org.lucoenergia.conluz.domain.shared.PlantId;
10+
import org.lucoenergia.conluz.infrastructure.production.plant.PlantResponse;
11+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.ApiTag;
12+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.BadRequestErrorResponse;
13+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.ForbiddenErrorResponse;
14+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.InternalServerErrorResponse;
15+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.NotFoundErrorResponse;
16+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.UnauthorizedErrorResponse;
17+
import org.springframework.security.access.prepost.PreAuthorize;
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.PathVariable;
20+
import org.springframework.web.bind.annotation.RequestMapping;
21+
import org.springframework.web.bind.annotation.RestController;
22+
23+
import java.util.UUID;
24+
25+
/**
26+
* Get plant by ID
27+
*/
28+
@RestController
29+
@RequestMapping(value = "/api/v1")
30+
public class GetPlantByIdController {
31+
32+
private final GetPlantService service;
33+
34+
public GetPlantByIdController(GetPlantService service) {
35+
this.service = service;
36+
}
37+
38+
@GetMapping("/plants/{id}")
39+
@Operation(
40+
summary = "Retrieves a single plant by ID",
41+
description = """
42+
This endpoint retrieves detailed information about a specific plant by its unique identifier.
43+
44+
**Required Role: ADMIN**
45+
46+
Authentication is required using a Bearer token.
47+
""",
48+
tags = ApiTag.PLANTS,
49+
operationId = "getPlantById",
50+
security = @SecurityRequirement(name = "bearerToken", scopes = {"ADMIN"})
51+
)
52+
@ApiResponses(value = {
53+
@ApiResponse(
54+
responseCode = "200",
55+
description = "Plant found and returned successfully",
56+
useReturnTypeSchema = true
57+
)
58+
})
59+
@ForbiddenErrorResponse
60+
@UnauthorizedErrorResponse
61+
@BadRequestErrorResponse
62+
@NotFoundErrorResponse
63+
@InternalServerErrorResponse
64+
@PreAuthorize("hasRole('ADMIN')")
65+
public PlantResponse getPlantById(@PathVariable("id") UUID plantId) {
66+
Plant plant = service.findById(PlantId.of(plantId));
67+
return new PlantResponse(plant);
68+
}
69+
}

src/main/java/org/lucoenergia/conluz/infrastructure/production/plant/get/GetPlantServiceImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import org.lucoenergia.conluz.domain.production.plant.get.GetPlantService;
44
import org.lucoenergia.conluz.domain.production.plant.Plant;
5+
import org.lucoenergia.conluz.domain.production.plant.PlantNotFoundException;
56
import org.lucoenergia.conluz.domain.production.plant.get.GetPlantRepository;
7+
import org.lucoenergia.conluz.domain.shared.PlantId;
68
import org.lucoenergia.conluz.domain.shared.pagination.Direction;
79
import org.lucoenergia.conluz.domain.shared.pagination.Order;
810
import org.lucoenergia.conluz.domain.shared.pagination.PagedRequest;
@@ -31,4 +33,10 @@ public PagedResult<Plant> findAll(PagedRequest pagedRequest) {
3133

3234
return repository.findAll(pagedRequest);
3335
}
36+
37+
@Override
38+
public Plant findById(PlantId id) {
39+
return repository.findById(id)
40+
.orElseThrow(() -> new PlantNotFoundException(id));
41+
}
3442
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package org.lucoenergia.conluz.infrastructure.production.plant.get;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.lucoenergia.conluz.domain.admin.supply.Supply;
5+
import org.lucoenergia.conluz.domain.admin.supply.SupplyMother;
6+
import org.lucoenergia.conluz.domain.admin.supply.create.CreateSupplyRepository;
7+
import org.lucoenergia.conluz.domain.admin.user.User;
8+
import org.lucoenergia.conluz.domain.admin.user.UserMother;
9+
import org.lucoenergia.conluz.domain.admin.user.create.CreateUserRepository;
10+
import org.lucoenergia.conluz.domain.production.plant.Plant;
11+
import org.lucoenergia.conluz.domain.production.plant.PlantMother;
12+
import org.lucoenergia.conluz.domain.production.plant.create.CreatePlantRepository;
13+
import org.lucoenergia.conluz.domain.shared.SupplyId;
14+
import org.lucoenergia.conluz.domain.shared.UserId;
15+
import org.lucoenergia.conluz.infrastructure.shared.BaseControllerTest;
16+
import org.springframework.beans.factory.annotation.Autowired;
17+
import org.springframework.http.HttpHeaders;
18+
import org.springframework.http.HttpStatus;
19+
import org.springframework.http.MediaType;
20+
import org.springframework.transaction.annotation.Transactional;
21+
22+
import java.time.format.DateTimeFormatter;
23+
import java.util.UUID;
24+
25+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
26+
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
27+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
28+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
29+
30+
@Transactional
31+
class GetPlantByIdControllerTest extends BaseControllerTest {
32+
33+
@Autowired
34+
private CreateUserRepository createUserRepository;
35+
@Autowired
36+
private CreateSupplyRepository createSupplyRepository;
37+
@Autowired
38+
private CreatePlantRepository createPlantRepository;
39+
40+
@Test
41+
void testGetPlantById_shouldReturnPlant() throws Exception {
42+
43+
// Create user, supply, and plant
44+
User user = UserMother.randomUser();
45+
createUserRepository.create(user);
46+
Supply supply = SupplyMother.random().build();
47+
supply = createSupplyRepository.create(supply, UserId.of(user.getId()));
48+
Plant plant = PlantMother.random(supply).build();
49+
plant = createPlantRepository.create(plant, SupplyId.of(supply.getId()));
50+
51+
// Login as default admin
52+
String authHeader = loginAsDefaultAdmin();
53+
54+
mockMvc.perform(get(String.format("/api/v1/plants/%s", plant.getId()))
55+
.header(HttpHeaders.AUTHORIZATION, authHeader)
56+
.contentType(MediaType.APPLICATION_JSON))
57+
.andDo(print())
58+
.andExpect(status().isOk())
59+
.andExpect(jsonPath("$.id").value(plant.getId().toString()))
60+
.andExpect(jsonPath("$.code").value(plant.getCode()))
61+
.andExpect(jsonPath("$.name").value(plant.getName()))
62+
.andExpect(jsonPath("$.address").value(plant.getAddress()))
63+
.andExpect(jsonPath("$.description").value(plant.getDescription()))
64+
.andExpect(jsonPath("$.inverterProvider").value(plant.getInverterProvider().name()))
65+
.andExpect(jsonPath("$.totalPower").value(plant.getTotalPower()))
66+
.andExpect(jsonPath("$.connectionDate").value(plant.getConnectionDate().format(DateTimeFormatter.ISO_DATE)))
67+
.andExpect(jsonPath("$.supply.id").value(supply.getId().toString()))
68+
.andExpect(jsonPath("$.supply.code").value(supply.getCode()));
69+
}
70+
71+
@Test
72+
void testGetPlantById_shouldReturnNotFoundWhenPlantDoesNotExist() throws Exception {
73+
74+
String authHeader = loginAsDefaultAdmin();
75+
76+
final String plantId = UUID.randomUUID().toString();
77+
78+
mockMvc.perform(get("/api/v1/plants/" + plantId)
79+
.header(HttpHeaders.AUTHORIZATION, authHeader)
80+
.contentType(MediaType.APPLICATION_JSON))
81+
.andDo(print())
82+
.andExpect(status().isNotFound())
83+
.andExpect(jsonPath("$.timestamp").isNotEmpty())
84+
.andExpect(jsonPath("$.status").value(HttpStatus.NOT_FOUND.value()))
85+
.andExpect(jsonPath("$.message").isNotEmpty())
86+
.andExpect(jsonPath("$.traceId").isNotEmpty());
87+
}
88+
89+
@Test
90+
void testGetPlantById_shouldReturnBadRequestWhenIdIsInvalid() throws Exception {
91+
String authHeader = loginAsDefaultAdmin();
92+
93+
mockMvc.perform(get("/api/v1/plants/invalid-uuid")
94+
.header(HttpHeaders.AUTHORIZATION, authHeader)
95+
.contentType(MediaType.APPLICATION_JSON))
96+
.andDo(print())
97+
.andExpect(status().isBadRequest())
98+
.andExpect(jsonPath("$.timestamp").isNotEmpty())
99+
.andExpect(jsonPath("$.status").value(HttpStatus.BAD_REQUEST.value()))
100+
.andExpect(jsonPath("$.message").isNotEmpty())
101+
.andExpect(jsonPath("$.traceId").isNotEmpty());
102+
}
103+
104+
@Test
105+
void testGetPlantById_shouldReturnUnauthorizedWhenNoToken() throws Exception {
106+
107+
mockMvc.perform(get("/api/v1/plants/" + UUID.randomUUID())
108+
.contentType(MediaType.APPLICATION_JSON))
109+
.andDo(print())
110+
.andExpect(status().isUnauthorized())
111+
.andExpect(jsonPath("$.timestamp").isNotEmpty())
112+
.andExpect(jsonPath("$.status").value(HttpStatus.UNAUTHORIZED.value()))
113+
.andExpect(jsonPath("$.message").isNotEmpty())
114+
.andExpect(jsonPath("$.traceId").isNotEmpty());
115+
}
116+
117+
@Test
118+
void testGetPlantById_shouldReturnForbiddenWhenUserIsNotAdmin() throws Exception {
119+
120+
// Create user, supply, and plant
121+
User user = UserMother.randomUser();
122+
createUserRepository.create(user);
123+
Supply supply = SupplyMother.random().build();
124+
supply = createSupplyRepository.create(supply, UserId.of(user.getId()));
125+
Plant plant = PlantMother.random(supply).build();
126+
plant = createPlantRepository.create(plant, SupplyId.of(supply.getId()));
127+
128+
String authHeader = loginAsPartner();
129+
130+
mockMvc.perform(get(String.format("/api/v1/plants/%s", plant.getId()))
131+
.header(HttpHeaders.AUTHORIZATION, authHeader)
132+
.contentType(MediaType.APPLICATION_JSON))
133+
.andDo(print())
134+
.andExpect(status().isForbidden())
135+
.andExpect(jsonPath("$.status").value(HttpStatus.FORBIDDEN.value()));
136+
}
137+
}

0 commit comments

Comments
 (0)