Skip to content

Commit 06f5552

Browse files
dakodakovmivanov1988
andauthored
control-service: secrets service unit tests (#2276)
Add unit tests for the secrets service. Minor code changes to address redundancies, feedback from previous review. --------- Signed-off-by: Dako Dakov <ddakov@vmware.com> Co-authored-by: github-actions <> Co-authored-by: Miroslav Ivanov <40535952+mivanov1988@users.noreply.github.com>
1 parent 189b074 commit 06f5552

File tree

7 files changed

+330
-29
lines changed

7 files changed

+330
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2021-2023 VMware, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.vmware.taurus.exception;
7+
8+
import org.springframework.http.HttpStatus;
9+
10+
public class DataJobSecretsSizeLimitException extends DomainError implements UserFacingError {
11+
12+
public DataJobSecretsSizeLimitException(String jobName, String why) {
13+
super(
14+
String.format("Exception processing secrets for '%s' data job.", jobName),
15+
why,
16+
"Unable to process data job secrets.",
17+
"Try re-creating the data job secrets or contract the service operator",
18+
null);
19+
}
20+
21+
@Override
22+
public HttpStatus getHttpStatus() {
23+
return HttpStatus.PAYLOAD_TOO_LARGE;
24+
}
25+
}

projects/control-service/projects/pipelines_control_service/src/main/java/com/vmware/taurus/exception/SecretStorageNotConfiguredException.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55

66
package com.vmware.taurus.exception;
77

8+
import org.springframework.http.HttpStatus;
9+
810
/** Exception thrown, when a secret storage has not been configured */
911
public class SecretStorageNotConfiguredException extends ExternalSystemError {
1012

1113
public SecretStorageNotConfiguredException() {
1214
super(MainExternalSystem.SECRETS, "Not Configured");
1315
}
16+
17+
@Override
18+
public HttpStatus getHttpStatus() {
19+
return HttpStatus.NOT_IMPLEMENTED;
20+
}
1421
}

projects/control-service/projects/pipelines_control_service/src/main/java/com/vmware/taurus/secrets/controller/DataJobsSecretsController.java

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
package com.vmware.taurus.secrets.controller;
77

88
import com.fasterxml.jackson.core.JsonProcessingException;
9-
import com.vmware.taurus.base.FeatureFlags;
109
import com.vmware.taurus.controlplane.model.api.DataJobsSecretsApi;
1110
import com.vmware.taurus.exception.DataJobSecretsException;
12-
import com.vmware.taurus.exception.SecretStorageNotConfiguredException;
1311
import com.vmware.taurus.secrets.service.JobSecretsService;
1412
import io.swagger.v3.oas.annotations.tags.Tag;
1513
import org.slf4j.Logger;
@@ -30,14 +28,10 @@
3028
public class DataJobsSecretsController implements DataJobsSecretsApi {
3129
static Logger log = LoggerFactory.getLogger(DataJobsSecretsController.class);
3230

33-
private final FeatureFlags featureFlags;
34-
3531
private final JobSecretsService secretsService;
3632

3733
@Autowired
38-
public DataJobsSecretsController(
39-
FeatureFlags featureFlags, @Nullable JobSecretsService secretsService) {
40-
this.featureFlags = featureFlags;
34+
public DataJobsSecretsController(@Nullable JobSecretsService secretsService) {
4135
this.secretsService = secretsService;
4236
}
4337

@@ -46,28 +40,20 @@ public ResponseEntity<Void> dataJobSecretsUpdate(
4640
String teamName, String jobName, String deploymentId, Map<String, Object> requestBody) {
4741
log.debug("Updating secrets for job: {}", jobName);
4842

49-
if (featureFlags.isVaultIntegrationEnabled()) {
50-
secretsService.updateJobSecrets(jobName, requestBody);
51-
return ResponseEntity.noContent().build();
52-
}
53-
54-
throw new SecretStorageNotConfiguredException();
43+
secretsService.updateJobSecrets(jobName, requestBody);
44+
return ResponseEntity.noContent().build();
5545
}
5646

5747
@Override
5848
public ResponseEntity<Map<String, Object>> dataJobSecretsRead(
5949
String teamName, String jobName, String deploymentId) {
6050
log.debug("Reading secrets for job: {}", jobName);
6151

62-
if (featureFlags.isVaultIntegrationEnabled()) {
63-
try {
64-
return ResponseEntity.ok(secretsService.readJobSecrets(jobName));
65-
} catch (JsonProcessingException e) {
66-
log.error("Error while parsing secrets for job: " + jobName, e);
67-
throw new DataJobSecretsException(jobName, "Error while parsing secrets for job");
68-
}
52+
try {
53+
return ResponseEntity.ok(secretsService.readJobSecrets(jobName));
54+
} catch (JsonProcessingException e) {
55+
log.error("Error while parsing secrets for job: " + jobName, e);
56+
throw new DataJobSecretsException(jobName, "Error while parsing secrets for job");
6957
}
70-
71-
throw new SecretStorageNotConfiguredException();
7258
}
7359
}

projects/control-service/projects/pipelines_control_service/src/main/java/com/vmware/taurus/secrets/controller/NoOpDataJobsSecretsController.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
package com.vmware.taurus.secrets.controller;
77

88
import com.vmware.taurus.controlplane.model.api.DataJobsSecretsApi;
9+
import com.vmware.taurus.exception.SecretStorageNotConfiguredException;
910
import io.swagger.v3.oas.annotations.tags.Tag;
1011
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1112
import org.springframework.context.annotation.ComponentScan;
12-
import org.springframework.http.HttpStatus;
1313
import org.springframework.http.ResponseEntity;
1414
import org.springframework.web.bind.annotation.RestController;
15-
import org.springframework.web.server.ResponseStatusException;
1615

1716
import java.util.Map;
1817

@@ -28,14 +27,12 @@ public class NoOpDataJobsSecretsController implements DataJobsSecretsApi {
2827
@Override
2928
public ResponseEntity<Void> dataJobSecretsUpdate(
3029
String teamName, String jobName, String deploymentId, Map<String, Object> requestBody) {
31-
throw new ResponseStatusException(
32-
HttpStatus.NOT_IMPLEMENTED, "Secrets service is not implemented");
30+
throw new SecretStorageNotConfiguredException();
3331
}
3432

3533
@Override
3634
public ResponseEntity<Map<String, Object>> dataJobSecretsRead(
3735
String teamName, String jobName, String deploymentId) {
38-
throw new ResponseStatusException(
39-
HttpStatus.NOT_IMPLEMENTED, "Secrets service is not implemented");
36+
throw new SecretStorageNotConfiguredException();
4037
}
4138
}

projects/control-service/projects/pipelines_control_service/src/main/java/com/vmware/taurus/secrets/service/vault/VaultJobSecretsService.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
import com.fasterxml.jackson.core.JsonProcessingException;
99
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.vmware.taurus.exception.DataJobSecretsException;
11+
import com.vmware.taurus.exception.DataJobSecretsSizeLimitException;
1012
import lombok.extern.slf4j.Slf4j;
1113
import org.json.JSONObject;
14+
import org.springframework.beans.factory.annotation.Value;
1215
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1316
import org.springframework.stereotype.Service;
1417
import org.springframework.vault.core.VaultOperations;
1518
import org.springframework.vault.support.Versioned;
1619

20+
import java.nio.charset.StandardCharsets;
1721
import java.util.Collections;
1822
import java.util.Map;
1923
import java.util.stream.Collectors;
@@ -23,8 +27,12 @@
2327
@ConditionalOnProperty(value = "featureflag.vault.integration.enabled")
2428
public class VaultJobSecretsService implements com.vmware.taurus.secrets.service.JobSecretsService {
2529

30+
private static final int VAULT_SIZE_LIMIT_DEFAULT = 1048576; // 1 MB
2631
private static final String SECRET = "secret";
2732

33+
@Value("${datajobs.vault.size.limit.bytes}")
34+
private int sizeLimitBytes = VAULT_SIZE_LIMIT_DEFAULT;
35+
2836
private final ObjectMapper objectMapper = new ObjectMapper();
2937
private final VaultOperations vaultOperations;
3038

@@ -34,6 +42,10 @@ public VaultJobSecretsService(VaultOperations vaultOperations) {
3442

3543
@Override
3644
public void updateJobSecrets(String jobName, Map<String, Object> secrets) {
45+
46+
checkJobName(jobName);
47+
checkJobSecretsMap(jobName, secrets);
48+
3749
Versioned<VaultJobSecrets> readResponse =
3850
vaultOperations.opsForVersionedKeyValue(SECRET).get(jobName, VaultJobSecrets.class);
3951

@@ -57,13 +69,20 @@ public void updateJobSecrets(String jobName, Map<String, Object> secrets) {
5769
return entry.getValue();
5870
}));
5971

60-
vaultJobSecrets.setSecretsJson(new JSONObject(updatedSecrets).toString());
72+
String updatedSecretsString = new JSONObject(updatedSecrets).toString();
73+
if (updatedSecretsString.getBytes(StandardCharsets.UTF_8).length > sizeLimitBytes) {
74+
throw new DataJobSecretsSizeLimitException(
75+
jobName, "Secret size exceeds configured limit of:" + sizeLimitBytes);
76+
}
77+
78+
vaultJobSecrets.setSecretsJson(updatedSecretsString);
6179

6280
vaultOperations.opsForVersionedKeyValue(SECRET).put(jobName, vaultJobSecrets);
6381
}
6482

6583
@Override
6684
public Map<String, Object> readJobSecrets(String jobName) throws JsonProcessingException {
85+
checkJobName(jobName);
6786
Versioned<VaultJobSecrets> readResponse =
6887
vaultOperations.opsForVersionedKeyValue(SECRET).get(jobName, VaultJobSecrets.class);
6988

@@ -76,4 +95,16 @@ public Map<String, Object> readJobSecrets(String jobName) throws JsonProcessingE
7695
return Collections.emptyMap();
7796
}
7897
}
98+
99+
private void checkJobName(String jobName) {
100+
if (jobName == null || jobName.isBlank()) {
101+
throw new DataJobSecretsException(jobName, "Data Job Name cannot be blank");
102+
}
103+
}
104+
105+
private void checkJobSecretsMap(String jobName, Map<String, Object> secrets) {
106+
if (secrets == null || secrets.isEmpty()) {
107+
throw new DataJobSecretsException(jobName, "Secrets parameter cannot be null or empty");
108+
}
109+
}
79110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2021-2023 VMware, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
/*
6+
* Copyright 2021-2023 VMware, Inc.
7+
* SPDX-License-Identifier: Apache-2.0
8+
*/
9+
10+
package com.vmware.taurus.secrets;
11+
12+
import com.fasterxml.jackson.core.JsonProcessingException;
13+
import com.vmware.taurus.exception.DataJobSecretsException;
14+
import com.vmware.taurus.exception.SecretStorageNotConfiguredException;
15+
import com.vmware.taurus.secrets.controller.DataJobsSecretsController;
16+
import com.vmware.taurus.secrets.controller.NoOpDataJobsSecretsController;
17+
import com.vmware.taurus.secrets.service.JobSecretsService;
18+
import org.junit.jupiter.api.Assertions;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
import org.mockito.InjectMocks;
22+
import org.mockito.Mock;
23+
import org.mockito.junit.jupiter.MockitoExtension;
24+
import org.springframework.http.HttpStatus;
25+
import org.springframework.http.ResponseEntity;
26+
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.mockito.Mockito.*;
32+
33+
@ExtendWith(MockitoExtension.class)
34+
class DataJobsSecretsControllerTest {
35+
36+
@Mock private JobSecretsService jobSecretsService;
37+
38+
@InjectMocks private DataJobsSecretsController controller;
39+
@InjectMocks private NoOpDataJobsSecretsController noOpsController;
40+
41+
@Test
42+
void testDataJobSecretsUpdate() {
43+
String jobName = "testJob";
44+
Map<String, Object> requestBody = new HashMap<>();
45+
46+
ResponseEntity<Void> expectedResponse = ResponseEntity.noContent().build();
47+
48+
doNothing().when(jobSecretsService).updateJobSecrets(jobName, requestBody);
49+
50+
ResponseEntity<Void> actualResponse =
51+
controller.dataJobSecretsUpdate(null, jobName, null, requestBody);
52+
53+
verify(jobSecretsService, times(1)).updateJobSecrets(jobName, requestBody);
54+
55+
assertEquals(expectedResponse.getStatusCode(), actualResponse.getStatusCode());
56+
}
57+
58+
@Test
59+
void testDataJobSecretsRead() throws JsonProcessingException {
60+
String jobName = "testJob";
61+
Map<String, Object> expectedSecrets = new HashMap<>();
62+
63+
when(jobSecretsService.readJobSecrets(jobName)).thenReturn(expectedSecrets);
64+
65+
ResponseEntity<Map<String, Object>> expectedResponse = ResponseEntity.ok(expectedSecrets);
66+
67+
ResponseEntity<Map<String, Object>> actualResponse =
68+
controller.dataJobSecretsRead(null, jobName, null);
69+
70+
verify(jobSecretsService, times(1)).readJobSecrets(jobName);
71+
72+
assertEquals(expectedResponse.getStatusCode(), actualResponse.getStatusCode());
73+
assertEquals(expectedResponse.getBody(), actualResponse.getBody());
74+
}
75+
76+
@Test
77+
void testDataJobSecretsRead_JsonProcessingException() throws JsonProcessingException {
78+
String jobName = "testJob";
79+
80+
when(jobSecretsService.readJobSecrets(jobName)).thenThrow(JsonProcessingException.class);
81+
82+
ResponseEntity<Map<String, Object>> expectedResponse =
83+
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
84+
85+
DataJobSecretsException thrownException =
86+
Assertions.assertThrows(
87+
DataJobSecretsException.class,
88+
() -> controller.dataJobSecretsRead(null, jobName, null));
89+
90+
verify(jobSecretsService, times(1)).readJobSecrets(jobName);
91+
92+
assertEquals(expectedResponse.getStatusCode(), thrownException.getHttpStatus());
93+
}
94+
95+
@Test
96+
void testDataJobSecrets_NotConfigured() throws JsonProcessingException {
97+
String jobName = "testJob";
98+
99+
ResponseEntity<Map<String, Object>> expectedResponse =
100+
ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).body(null);
101+
102+
SecretStorageNotConfiguredException thrownException =
103+
Assertions.assertThrows(
104+
SecretStorageNotConfiguredException.class,
105+
() -> noOpsController.dataJobSecretsRead(null, jobName, null));
106+
107+
assertEquals(expectedResponse.getStatusCode(), thrownException.getHttpStatus());
108+
109+
thrownException =
110+
Assertions.assertThrows(
111+
SecretStorageNotConfiguredException.class,
112+
() -> noOpsController.dataJobSecretsUpdate(null, jobName, null, null));
113+
114+
assertEquals(expectedResponse.getStatusCode(), thrownException.getHttpStatus());
115+
}
116+
}

0 commit comments

Comments
 (0)