Skip to content

Commit

Permalink
ADM-709:[backend][docs]:Verify buildkite and obtain buildkite data wi…
Browse files Browse the repository at this point in the history
…th new API (#889)

* ADM-709:[backend]feat: add verify token and get info for buildKite controller


Co-authored-by: Andrea <Andrea2000728>
  • Loading branch information
Andrea2000728 authored Jan 11, 2024
1 parent ca482f0 commit 54df641
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package heartbeat.controller.pipeline;

import heartbeat.controller.pipeline.dto.request.PipelineType;
import heartbeat.controller.pipeline.dto.request.TokenParam;
import heartbeat.controller.pipeline.dto.request.PipelineParam;
import heartbeat.controller.pipeline.dto.request.PipelineStepsParam;
import heartbeat.controller.pipeline.dto.response.BuildKiteResponseDTO;
Expand Down Expand Up @@ -29,12 +31,34 @@ public class PipelineController {

private final BuildKiteService buildKiteService;

@Deprecated
@PostMapping("/{pipelineType}")
public BuildKiteResponseDTO getBuildKiteInfo(@PathVariable String pipelineType,
@Valid @RequestBody PipelineParam pipelineParam) {
return buildKiteService.fetchPipelineInfo(pipelineParam);
}

@PostMapping("/{pipelineType}/verify")
public ResponseEntity<Void> verifyBuildKiteToken(@PathVariable @NotBlank String pipelineType,
@Valid @RequestBody TokenParam tokenParam) {
PipelineType.fromValue(pipelineType);
buildKiteService.verifyToken(tokenParam.getToken());
return ResponseEntity.noContent().build();
}

@PostMapping("/{pipelineType}/info")
public ResponseEntity<BuildKiteResponseDTO> fetchBuildKiteInfo(@PathVariable @NotBlank String pipelineType,
@Valid @RequestBody PipelineParam pipelineParam) {
PipelineType.fromValue(pipelineType);
BuildKiteResponseDTO buildKiteResponse = buildKiteService.getBuildKiteInfo(pipelineParam);
if (buildKiteResponse.getPipelineList().isEmpty()) {
return ResponseEntity.noContent().build();
}
else {
return ResponseEntity.ok(buildKiteResponse);
}
}

@GetMapping("/{pipelineType}/{organizationId}/pipelines/{buildId}/steps")
public ResponseEntity<PipelineStepsDTO> getPipelineSteps(
@RequestHeader("Authorization") @NotBlank(message = "Token must not be blank") String token,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package heartbeat.controller.pipeline.dto.request;

import lombok.extern.log4j.Log4j2;

@Log4j2
public enum PipelineType {

BUILDKITE("buildkite");

public final String pipelineType;

PipelineType(String pipelineType) {
this.pipelineType = pipelineType;
}

public static PipelineType fromValue(String type) {
return switch (type) {
case "buildkite" -> BUILDKITE;
default -> {
log.error("Failed to match Pipeline type: {} ", type);
throw new IllegalArgumentException("Pipeline type does not find!");
}
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package heartbeat.controller.pipeline.dto.request;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TokenParam {

@Valid
@NotBlank(message = "Token cannot be empty.")
private String token;

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.net.URLEncoder;

@Service
@RequiredArgsConstructor
Expand All @@ -58,20 +57,21 @@ public void shutdownExecutor() {
customTaskExecutor.shutdown();
}

@Deprecated
public BuildKiteResponseDTO fetchPipelineInfo(PipelineParam pipelineParam) {
try {
String buildKiteToken = "Bearer " + pipelineParam.getToken();
log.info("Start to query token permissions by token");
BuildKiteTokenInfo buildKiteTokenInfo = buildKiteFeignClient.getTokenInfo(buildKiteToken);
log.info("Successfully query token permissions by token, token info scopes: {}",
buildKiteTokenInfo.getScopes());
verifyToken(buildKiteTokenInfo);
verifyTokenScopes(buildKiteTokenInfo);
log.info("Start to query BuildKite organizations by token");
List<BuildKiteOrganizationsInfo> buildKiteOrganizationsInfo = buildKiteFeignClient
.getBuildKiteOrganizationsInfo(buildKiteToken);
log.info("Successfully query BuildKite organizations by token, slug: {}", buildKiteOrganizationsInfo);

log.info("Start to query buildKite pipelineInfo by organizations slug: {}", buildKiteOrganizationsInfo);
log.info("Start to query BuildKite pipelineInfo by organizations slug: {}", buildKiteOrganizationsInfo);
List<Pipeline> buildKiteInfoList = buildKiteOrganizationsInfo.stream()
.flatMap(org -> buildKiteFeignClient
.getPipelineInfo(buildKiteToken, org.getSlug(), "1", "100", pipelineParam.getStartTime(),
Expand All @@ -80,7 +80,7 @@ public BuildKiteResponseDTO fetchPipelineInfo(PipelineParam pipelineParam) {
.map(pipeline -> PipelineTransformer.fromBuildKitePipelineDto(pipeline, org.getSlug(),
org.getName())))
.collect(Collectors.toList());
log.info("Successfully get buildKite pipelineInfo, slug:{}, pipelineInfoList size:{}",
log.info("Successfully get BuildKite pipelineInfo, slug:{}, pipelineInfoList size:{}",
buildKiteOrganizationsInfo, buildKiteInfoList.size());

return BuildKiteResponseDTO.builder().pipelineList(buildKiteInfoList).build();
Expand All @@ -98,7 +98,7 @@ public BuildKiteResponseDTO fetchPipelineInfo(PipelineParam pipelineParam) {
}
}

private void verifyToken(BuildKiteTokenInfo buildKiteTokenInfo) {
private void verifyTokenScopes(BuildKiteTokenInfo buildKiteTokenInfo) {
for (String permission : permissions) {
if (!buildKiteTokenInfo.getScopes().contains(permission)) {
log.error("Failed to call BuildKite, because of insufficient permission, current permissions: {}",
Expand Down Expand Up @@ -300,4 +300,59 @@ private List<DeployInfo> getBuildsByState(List<BuildKiteBuildInfo> buildInfos,
.toList();
}

public void verifyToken(String token) {
try {
String buildKiteToken = "Bearer " + token;
log.info("Start to query token permissions by token");
BuildKiteTokenInfo buildKiteTokenInfo = buildKiteFeignClient.getTokenInfo(buildKiteToken);
log.info("Successfully query token permissions by token, token info scopes: {}",
buildKiteTokenInfo.getScopes());
verifyTokenScopes(buildKiteTokenInfo);
}
catch (RuntimeException e) {
Throwable cause = Optional.ofNullable(e.getCause()).orElse(e);
log.error("Failed to call BuildKite, e: {}", cause.getMessage());
if (cause instanceof BaseException baseException) {
throw baseException;
}
throw new InternalServerErrorException(
String.format("Failed to call BuildKite, cause is %s", cause.getMessage()));
}
}

public BuildKiteResponseDTO getBuildKiteInfo(PipelineParam pipelineParam) {
try {
String buildKiteToken = "Bearer " + pipelineParam.getToken();
log.info("Start to query BuildKite organizations by token");
List<BuildKiteOrganizationsInfo> buildKiteOrganizationsInfo = buildKiteFeignClient
.getBuildKiteOrganizationsInfo(buildKiteToken);
log.info("Successfully query BuildKite organizations by token, slug: {}", buildKiteOrganizationsInfo);

log.info("Start to query BuildKite pipelineInfo by organizations slug: {}", buildKiteOrganizationsInfo);
List<Pipeline> buildKiteInfoList = buildKiteOrganizationsInfo.stream()
.flatMap(org -> buildKiteFeignClient
.getPipelineInfo(buildKiteToken, org.getSlug(), "1", "100", pipelineParam.getStartTime(),
pipelineParam.getEndTime())
.stream()
.map(pipeline -> PipelineTransformer.fromBuildKitePipelineDto(pipeline, org.getSlug(),
org.getName())))
.collect(Collectors.toList());
log.info("Successfully get BuildKite pipelineInfo, slug:{}, pipelineInfoList size:{}",
buildKiteOrganizationsInfo, buildKiteInfoList.size());

return BuildKiteResponseDTO.builder().pipelineList(buildKiteInfoList).build();
}
catch (RuntimeException e) {
Throwable cause = Optional.ofNullable(e.getCause()).orElse(e);
log.error("Failed to call BuildKite, start time: {}, e: {}", pipelineParam.getStartTime(),
cause.getMessage());
if (cause instanceof BaseException baseException) {
throw baseException;
}
throw new InternalServerErrorException(
String.format("Failed to call BuildKite, cause is %s", cause.getMessage()));

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
Expand All @@ -12,13 +13,15 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import heartbeat.controller.pipeline.dto.request.PipelineParam;
import heartbeat.controller.pipeline.dto.request.TokenParam;
import heartbeat.controller.pipeline.dto.response.BuildKiteResponseDTO;
import heartbeat.controller.pipeline.dto.response.Pipeline;
import heartbeat.controller.pipeline.dto.response.PipelineStepsDTO;
import heartbeat.service.pipeline.buildkite.BuildKiteService;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import lombok.val;
Expand All @@ -38,6 +41,14 @@
@AutoConfigureJsonTesters
public class BuildKiteControllerTest {

public static final String TEST_TOKEN = "test_token";

public static final String TEST_START_TIME = "16737733";

public static final String TEST_END_TIME = "17657557";

public static final String BUILD_KITE = "buildkite";

@MockBean
private BuildKiteService buildKiteService;

Expand All @@ -53,16 +64,18 @@ void shouldReturnCorrectPipelineInfoWhenCallBuildKiteMockServer() throws Excepti
BuildKiteResponseDTO buildKiteResponseDTO = BuildKiteResponseDTO.builder().pipelineList(pipelines).build();
when(buildKiteService.fetchPipelineInfo(any())).thenReturn(buildKiteResponseDTO);
PipelineParam pipelineParam = PipelineParam.builder()
.token("test_token")
.startTime("16737733")
.endTime("17657557")
.token(TEST_TOKEN)
.startTime(TEST_START_TIME)
.endTime(TEST_END_TIME)
.build();

MockHttpServletResponse response = mockMvc
.perform(post("/pipelines/buildKite").contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(pipelineParam)))
.andExpect(status().isOk())
.andReturn()
.getResponse();

final var resultId = JsonPath.parse(response.getContentAsString()).read("$.pipelineList[0].id").toString();
assertThat(resultId).contains("payment-selector-ui");
final var resultName = JsonPath.parse(response.getContentAsString()).read("$.pipelineList[0].name").toString();
Expand All @@ -88,6 +101,7 @@ void shouldReturnCorrectPipelineStepsWhenCalBuildKiteMockServer() throws Excepti
.andExpect(status().isOk())
.andReturn()
.getResponse();

val resultStep = JsonPath.parse(response.getContentAsString()).read("$.steps[0]");
assertThat(resultStep).isEqualTo(":docker: publish image to cloudsmith");
}
Expand All @@ -96,7 +110,6 @@ void shouldReturnCorrectPipelineStepsWhenCalBuildKiteMockServer() throws Excepti
void shouldReturnNoContentIfNoStepsWhenCallBuildKite() throws Exception {
List<String> steps = new ArrayList<>();
PipelineStepsDTO emptyPipelineSteps = PipelineStepsDTO.builder().steps(steps).build();

when(buildKiteService.fetchPipelineSteps(anyString(), anyString(), anyString(), any()))
.thenReturn(emptyPipelineSteps);

Expand All @@ -116,4 +129,70 @@ void shouldReturnNoContentIfNoStepsWhenCallBuildKite() throws Exception {
assertThat(response.getContentAsString()).isEqualTo("");
}

@Test
void shouldReturnNoContentWhenCorrectTokenCallBuildKite() throws Exception {
ObjectMapper mapper = new ObjectMapper();
TokenParam tokenParam = TokenParam.builder().token(TEST_TOKEN).build();
doNothing().when(buildKiteService).verifyToken(any());

MockHttpServletResponse response = mockMvc
.perform(post("/pipelines/{pipelineType}/verify", BUILD_KITE).contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(tokenParam)))
.andExpect(status().isNoContent())
.andReturn()
.getResponse();

assertThat(response.getContentAsString()).isEqualTo("");
}

@Test
void shouldReturnPipelineInfoWhenCorrectTokenCallBuildKite() throws Exception {
ObjectMapper mapper = new ObjectMapper();
List<Pipeline> pipelines = mapper.readValue(
new File("src/test/java/heartbeat/controller/pipeline/pipelineInfoData.json"), new TypeReference<>() {
});
BuildKiteResponseDTO buildKiteResponseDTO = BuildKiteResponseDTO.builder().pipelineList(pipelines).build();
PipelineParam pipelineParam = PipelineParam.builder()
.token(TEST_TOKEN)
.startTime(TEST_START_TIME)
.endTime(TEST_END_TIME)
.build();
when(buildKiteService.getBuildKiteInfo(any())).thenReturn(buildKiteResponseDTO);

MockHttpServletResponse response = mockMvc
.perform(post("/pipelines/{pipelineType}/info", BUILD_KITE).contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(pipelineParam)))
.andExpect(status().isOk())
.andReturn()
.getResponse();

final var resultId = JsonPath.parse(response.getContentAsString()).read("$.pipelineList[0].id").toString();
assertThat(resultId).contains("payment-selector-ui");
final var resultName = JsonPath.parse(response.getContentAsString()).read("$.pipelineList[0].name").toString();
assertThat(resultName).contains("payment-selector-ui");
}

@Test
void shouldReturnNoContentGivenPipelineInfoIsNullWhenCallingBuildKite() throws Exception {
ObjectMapper mapper = new ObjectMapper();
BuildKiteResponseDTO buildKiteResponseDTO = BuildKiteResponseDTO.builder()
.pipelineList(Collections.emptyList())
.build();
PipelineParam pipelineParam = PipelineParam.builder()
.token(TEST_TOKEN)
.startTime(TEST_START_TIME)
.endTime(TEST_END_TIME)
.build();
when(buildKiteService.getBuildKiteInfo(any())).thenReturn(buildKiteResponseDTO);

MockHttpServletResponse response = mockMvc
.perform(post("/pipelines/{pipelineType}/info", BUILD_KITE).contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(pipelineParam)))
.andExpect(status().isNoContent())
.andReturn()
.getResponse();

assertThat(response.getContentAsString()).isEqualTo("");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package heartbeat.controller.pipeline.dto.response;

import heartbeat.controller.pipeline.dto.request.PipelineType;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class PipelineTypeTest {

@Test
public void shouldConvertValueToType() {
PipelineType buildKiteType = PipelineType.fromValue("buildkite");

assertEquals(buildKiteType, PipelineType.BUILDKITE);
}

@Test
public void shouldThrowExceptionWhenDateTypeNotSupported() {
assertThatThrownBy(() -> PipelineType.fromValue("unknown")).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Pipeline type does not find!");
}

}
Loading

0 comments on commit 54df641

Please sign in to comment.