Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ca.bc.gov.oracleapi.dto.consep;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

/**
* A DTO containing the fields needed for
* assigning a germinator ID to an existing {@link ca.bc.gov.oracleapi.entity.consep.GerminatorTrayEntity}.
*/
public record GerminatorTrayAssignGerminatorIdDto(
@Schema(description = "Primary key of the germinator tray", example = "101")
@NotNull
Integer germinatorTrayId,
Comment on lines +12 to +15
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request DTO should not include the germinatorTrayId field. In this codebase, PATCH endpoints follow the convention of passing the resource ID only as a path variable, not in the request body. This is consistent with other PATCH endpoints like MoistureContentConesEndpoint and PurityTestsEndpoint, where the resource ID is provided via @PathVariable and the request DTO (FormDto) contains only the fields to be updated. Remove the germinatorTrayId field from this DTO.

Copilot uses AI. Check for mistakes.

@Schema(description = "Germinator ID to assign to the tray", example = "1")
@Size(max = 1)
@NotBlank
String germinatorId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ca.bc.gov.oracleapi.dto.consep;

/**
* A DTO containing the return fields for
* assigning a germinator ID to an existing {@link ca.bc.gov.oracleapi.entity.consep.GerminatorTrayEntity}.
*/
public record GerminatorTrayAssignGerminatorIdResponseDto(
Integer germinatorTrayId,
String germinatorId
) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ca.bc.gov.oracleapi.endpoint.consep;

import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayAssignGerminatorIdDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayAssignGerminatorIdResponseDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayCreateDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayCreateResponseDto;
import ca.bc.gov.oracleapi.response.ApiAuthResponse;
Expand All @@ -14,11 +16,14 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

/**
* This class exposes germinator tray resources API.
Expand Down Expand Up @@ -53,4 +58,33 @@ public List<GerminatorTrayCreateResponseDto> assignGerminatorTrays(
) {
return testResultService.assignGerminatorTrays(requests);
}

/**
* Assigns a germinator ID to an existing germinator tray.
*
* @param germinatorTrayId the ID of the germinator tray
* @param request the request containing the germinator ID to assign
* @return a response DTO confirming the assignment
*/
@PatchMapping("/{germinatorTrayId}/germinator-id")
@ResponseStatus(HttpStatus.OK)
@ApiResponse(
responseCode = "200",
description = "Successfully assigned germinator ID to the tray.",
content = @Content(schema = @Schema(implementation = GerminatorTrayAssignGerminatorIdResponseDto.class))
)
@ApiAuthResponse
@RoleAccessConfig({ "SPAR_TSC_SUBMITTER", "SPAR_TSC_SUPERVISOR" })
public GerminatorTrayAssignGerminatorIdResponseDto assignGerminatorIdToTray(
@PathVariable Integer germinatorTrayId,
@Valid @RequestBody GerminatorTrayAssignGerminatorIdDto request
) {
// Validate that the tray ID in the path matches the one in the request body
if (!germinatorTrayId.equals(request.germinatorTrayId())) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Germinator tray ID in path does not match the ID in request body"
);
} return testResultService.assignGerminatorIdToTray(request);
Comment on lines +82 to +88
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation logic is unnecessary and inconsistent with the codebase's PATCH endpoint conventions. Other PATCH endpoints in this codebase (e.g., MoistureContentConesEndpoint.updateReplicateField, PurityTestsEndpoint.updateReplicateField) do not include the resource ID in the request body and therefore don't need this validation. The germinatorTrayId should be removed from GerminatorTrayAssignGerminatorIdDto, and this validation should be removed accordingly.

Suggested change
// Validate that the tray ID in the path matches the one in the request body
if (!germinatorTrayId.equals(request.germinatorTrayId())) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Germinator tray ID in path does not match the ID in request body"
);
} return testResultService.assignGerminatorIdToTray(request);
return testResultService.assignGerminatorIdToTray(request);

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement should be on a new line after the closing brace. This appears to be a formatting error where the return statement is incorrectly placed on the same line as the closing brace of the if block.

Suggested change
} return testResultService.assignGerminatorIdToTray(request);
}
return testResultService.assignGerminatorIdToTray(request);

Copilot uses AI. Check for mistakes.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import ca.bc.gov.oracleapi.config.SparLog;
import ca.bc.gov.oracleapi.dto.consep.GermTestResultDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayAssignGerminatorIdDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayAssignGerminatorIdResponseDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayCreateDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayCreateResponseDto;
import ca.bc.gov.oracleapi.entity.consep.ActivityEntity;
Expand Down Expand Up @@ -315,4 +317,48 @@ private Map<BigDecimal, GermTestResultDto> collectAndValidateGermTestResults(

return resultMap;
}

/**
* Assign a germinator ID to an existing germinator tray.
*
* @param request contains the germinator tray ID and the germinator ID to assign
* @return a response DTO confirming the assignment
* @throws ResponseStatusException if the request is null or if the tray is not found
Comment on lines +321 to +326
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This JavaDoc will need to be updated once the API is refactored to follow the codebase convention. The @param description should reflect that germinatorTrayId and germinatorId are separate parameters rather than being contained in a single request DTO.

Copilot uses AI. Check for mistakes.
*/
public GerminatorTrayAssignGerminatorIdResponseDto assignGerminatorIdToTray(
GerminatorTrayAssignGerminatorIdDto request
) {
if (request == null) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Germinator tray assignment request cannot be null"
);
}
Comment on lines +331 to +336
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the API is refactored to follow the codebase convention, this null check should be updated. Instead of checking if the request DTO is null, the method should validate the individual parameters (germinatorTrayId and germinatorId) or rely on Spring's validation annotations at the controller level. Similar to other service methods in the codebase, consider using @NonNull annotations on the parameters.

Copilot uses AI. Check for mistakes.

SparLog.info(
"Assigning germinator ID {} to germinator tray ID {}",
request.germinatorId(),
request.germinatorTrayId()
);

GerminatorTrayEntity tray = germinatorTrayRepository.findById(request.germinatorTrayId())
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"Germinator tray not found with ID: " + request.germinatorTrayId()
));

tray.setGerminatorId(request.germinatorId());
germinatorTrayRepository.save(tray);

SparLog.info(
"Successfully assigned germinator ID {} to tray ID {}",
request.germinatorId(),
request.germinatorTrayId()
);

return new GerminatorTrayAssignGerminatorIdResponseDto(
request.germinatorTrayId(),
request.germinatorId()
Comment on lines +324 to +361
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the API is refactored to follow the codebase convention (removing germinatorTrayId from the request DTO), this service method signature will need to be updated to accept the germinatorTrayId and germinatorId as separate parameters instead of as a single DTO. The method should be changed to accept Integer germinatorTrayId and String germinatorId directly.

Suggested change
* @param request contains the germinator tray ID and the germinator ID to assign
* @return a response DTO confirming the assignment
* @throws ResponseStatusException if the request is null or if the tray is not found
*/
public GerminatorTrayAssignGerminatorIdResponseDto assignGerminatorIdToTray(
GerminatorTrayAssignGerminatorIdDto request
) {
if (request == null) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Germinator tray assignment request cannot be null"
);
}
SparLog.info(
"Assigning germinator ID {} to germinator tray ID {}",
request.germinatorId(),
request.germinatorTrayId()
);
GerminatorTrayEntity tray = germinatorTrayRepository.findById(request.germinatorTrayId())
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"Germinator tray not found with ID: " + request.germinatorTrayId()
));
tray.setGerminatorId(request.germinatorId());
germinatorTrayRepository.save(tray);
SparLog.info(
"Successfully assigned germinator ID {} to tray ID {}",
request.germinatorId(),
request.germinatorTrayId()
);
return new GerminatorTrayAssignGerminatorIdResponseDto(
request.germinatorTrayId(),
request.germinatorId()
* @param germinatorTrayId the ID of the germinator tray to assign to
* @param germinatorId the germinator ID to assign to the tray
* @return a response DTO confirming the assignment
* @throws ResponseStatusException if any parameter is null or if the tray is not found
*/
public GerminatorTrayAssignGerminatorIdResponseDto assignGerminatorIdToTray(
Integer germinatorTrayId,
String germinatorId
) {
if (germinatorTrayId == null) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Germinator tray ID cannot be null"
);
}
if (germinatorId == null) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Germinator ID cannot be null"
);
}
SparLog.info(
"Assigning germinator ID {} to germinator tray ID {}",
germinatorId,
germinatorTrayId
);
GerminatorTrayEntity tray = germinatorTrayRepository.findById(germinatorTrayId)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"Germinator tray not found with ID: " + germinatorTrayId
));
tray.setGerminatorId(germinatorId);
germinatorTrayRepository.save(tray);
SparLog.info(
"Successfully assigned germinator ID {} to tray ID {}",
germinatorId,
germinatorTrayId
);
return new GerminatorTrayAssignGerminatorIdResponseDto(
germinatorTrayId,
germinatorId

Copilot uses AI. Check for mistakes.
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayAssignGerminatorIdDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayAssignGerminatorIdResponseDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayCreateDto;
import ca.bc.gov.oracleapi.dto.consep.GerminatorTrayCreateResponseDto;
import ca.bc.gov.oracleapi.service.consep.TestResultService;
Expand Down Expand Up @@ -143,4 +146,97 @@ void assignGerminatorTrays_propagatesNotFoundWhenTestResultDatesMissing_andCalls
// Verify that the service was invoked
verify(testResultService, times(1)).assignGerminatorTrays(requests);
}
}

@Test
void assignGerminatorIdToTray_returns200AndBody_andCallsService() throws Exception {
// Arrange
Integer germinatorTrayId = 101;
String germinatorId = "A";
GerminatorTrayAssignGerminatorIdDto request =
new GerminatorTrayAssignGerminatorIdDto(germinatorTrayId, germinatorId);

GerminatorTrayAssignGerminatorIdResponseDto response =
new GerminatorTrayAssignGerminatorIdResponseDto(
germinatorTrayId,
germinatorId
);

when(testResultService.assignGerminatorIdToTray(any())).thenReturn(response);
Comment on lines +150 to +164
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the API is refactored to follow the codebase convention (removing germinatorTrayId from the request DTO), this test will need to be updated. The request object should only contain the germinatorId, not the germinatorTrayId. The service method will also need to be updated to accept separate parameters.

Copilot uses AI. Check for mistakes.

// Act / Assert
mockMvc.perform(patch(BASE_URL + "/" + germinatorTrayId + "/germinator-id")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.germinatorTrayId").value(101))
.andExpect(jsonPath("$.germinatorId").value("A"));

// Verify service was called
verify(testResultService, times(1)).assignGerminatorIdToTray(request);
}

@Test
void assignGerminatorIdToTray_returns404_whenTrayNotFound() throws Exception {
// Arrange
Integer germinatorTrayId = 999;
String germinatorId = "A";
GerminatorTrayAssignGerminatorIdDto request =
new GerminatorTrayAssignGerminatorIdDto(germinatorTrayId, germinatorId);

when(testResultService.assignGerminatorIdToTray(any()))
.thenThrow(new ResponseStatusException(
HttpStatus.NOT_FOUND,
"Germinator tray not found with ID: " + germinatorTrayId
));

// Act / Assert
mockMvc.perform(patch(BASE_URL + "/" + germinatorTrayId + "/germinator-id")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isNotFound());

// Verify service was called
verify(testResultService, times(1)).assignGerminatorIdToTray(request);
}
Comment on lines +180 to +203
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the API is refactored to follow the codebase convention (removing germinatorTrayId from the request DTO), this test will need to be updated. The request object should only contain the germinatorId, not the germinatorTrayId.

Copilot uses AI. Check for mistakes.

@Test
void assignGerminatorIdToTray_returns400_whenPathAndBodyIdMismatch() throws Exception {
// Arrange - path has ID 101 but body has ID 102
Integer pathTrayId = 101;
Integer bodyTrayId = 102;
String germinatorId = "A";
GerminatorTrayAssignGerminatorIdDto request =
new GerminatorTrayAssignGerminatorIdDto(bodyTrayId, germinatorId);

// Act / Assert
mockMvc.perform(patch(BASE_URL + "/" + pathTrayId + "/germinator-id")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());

// Verify service was NOT called because validation failed
verify(testResultService, times(0)).assignGerminatorIdToTray(any());
}

@Test
Comment on lines +206 to +225
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test validates logic that should not exist in the endpoint. Following the codebase's PATCH endpoint conventions, the germinatorTrayId should not be in the request body, making this path/body mismatch validation unnecessary. Once the API is refactored to follow the convention, this test should be removed.

Suggested change
void assignGerminatorIdToTray_returns400_whenPathAndBodyIdMismatch() throws Exception {
// Arrange - path has ID 101 but body has ID 102
Integer pathTrayId = 101;
Integer bodyTrayId = 102;
String germinatorId = "A";
GerminatorTrayAssignGerminatorIdDto request =
new GerminatorTrayAssignGerminatorIdDto(bodyTrayId, germinatorId);
// Act / Assert
mockMvc.perform(patch(BASE_URL + "/" + pathTrayId + "/germinator-id")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
// Verify service was NOT called because validation failed
verify(testResultService, times(0)).assignGerminatorIdToTray(any());
}
@Test

Copilot uses AI. Check for mistakes.
void whenGerminatorIdBlank() throws Exception {
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test method name does not follow the established naming convention in this file. Other test methods follow the pattern "methodName_expectedBehavior_condition" (e.g., "assignGerminatorIdToTray_returns400_whenPathAndBodyIdMismatch"). This test should be renamed to follow the same pattern, such as "assignGerminatorIdToTray_returns400_whenGerminatorIdBlank".

Suggested change
void whenGerminatorIdBlank() throws Exception {
void assignGerminatorIdToTray_returns400_whenGerminatorIdBlank() throws Exception {

Copilot uses AI. Check for mistakes.
// Arrange - empty germinator ID
Integer germinatorTrayId = 101;
GerminatorTrayAssignGerminatorIdDto request =
new GerminatorTrayAssignGerminatorIdDto(germinatorTrayId, "");

// Act / Assert
mockMvc.perform(patch(BASE_URL + "/" + germinatorTrayId + "/germinator-id")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());

// Verify service was NOT called because validation failed
verify(testResultService, times(0)).assignGerminatorIdToTray(any());
}
Comment on lines +225 to +241
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the API is refactored to follow the codebase convention (removing germinatorTrayId from the request DTO), this test will need to be updated. The request object should only contain the germinatorId, not the germinatorTrayId.

Copilot uses AI. Check for mistakes.
}
Loading