diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 40f50cc..01c5b17 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,9 @@ jobs: - name: Check code format run: ./gradlew spotlessCheck + - name: Lint + run: make lint + - name: Test run: make test diff --git a/Makefile b/Makefile index 945cf42..686a63b 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ fmt: @./gradlew spotlessApply lint: - @echo "linting not ready yet" + @./gradlew pmdMain test: @./gradlew test diff --git a/build.gradle b/build.gradle index f4d6b3f..07e2e25 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'signing' id 'idea' id 'eclipse' + id 'pmd' id 'com.diffplug.spotless' version '6.21.0' @@ -13,6 +14,7 @@ plugins { allprojects { apply plugin: 'com.diffplug.spotless' + apply plugin: 'pmd' repositories { mavenCentral() @@ -64,6 +66,17 @@ allprojects { endWithNewline() } } + + pmd { + consoleOutput = true + toolVersion = "7.12.0" + + // This tells PMD to use your custom ruleset file. + ruleSetFiles = rootProject.files("config/pmd/pmd-ruleset.xml") + + // This is important: it prevents PMD from using its default rules. + ruleSets = [] + } } def configureMavenCentralPublishing(Project project) { diff --git a/config/pmd/pmd-ruleset.xml b/config/pmd/pmd-ruleset.xml new file mode 100644 index 0000000..7c1187b --- /dev/null +++ b/config/pmd/pmd-ruleset.xml @@ -0,0 +1,68 @@ + + + + + Custom ruleset that excludes generated code from PMD analysis. + + + .*/cloud/stackit/sdk/.*/model/.* + .*/cloud/stackit/sdk/.*/api/.* + .*/cloud/stackit/sdk/.*/ApiCallback.java + .*/cloud/stackit/sdk/.*/ApiClient.java + .*/cloud/stackit/sdk/.*/ApiResponse.java + .*/cloud/stackit/sdk/.*/GzipRequestInterceptor.java + .*/cloud/stackit/sdk/.*/JSON.java + .*/cloud/stackit/sdk/.*/Pair.java + .*/cloud/stackit/sdk/.*/ProgressRequestBody.java + .*/cloud/stackit/sdk/.*/ProgressResponseBody.java + .*/cloud/stackit/sdk/.*/ServerConfiguration.java + .*/cloud/stackit/sdk/.*/ServerVariable.java + .*/cloud/stackit/sdk/.*/StringUtil.java + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java b/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java index d321ed3..3315067 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java +++ b/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java @@ -19,14 +19,16 @@ import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.util.Date; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import okhttp3.*; import org.jetbrains.annotations.NotNull; -/* KeyFlowAuthenticator handles the Key Flow Authentication based on the Service Account Key. */ +/* + * KeyFlowAuthenticator handles the Key Flow Authentication based on the Service Account Key. + */ public class KeyFlowAuthenticator implements Authenticator { private static final String REFRESH_TOKEN = "refresh_token"; private static final String ASSERTION = "assertion"; @@ -44,6 +46,8 @@ public class KeyFlowAuthenticator implements Authenticator { private final String tokenUrl; private long tokenLeewayInSeconds = DEFAULT_TOKEN_LEEWAY; + private final Object tokenRefreshMonitor = new Object(); + /** * Creates the initial service account and refreshes expired access token. * @@ -128,7 +132,7 @@ public Request authenticate(Route route, @NotNull Response response) throws IOEx try { accessToken = getAccessToken(); } catch (ApiException | InvalidKeySpecException e) { - throw new RuntimeException(e); + throw new IllegalStateException(e); } // Return a new request with the refreshed token @@ -140,19 +144,19 @@ public Request authenticate(Route route, @NotNull Response response) throws IOEx protected static class KeyFlowTokenResponse { @SerializedName("access_token") - private String accessToken; + private final String accessToken; @SerializedName("refresh_token") - private String refreshToken; + private final String refreshToken; @SerializedName("expires_in") private long expiresIn; @SerializedName("scope") - private String scope; + private final String scope; @SerializedName("token_type") - private String tokenType; + private final String tokenType; public KeyFlowTokenResponse( String accessToken, @@ -184,14 +188,16 @@ protected String getAccessToken() { * @throws IOException request for new access token failed * @throws ApiException response for new access token with bad status code */ - public synchronized String getAccessToken() - throws IOException, ApiException, InvalidKeySpecException { - if (token == null) { - createAccessToken(); - } else if (token.isExpired()) { - createAccessTokenWithRefreshToken(); + @SuppressWarnings("PMD.AvoidSynchronizedStatement") + public String getAccessToken() throws IOException, ApiException, InvalidKeySpecException { + synchronized (tokenRefreshMonitor) { + if (token == null) { + createAccessToken(); + } else if (token.isExpired()) { + createAccessTokenWithRefreshToken(); + } + return token.getAccessToken(); } - return token.getAccessToken(); } /** @@ -202,20 +208,23 @@ public synchronized String getAccessToken() * @throws ApiException response for new access token with bad status code * @throws JsonSyntaxException parsing of the created access token failed */ - protected void createAccessToken() - throws InvalidKeySpecException, IOException, JsonSyntaxException, ApiException { - String grant = "urn:ietf:params:oauth:grant-type:jwt-bearer"; - String assertion; - try { - assertion = generateSelfSignedJWT(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException( - "could not find required algorithm for jwt signing. This should not happen and should be reported on https://github.com/stackitcloud/stackit-sdk-java/issues", - e); + @SuppressWarnings("PMD.AvoidSynchronizedStatement") + protected void createAccessToken() throws InvalidKeySpecException, IOException, ApiException { + synchronized (tokenRefreshMonitor) { + String assertion; + try { + assertion = generateSelfSignedJWT(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException( + "could not find required algorithm for jwt signing. This should not happen and should be reported on https://github.com/stackitcloud/stackit-sdk-java/issues", + e); + } + + String grant = "urn:ietf:params:oauth:grant-type:jwt-bearer"; + try (Response response = requestToken(grant, assertion).execute()) { + parseTokenResponse(response); + } } - Response response = requestToken(grant, assertion).execute(); - parseTokenResponse(response); - response.close(); } /** @@ -225,16 +234,24 @@ protected void createAccessToken() * @throws ApiException response for new access token with bad status code * @throws JsonSyntaxException can not parse new access token */ - protected synchronized void createAccessTokenWithRefreshToken() - throws IOException, JsonSyntaxException, ApiException { - String refreshToken = token.refreshToken; - Response response = requestToken(REFRESH_TOKEN, refreshToken).execute(); - parseTokenResponse(response); - response.close(); + @SuppressWarnings("PMD.AvoidSynchronizedStatement") + protected void createAccessTokenWithRefreshToken() throws IOException, ApiException { + synchronized (tokenRefreshMonitor) { + String refreshToken = token.refreshToken; + try (Response response = requestToken(REFRESH_TOKEN, refreshToken).execute()) { + parseTokenResponse(response); + } + } } - private synchronized void parseTokenResponse(Response response) - throws ApiException, JsonSyntaxException, IOException { + /** + * Parses the token response from the server + * + * @param response HTTP response containing the token + * @throws ApiException if the response has a bad status code + * @throws JsonSyntaxException if the response body cannot be parsed + */ + private void parseTokenResponse(Response response) throws ApiException { if (response.code() != HttpURLConnection.HTTP_OK) { String body = null; if (response.body() != null) { @@ -256,10 +273,10 @@ private synchronized void parseTokenResponse(Response response) response.body().close(); } - private Call requestToken(String grant, String assertionValue) throws IOException { + private Call requestToken(String grant, String assertionValue) { FormBody.Builder bodyBuilder = new FormBody.Builder(); bodyBuilder.addEncoded("grant_type", grant); - String assertionKey = grant.equals(REFRESH_TOKEN) ? REFRESH_TOKEN : ASSERTION; + String assertionKey = REFRESH_TOKEN.equals(grant) ? REFRESH_TOKEN : ASSERTION; bodyBuilder.addEncoded(assertionKey, assertionValue); FormBody body = bodyBuilder.build(); @@ -289,7 +306,7 @@ private String generateSelfSignedJWT() prvKey = saKey.getCredentials().getPrivateKeyParsed(); Algorithm algorithm = Algorithm.RSA512(prvKey); - Map jwtHeader = new HashMap<>(); + Map jwtHeader = new ConcurrentHashMap<>(); jwtHeader.put("kid", saKey.getCredentials().getKid()); return JWT.create() diff --git a/core/src/main/java/cloud/stackit/sdk/core/KeyFlowInterceptor.java b/core/src/main/java/cloud/stackit/sdk/core/KeyFlowInterceptor.java index d310489..63461e9 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/KeyFlowInterceptor.java +++ b/core/src/main/java/cloud/stackit/sdk/core/KeyFlowInterceptor.java @@ -37,7 +37,7 @@ public Response intercept(Chain chain) throws IOException { } catch (InvalidKeySpecException | ApiException e) { // try-catch required, because ApiException can not be thrown in the implementation // of Interceptor.intercept(Chain chain) - throw new RuntimeException(e); + throw new IllegalStateException(e); } Request authenticatedRequest = diff --git a/core/src/main/java/cloud/stackit/sdk/core/auth/SetupAuth.java b/core/src/main/java/cloud/stackit/sdk/core/auth/SetupAuth.java index b79c08d..0dec702 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/auth/SetupAuth.java +++ b/core/src/main/java/cloud/stackit/sdk/core/auth/SetupAuth.java @@ -6,6 +6,7 @@ import cloud.stackit.sdk.core.config.EnvironmentVariables; import cloud.stackit.sdk.core.exception.CredentialsInFileNotFoundException; import cloud.stackit.sdk.core.exception.PrivateKeyNotFoundException; +import cloud.stackit.sdk.core.model.ServiceAccountCredentials; import cloud.stackit.sdk.core.model.ServiceAccountKey; import cloud.stackit.sdk.core.utils.Utils; import com.google.gson.Gson; @@ -40,9 +41,12 @@ public class SetupAuth { * let it handle the rest. Will be removed in April 2026. */ @Deprecated + public SetupAuth() { + // deprecated + } + // TODO: constructor of SetupAuth should be private after deprecated constructors/methods are // removed (only static methods should remain) - public SetupAuth() {} /** * Set up the KeyFlow Authentication and can be integrated in an OkHttp client, by adding @@ -61,7 +65,8 @@ public SetupAuth(CoreConfiguration cfg) { } /* - * @deprecated Use static methods of SetupAuth instead or just use the KeyFlowAuthenticator and let it handle the rest. Will be removed in April 2026. + * @deprecated Use static methods of SetupAuth instead or just use the KeyFlowAuthenticator + * and let it handle the rest. Will be removed in April 2026. */ @Deprecated public void init() throws IOException { @@ -70,12 +75,13 @@ public void init() throws IOException { } /* - * @deprecated Use static methods of SetupAuth instead or just use the KeyFlowAuthenticator and let it handle the rest. Will be removed in April 2026. + * @deprecated Use static methods of SetupAuth instead or just use the KeyFlowAuthenticator + * and let it handle the rest. Will be removed in April 2026. */ @Deprecated public Interceptor getAuthHandler() { if (authHandler == null) { - throw new RuntimeException("init() has to be called first"); + throw new IllegalStateException("init() has to be called first"); } return authHandler; } @@ -120,13 +126,22 @@ private static String getDefaultCredentialsFilePath() { * can be found * @throws IOException thrown when a file can not be found */ - public static ServiceAccountKey setupKeyFlow(CoreConfiguration cfg) - throws CredentialsInFileNotFoundException, IOException { + public static ServiceAccountKey setupKeyFlow(CoreConfiguration cfg) throws IOException { return setupKeyFlow(cfg, new EnvironmentVariables()); } + /** + * Sets up the KeyFlow Authentication + * + * @param cfg Configuration + * @param env Environment variables + * @return Service account key + * @throws CredentialsInFileNotFoundException thrown when no service account key or private key + * can be found + * @throws IOException thrown when a file can not be found + */ protected static ServiceAccountKey setupKeyFlow(CoreConfiguration cfg, EnvironmentVariables env) - throws CredentialsInFileNotFoundException, IOException { + throws IOException { // Explicit config in code if (Utils.isStringSet(cfg.getServiceAccountKey())) { ServiceAccountKey saKey = ServiceAccountKey.loadFromJson(cfg.getServiceAccountKey()); @@ -179,14 +194,22 @@ protected static ServiceAccountKey setupKeyFlow(CoreConfiguration cfg, Environme return saKey; } + /** + * Loads the private key into the service account key + * + * @param cfg Configuration + * @param env Environment variables + * @param saKey Service account key + * @throws PrivateKeyNotFoundException if the private key could not be found + */ protected static void loadPrivateKey( - CoreConfiguration cfg, EnvironmentVariables env, ServiceAccountKey saKey) - throws PrivateKeyNotFoundException { - if (!saKey.getCredentials().isPrivateKeySet()) { + CoreConfiguration cfg, EnvironmentVariables env, ServiceAccountKey saKey) { + ServiceAccountCredentials credentials = saKey.getCredentials(); + if (!credentials.isPrivateKeySet()) { try { String privateKey = getPrivateKey(cfg, env); - saKey.getCredentials().setPrivateKey(privateKey); - } catch (Exception e) { + credentials.setPrivateKey(privateKey); + } catch (CredentialsInFileNotFoundException | IOException e) { throw new PrivateKeyNotFoundException("could not find private key", e); } } @@ -216,13 +239,14 @@ protected static void loadPrivateKey( * * * @param cfg + * @param env * @return found private key * @throws CredentialsInFileNotFoundException throws if no private key could be found * @throws IOException throws if the provided path can not be found or the file within the * pathKey can not be found */ private static String getPrivateKey(CoreConfiguration cfg, EnvironmentVariables env) - throws CredentialsInFileNotFoundException, IOException { + throws IOException { // Explicit code config // Get private key if (Utils.isStringSet(cfg.getPrivateKey())) { @@ -279,8 +303,7 @@ private static String getPrivateKey(CoreConfiguration cfg, EnvironmentVariables * pathKey can not be found */ protected static String readValueFromCredentialsFile( - String path, String valueKey, String pathKey) - throws IOException, CredentialsInFileNotFoundException { + String path, String valueKey, String pathKey) throws IOException { // Read credentials file String fileContent = new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8); diff --git a/core/src/main/java/cloud/stackit/sdk/core/config/CoreConfiguration.java b/core/src/main/java/cloud/stackit/sdk/core/config/CoreConfiguration.java index caa5ed3..6debb6d 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/config/CoreConfiguration.java +++ b/core/src/main/java/cloud/stackit/sdk/core/config/CoreConfiguration.java @@ -13,8 +13,6 @@ public class CoreConfiguration { private String tokenCustomUrl; private Long tokenExpirationLeeway; - public CoreConfiguration() {} - public Map getDefaultHeader() { return defaultHeader; } diff --git a/core/src/main/java/cloud/stackit/sdk/core/exception/ApiException.java b/core/src/main/java/cloud/stackit/sdk/core/exception/ApiException.java index d7d6210..26779f3 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/exception/ApiException.java +++ b/core/src/main/java/cloud/stackit/sdk/core/exception/ApiException.java @@ -5,14 +5,16 @@ /** ApiException class. */ public class ApiException extends Exception { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 8115526329759018011L; - private int code = 0; - private Map> responseHeaders = null; - private String responseBody = null; + private int code; + private Map> responseHeaders; + private String responseBody; /** Constructor for ApiException. */ - public ApiException() {} + public ApiException() { + super(); + } /** * Constructor for ApiException. @@ -162,6 +164,7 @@ public String getResponseBody() { * * @return The exception message */ + @Override public String getMessage() { return String.format( "Message: %s%nHTTP response code: %s%nHTTP response body: %s%nHTTP response headers: %s", diff --git a/core/src/main/java/cloud/stackit/sdk/core/exception/CredentialsInFileNotFoundException.java b/core/src/main/java/cloud/stackit/sdk/core/exception/CredentialsInFileNotFoundException.java index 052fc01..7efd80f 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/exception/CredentialsInFileNotFoundException.java +++ b/core/src/main/java/cloud/stackit/sdk/core/exception/CredentialsInFileNotFoundException.java @@ -1,6 +1,7 @@ package cloud.stackit.sdk.core.exception; public class CredentialsInFileNotFoundException extends RuntimeException { + private static final long serialVersionUID = -3290974267932615412L; public CredentialsInFileNotFoundException(String msg) { super(msg); diff --git a/core/src/main/java/cloud/stackit/sdk/core/exception/PrivateKeyNotFoundException.java b/core/src/main/java/cloud/stackit/sdk/core/exception/PrivateKeyNotFoundException.java index 365ea8e..e31688e 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/exception/PrivateKeyNotFoundException.java +++ b/core/src/main/java/cloud/stackit/sdk/core/exception/PrivateKeyNotFoundException.java @@ -1,6 +1,7 @@ package cloud.stackit.sdk.core.exception; public class PrivateKeyNotFoundException extends RuntimeException { + private static final long serialVersionUID = -81419539524374575L; public PrivateKeyNotFoundException(String msg) { super(msg); diff --git a/core/src/main/java/cloud/stackit/sdk/core/utils/TestUtils.java b/core/src/main/java/cloud/stackit/sdk/core/utils/TestUtils.java index a027a5b..c4a648c 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/utils/TestUtils.java +++ b/core/src/main/java/cloud/stackit/sdk/core/utils/TestUtils.java @@ -1,6 +1,9 @@ package cloud.stackit.sdk.core.utils; +@SuppressWarnings("PMD.TestClassWithoutTestCases") public class TestUtils { public static final String MOCK_SERVICE_ACCOUNT_KEY = "{\"id\":\"id\",\"publicKey\":\"publicKey\",\"createdAt\":\"2025-03-26T15:08:45.915+00:00\",\"keyType\":\"keyType\",\"keyOrigin\":\"keyOrigin\",\"keyAlgorithm\":\"keyAlgo\",\"active\":true,\"validUntil\":\"2025-03-26T15:08:45.915+00:00\",\"credentials\":{\"aud\":\"aud\",\"iss\":\"iss\",\"kid\":\"kid\",\"privateKey\":\"privateKey\",\"sub\":\"sub\"}}\n"; + + public static final String MOCK_SERVICE_ACCOUNT_PRIVATE_KEY = "privateKey"; } diff --git a/core/src/main/java/cloud/stackit/sdk/core/utils/Utils.java b/core/src/main/java/cloud/stackit/sdk/core/utils/Utils.java index 5a88097..63e9afe 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/utils/Utils.java +++ b/core/src/main/java/cloud/stackit/sdk/core/utils/Utils.java @@ -1,7 +1,31 @@ package cloud.stackit.sdk.core.utils; public final class Utils { - public static boolean isStringSet(String input) { - return input != null && !input.trim().isEmpty(); + private Utils() {} + + /* + * Assert a string is not null and not empty + * + * @param input The string to check + * @return check result + * */ + public static boolean isStringSet(final String input) { + return input != null && !checkTrimEmpty(input); + } + + /* + * Assert a string is not empty. Helper method because String.trim().length() == 0 + * / String.trim().isEmpty() is an inefficient way to validate a blank String. + * + * @param input The string to check + * @return check result + * */ + private static boolean checkTrimEmpty(String input) { + for (int i = 0; i < input.length(); i++) { + if (!Character.isWhitespace(input.charAt(i))) { + return false; + } + } + return true; } } diff --git a/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java b/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java index dac8362..7f763ce 100644 --- a/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java +++ b/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java @@ -22,12 +22,16 @@ import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +@SuppressWarnings("PMD.TooManyMethods") class KeyFlowAuthenticatorTest { - private static MockWebServer mockWebServer; + private MockWebServer mockWebServer; private ServiceAccountKey defaultSaKey; private OkHttpClient httpClient; + + private static final String MOCK_WEBSERVER_PATH = "/token"; private static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0jVPq7ACbkwW6\n" @@ -58,7 +62,7 @@ class KeyFlowAuthenticatorTest { + "h/9afEtu5aUE/m+1vGBoH8z1\n" + "-----END PRIVATE KEY-----\n"; - ServiceAccountKey createDummyServiceAccount() { + private ServiceAccountKey createDummyServiceAccount() { ServiceAccountCredentials credentials = new ServiceAccountCredentials("aud", "iss", "kid", PRIVATE_KEY, "sub"); return new ServiceAccountKey( @@ -75,7 +79,7 @@ ServiceAccountKey createDummyServiceAccount() { credentials); } - KeyFlowAuthenticator.KeyFlowTokenResponse mockResponseBody(boolean expired) + private KeyFlowAuthenticator.KeyFlowTokenResponse mockResponseBody(boolean expired) throws NoSuchAlgorithmException, InvalidKeySpecException { Date issuedAt = new Date(); Date expiredAt = Date.from(new Date().toInstant().plusSeconds(60 * 10)); @@ -106,7 +110,8 @@ void tearDown() throws IOException { } @Test - void getAccessToken_response200_noException() + @DisplayName("get access token - Response 200 - No exception") + void testGetAccessTokenResponse200NoException() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, ApiException { // Setup mockServer @@ -117,7 +122,7 @@ void getAccessToken_response200_noException() mockWebServer.enqueue(mockedResponse); // Config - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); CoreConfiguration cfg = new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer @@ -129,7 +134,8 @@ void getAccessToken_response200_noException() } @Test - void getAccessToken_expiredToken_noException() + @DisplayName("get access token - expired token - no exception") + void testGetAccessTokenExpiredTokenNoException() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, ApiException { // Setup expiredToken and newToken KeyFlowAuthenticator.KeyFlowTokenResponse expiredKey = mockResponseBody(true); @@ -143,7 +149,7 @@ void getAccessToken_expiredToken_noException() mockWebServer.enqueue(mockedResponse); // Config - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); CoreConfiguration cfg = new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer @@ -155,11 +161,12 @@ void getAccessToken_expiredToken_noException() } @Test - void createAccessToken_response200WithEmptyBody_throwsException() { + @DisplayName("create access token - response 200 with empty body - throws exception") + void createAccessTokenResponse200WithEmptyBodyThrowsException() { // Setup mockServer MockResponse mockedResponse = new MockResponse().setResponseCode(200); mockWebServer.enqueue(mockedResponse); - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); // Config CoreConfiguration cfg = @@ -173,11 +180,12 @@ void createAccessToken_response200WithEmptyBody_throwsException() { } @Test - void createAccessToken_response400_throwsApiException() { + @DisplayName("create access token - response 400 - throws ApiException") + void createAccessTokenResponse400ThrowsApiException() { // Setup mockServer MockResponse mockedResponse = new MockResponse().setResponseCode(400); mockWebServer.enqueue(mockedResponse); - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); // Config CoreConfiguration cfg = @@ -191,7 +199,8 @@ void createAccessToken_response400_throwsApiException() { } @Test - void createAccessToken_response200WithValidResponse_noException() + @DisplayName("create access token - response 200 with valid response - no exception") + void createAccessTokenResponse200WithValidResponseNoException() throws NoSuchAlgorithmException, InvalidKeySpecException { // Setup mockServer KeyFlowAuthenticator.KeyFlowTokenResponse responseBody = mockResponseBody(false); @@ -201,7 +210,7 @@ void createAccessToken_response200WithValidResponse_noException() mockWebServer.enqueue(mockedResponse); // Config - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); CoreConfiguration cfg = new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer @@ -213,7 +222,9 @@ void createAccessToken_response200WithValidResponse_noException() } @Test - void createAccessTokenWithRefreshToken_response200WithValidResponse_noException() + @DisplayName( + "create access token with refresh token - response 200 with valid response - no exception") + void createAccessTokenWithRefreshTokenResponse200WithValidResponseNoException() throws NoSuchAlgorithmException, InvalidKeySpecException { // Setup mockServer KeyFlowAuthenticator.KeyFlowTokenResponse mockedBody = mockResponseBody(false); @@ -223,7 +234,7 @@ void createAccessTokenWithRefreshToken_response200WithValidResponse_noException( mockWebServer.enqueue(mockedResponse); // Config - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); CoreConfiguration cfg = new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer @@ -236,13 +247,15 @@ void createAccessTokenWithRefreshToken_response200WithValidResponse_noException( } @Test - void createAccessTokenWithRefreshToken_response200WithEmptyBody_throwsException() + @DisplayName( + "create access token with refresh token - response 200 with empty body - throws exception") + void createAccessTokenWithRefreshTokenResponse200WithEmptyBodyThrowsException() throws NoSuchAlgorithmException, InvalidKeySpecException { // Setup mockServer KeyFlowAuthenticator.KeyFlowTokenResponse mockResponse = mockResponseBody(false); MockResponse mockedResponse = new MockResponse().setResponseCode(200); mockWebServer.enqueue(mockedResponse); - HttpUrl url = mockWebServer.url("/token"); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); // Config CoreConfiguration cfg = diff --git a/core/src/test/java/cloud/stackit/sdk/core/KeyFlowInterceptorTest.java b/core/src/test/java/cloud/stackit/sdk/core/KeyFlowInterceptorTest.java index b6f4b39..240ee40 100644 --- a/core/src/test/java/cloud/stackit/sdk/core/KeyFlowInterceptorTest.java +++ b/core/src/test/java/cloud/stackit/sdk/core/KeyFlowInterceptorTest.java @@ -13,6 +13,7 @@ import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -47,7 +48,8 @@ void teardown() throws IOException { } @Test - void intercept_addsAuthHeader() + @DisplayName("intercept adds auth header") + void interceptAddsAuthHeader() throws IOException, InvalidKeySpecException, ApiException, InterruptedException { final String accessToken = "my-access-token"; when(authenticator.getAccessToken()).thenReturn(accessToken); diff --git a/core/src/test/java/cloud/stackit/sdk/core/auth/SetupAuthTest.java b/core/src/test/java/cloud/stackit/sdk/core/auth/SetupAuthTest.java index 7d6871a..3db4186 100644 --- a/core/src/test/java/cloud/stackit/sdk/core/auth/SetupAuthTest.java +++ b/core/src/test/java/cloud/stackit/sdk/core/auth/SetupAuthTest.java @@ -9,6 +9,7 @@ import cloud.stackit.sdk.core.exception.PrivateKeyNotFoundException; import cloud.stackit.sdk.core.model.ServiceAccountCredentials; import cloud.stackit.sdk.core.model.ServiceAccountKey; +import cloud.stackit.sdk.core.utils.TestUtils; import com.google.gson.Gson; import java.io.File; import java.io.IOException; @@ -18,16 +19,21 @@ import java.security.spec.InvalidKeySpecException; import java.time.temporal.ChronoUnit; import java.util.Date; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javax.swing.filechooser.FileSystemView; +import okhttp3.Interceptor; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +@SuppressWarnings("PMD.TooManyMethods") @ExtendWith(MockitoExtension.class) class SetupAuthTest { + private static final String JSON_FILE_EXTENSION = ".json"; + @Mock private EnvironmentVariables envs; private final String invalidCredentialsFilePath = FileSystemView.getFileSystemView().getHomeDirectory() @@ -38,7 +44,7 @@ class SetupAuthTest { + File.separator + "file.json"; - ServiceAccountKey createDummyServiceAccount(String privateKey) { + private ServiceAccountKey createDummyServiceAccount(String privateKey) { ServiceAccountCredentials credentials = new ServiceAccountCredentials("aud", "iss", "kid", privateKey, "sub"); return new ServiceAccountKey( @@ -55,9 +61,9 @@ ServiceAccountKey createDummyServiceAccount(String privateKey) { credentials); } - Path createJsonFile(Map content) throws IOException { + private Path createJsonFile(Map content) throws IOException { String contentJson = new Gson().toJson(content); - Path file = Files.createTempFile("credentials", ".json"); + Path file = Files.createTempFile("credentials", JSON_FILE_EXTENSION); file.toFile().deleteOnExit(); Files.write(file, contentJson.getBytes(StandardCharsets.UTF_8)); @@ -65,12 +71,34 @@ Path createJsonFile(Map content) throws IOException { } @Test - void setupKeyFlow_readServiceAccountFromPath() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("get access token - without running init - throws exception") + void testGetAccessTokenWithoutRunningInitThrowsException() throws IOException { + SetupAuth setupAuth = new SetupAuth(); + assertThrows(RuntimeException.class, setupAuth::getAuthHandler); + } + + @Test + @DisplayName("get access token - with running init - returns interceptor") + void testGetAccessTokenWithRunningInitReturnsInterceptor() throws IOException { + ServiceAccountKey saKey = + createDummyServiceAccount(TestUtils.MOCK_SERVICE_ACCOUNT_PRIVATE_KEY); + String initSaKeyJson = new Gson().toJson(saKey); + + CoreConfiguration config = new CoreConfiguration().serviceAccountKey(initSaKeyJson); + + SetupAuth setupAuth = new SetupAuth(config); + setupAuth.init(); + assertInstanceOf(Interceptor.class, setupAuth.getAuthHandler()); + } + + @Test + @DisplayName("setup key flow - read service account from path") + void setupKeyFlowReadServiceAccountFromPath() throws IOException { // Create service account key file - ServiceAccountKey initSaKey = createDummyServiceAccount("privateKey"); + ServiceAccountKey initSaKey = + createDummyServiceAccount(TestUtils.MOCK_SERVICE_ACCOUNT_PRIVATE_KEY); String initSaKeyJson = new Gson().toJson(initSaKey); - Path saKeyPath = Files.createTempFile("serviceAccountKey", ".json"); + Path saKeyPath = Files.createTempFile("serviceAccountKey", JSON_FILE_EXTENSION); saKeyPath.toFile().deleteOnExit(); Files.write(saKeyPath, initSaKeyJson.getBytes(StandardCharsets.UTF_8)); @@ -84,10 +112,11 @@ void setupKeyFlow_readServiceAccountFromPath() } @Test - void setupKeyFlow_readServiceAccountFromConfig() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("setup key flow - read service account from config") + void setupKeyFlowReadServiceAccountFromConfig() throws IOException { // Create service account key - ServiceAccountKey initSaKey = createDummyServiceAccount("privateKey"); + ServiceAccountKey initSaKey = + createDummyServiceAccount(TestUtils.MOCK_SERVICE_ACCOUNT_PRIVATE_KEY); String initSaKeyJson = new Gson().toJson(initSaKey); // Create config and read setup auth with the previous created saKey @@ -98,9 +127,11 @@ void setupKeyFlow_readServiceAccountFromConfig() } @Test - void setupKeyFlow_readServiceAccountFromKeyEnv() throws IOException { + @DisplayName("setup key flow - read service account from key env") + void setupKeyFlowReadServiceAccountFromKeyEnv() throws IOException { // Create service account key - ServiceAccountKey initSaKey = createDummyServiceAccount("privateKey"); + ServiceAccountKey initSaKey = + createDummyServiceAccount(TestUtils.MOCK_SERVICE_ACCOUNT_PRIVATE_KEY); String initSaKeyJson = new Gson().toJson(initSaKey); // Mock env STACKIT_SERVICE_ACCOUNT_KEY @@ -114,13 +145,14 @@ void setupKeyFlow_readServiceAccountFromKeyEnv() throws IOException { } @Test - void setupKeyFlow_readServiceAccountFromKeyPathEnv() throws IOException { + @DisplayName("setup key flow - read service account from key path env") + void setupKeyFlowReadServiceAccountFromKeyPathEnv() throws IOException { // Create service account key ServiceAccountKey initSaKey = createDummyServiceAccount("privateKey"); String keyPathContent = new Gson().toJson(initSaKey); // Create dummy keyPathFile - Path keyPathFile = Files.createTempFile("serviceAccountKey", ".json"); + Path keyPathFile = Files.createTempFile("serviceAccountKey", JSON_FILE_EXTENSION); keyPathFile.toFile().deleteOnExit(); Files.write(keyPathFile, keyPathContent.getBytes(StandardCharsets.UTF_8)); @@ -136,12 +168,14 @@ void setupKeyFlow_readServiceAccountFromKeyPathEnv() throws IOException { } @Test - void setupKeyFlow_readServiceAccountFromPathWithoutPrivateKey_throwsException() + @DisplayName( + "setup key flow - read service account from path without private key - throws exception") + void setupKeyFlowReadServiceAccountFromPathWithoutPrivateKeyThrowsException() throws IOException, InvalidKeySpecException, ApiException { // Create service account key file ServiceAccountKey initSaKey = createDummyServiceAccount(null); String initSaKeyJson = new Gson().toJson(initSaKey); - Path saKeyPath = Files.createTempFile("serviceAccountKey", ".json"); + Path saKeyPath = Files.createTempFile("serviceAccountKey", JSON_FILE_EXTENSION); saKeyPath.toFile().deleteOnExit(); Files.write(saKeyPath, initSaKeyJson.getBytes(StandardCharsets.UTF_8)); @@ -157,8 +191,9 @@ void setupKeyFlow_readServiceAccountFromPathWithoutPrivateKey_throwsException() } @Test - void setupKeyFlow_readServiceAccountFromConfigWithoutPrivateKey_throwsException() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName( + "setup key flow - read service account from config without private key - throws exception") + void setupKeyFlowReadServiceAccountFromConfigWithoutPrivateKeyThrowsException() { // Create service account key ServiceAccountKey initSaKey = createDummyServiceAccount(null); String initSaKeyJson = new Gson().toJson(initSaKey); @@ -175,8 +210,8 @@ void setupKeyFlow_readServiceAccountFromConfigWithoutPrivateKey_throwsException( } @Test - void loadPrivateKey_setPrivateKeyFromConfig() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("load private key - set private key from config") + void loadPrivateKeySetPrivateKeyFromConfig() { final String prvKey = "prvKey"; ServiceAccountKey saKey = createDummyServiceAccount(null); @@ -188,8 +223,8 @@ void loadPrivateKey_setPrivateKeyFromConfig() } @Test - void loadPrivateKey_doesNotOverwriteExistingPrivateKey() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("load private key - does not overwrite existing private key") + void loadPrivateKeyDoesNotOverwriteExistingPrivateKey() { final String initialPrivateKey = "prvKey"; final String cfgPrivateKey = "prvKey-updated"; @@ -203,8 +238,8 @@ void loadPrivateKey_doesNotOverwriteExistingPrivateKey() } @Test - void loadPrivateKey_setPrivateKeyPath() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("load private key - set private key path") + void loadPrivateKeySetPrivateKeyPath() throws IOException { Path tempPrvKeyFile = Files.createTempFile("privateKey", ".pem"); tempPrvKeyFile.toFile().deleteOnExit(); @@ -222,8 +257,8 @@ void loadPrivateKey_setPrivateKeyPath() } @Test - void loadPrivateKey_setPrivateKeyPathViaCredentialsFile() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("load private key - set private key path via credentials file") + void loadPrivateKeySetPrivateKeyPathViaCredentialsFile() throws IOException { // Create privateKeyFile Path tempPrvKeyFile = Files.createTempFile("privateKey", ".pem"); tempPrvKeyFile.toFile().deleteOnExit(); @@ -233,10 +268,10 @@ void loadPrivateKey_setPrivateKeyPathViaCredentialsFile() Files.write(tempPrvKeyFile, privateKeyContent.getBytes(StandardCharsets.UTF_8)); // Create credentialsFile - Path tempCredentialsFile = Files.createTempFile("credentialsFile", ".json"); + Path tempCredentialsFile = Files.createTempFile("credentialsFile", JSON_FILE_EXTENSION); tempCredentialsFile.toFile().deleteOnExit(); - Map credFileContent = new HashMap<>(); + Map credFileContent = new ConcurrentHashMap<>(); credFileContent.put( EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY_PATH, tempPrvKeyFile.toAbsolutePath().toString()); @@ -257,16 +292,16 @@ void loadPrivateKey_setPrivateKeyPathViaCredentialsFile() } @Test - void loadPrivateKey_setPrivateKeyViaCredentialsFile() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("load private key - set private key via credentials file") + void loadPrivateKeySetPrivateKeyViaCredentialsFile() throws IOException { final String privateKeyContent = ""; // Create credentialsFile - Path tempCredentialsFile = Files.createTempFile("credentialsFile", ".json"); + Path tempCredentialsFile = Files.createTempFile("credentialsFile", JSON_FILE_EXTENSION); tempCredentialsFile.toFile().deleteOnExit(); // Create dummy credentialsFile - Map credFileContent = new HashMap<>(); + Map credFileContent = new ConcurrentHashMap<>(); credFileContent.put(EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY, privateKeyContent); String credFileContentJson = new Gson().toJson(credFileContent); @@ -285,7 +320,8 @@ void loadPrivateKey_setPrivateKeyViaCredentialsFile() } @Test - void loadPrivateKey_setPrivateKeyViaEnv() throws IOException { + @DisplayName("load private key - set private key via env") + void loadPrivateKeySetPrivateKeyViaEnv() { final String prvKey = "prvKey"; ServiceAccountKey saKey = createDummyServiceAccount(null); when(envs.getStackitPrivateKey()).thenReturn(prvKey); @@ -298,7 +334,8 @@ void loadPrivateKey_setPrivateKeyViaEnv() throws IOException { } @Test - void loadPrivateKey_setPrivateKeyPathViaEnv() throws IOException { + @DisplayName("load private key - set private key path via env") + void loadPrivateKeySetPrivateKeyPathViaEnv() throws IOException { final String prvKey = "prvKey"; ServiceAccountKey saKey = createDummyServiceAccount(null); Path tempPrvKeyFile = Files.createTempFile("privateKey", ".pem"); @@ -316,16 +353,16 @@ void loadPrivateKey_setPrivateKeyPathViaEnv() throws IOException { } @Test - void loadPrivateKey_setPrivateKeyViaCredentialsFileInEnv() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("load private key - set private key via credentials file in Env") + void loadPrivateKeySetPrivateKeyViaCredentialsFileInEnv() throws IOException { final String privateKeyContent = ""; // Create credentialsFile - Path tempCredentialsFile = Files.createTempFile("credentialsFile", ".json"); + Path tempCredentialsFile = Files.createTempFile("credentialsFile", JSON_FILE_EXTENSION); tempCredentialsFile.toFile().deleteOnExit(); // Create dummy credentialsFile - Map credFileContent = new HashMap<>(); + Map credFileContent = new ConcurrentHashMap<>(); credFileContent.put(EnvironmentVariables.ENV_STACKIT_PRIVATE_KEY, privateKeyContent); String credFileContentJson = new Gson().toJson(credFileContent); @@ -343,7 +380,8 @@ void loadPrivateKey_setPrivateKeyViaCredentialsFileInEnv() } @Test - void loadPrivateKey_invalidPrivateKeyPath_throwsException() + @DisplayName("load private key - invalid private key path - throws exception") + void loadPrivateKeyInvalidPrivateKeyPathThrowsException() throws IOException, InvalidKeySpecException, ApiException { String invalidPath = @@ -366,18 +404,18 @@ void loadPrivateKey_invalidPrivateKeyPath_throwsException() } @Test - void readValueFromCredentialsFile_keyAndKeyPathSet_returnsKeyValue() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("read value from credentials file - key and key path set - returns key value") + void readValueFromCredentialsFileKeyAndKeyPathSetReturnsKeyValue() throws IOException { String keyContent = "key"; String keyPathContent = "keyPath"; // Create dummy keyPathFile - Path keyPathFile = Files.createTempFile("serviceAccountKey", ".json"); + Path keyPathFile = Files.createTempFile("serviceAccountKey", JSON_FILE_EXTENSION); keyPathFile.toFile().deleteOnExit(); Files.write(keyPathFile, keyPathContent.getBytes(StandardCharsets.UTF_8)); // Create dummy credentialsFile - Map credentialsFileContent = new HashMap<>(); + Map credentialsFileContent = new ConcurrentHashMap<>(); credentialsFileContent.put( EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY, keyContent); credentialsFileContent.put( @@ -395,12 +433,12 @@ void readValueFromCredentialsFile_keyAndKeyPathSet_returnsKeyValue() } @Test - void readValueFromCredentialsFile_keySet_returnsKeyValue() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("read value from credentials file - key set - returns key value") + void readValueFromCredentialsFileKeySetReturnsKeyValue() throws IOException { String keyContent = "key"; // Create dummy credentialsFile - Map credentialsFileContent = new HashMap<>(); + Map credentialsFileContent = new ConcurrentHashMap<>(); credentialsFileContent.put( EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY, keyContent); Path credentialsFile = createJsonFile(credentialsFileContent); @@ -415,16 +453,16 @@ void readValueFromCredentialsFile_keySet_returnsKeyValue() } @Test - void readValueFromCredentialsFile_KeyPathSet_returnsKeyValue() - throws IOException, InvalidKeySpecException, ApiException { + @DisplayName("read value from credentials file - key path set - returns key value") + void readValueFromCredentialsFileKeyPathSetReturnsKeyValue() throws IOException { // Create dummy keyPathFile String keyPathContent = "keyPath"; - Path keyPathFile = Files.createTempFile("serviceAccountKey", ".json"); + Path keyPathFile = Files.createTempFile("serviceAccountKey", JSON_FILE_EXTENSION); keyPathFile.toFile().deleteOnExit(); Files.write(keyPathFile, keyPathContent.getBytes(StandardCharsets.UTF_8)); // Create dummy credentialsFile - Map credentialsFileContent = new HashMap<>(); + Map credentialsFileContent = new ConcurrentHashMap<>(); credentialsFileContent.put( EnvironmentVariables.ENV_STACKIT_SERVICE_ACCOUNT_KEY_PATH, keyPathFile.toAbsolutePath().toString()); diff --git a/core/src/test/java/cloud/stackit/sdk/core/config/CoreConfigurationTest.java b/core/src/test/java/cloud/stackit/sdk/core/config/CoreConfigurationTest.java index d02897a..9a495a4 100644 --- a/core/src/test/java/cloud/stackit/sdk/core/config/CoreConfigurationTest.java +++ b/core/src/test/java/cloud/stackit/sdk/core/config/CoreConfigurationTest.java @@ -2,15 +2,16 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.junit.jupiter.api.Test; +@SuppressWarnings("PMD.TooManyMethods") class CoreConfigurationTest { @Test - void getDefaultHeader() { - HashMap map = new HashMap(); + void testGetDefaultHeader() { + Map map = new ConcurrentHashMap<>(); map.put("key", "value"); CoreConfiguration cfg = new CoreConfiguration().defaultHeader(map); Map cfgHeader = cfg.getDefaultHeader(); @@ -19,7 +20,7 @@ void getDefaultHeader() { } @Test - void getServiceAccountKey() { + void testGetServiceAccountKey() { final String saKey = ""; CoreConfiguration cfg = new CoreConfiguration().serviceAccountKey(saKey); @@ -30,7 +31,7 @@ void getServiceAccountKey() { } @Test - void getServiceAccountKeyPath() { + void testGetServiceAccountKeyPath() { final String saKeyPath = ""; CoreConfiguration cfg = new CoreConfiguration().serviceAccountKeyPath(saKeyPath); @@ -41,7 +42,7 @@ void getServiceAccountKeyPath() { } @Test - void getPrivateKeyPath() { + void testGetPrivateKeyPath() { final String privateKeyPath = ""; CoreConfiguration cfg = new CoreConfiguration().privateKeyPath(privateKeyPath); @@ -52,7 +53,7 @@ void getPrivateKeyPath() { } @Test - void getPrivateKey() { + void testGetPrivateKey() { final String privateKey = ""; CoreConfiguration cfg = new CoreConfiguration().privateKey(privateKey); @@ -63,7 +64,7 @@ void getPrivateKey() { } @Test - void getCustomEndpoint() { + void testGetCustomEndpoint() { final String customEndpoint = ""; CoreConfiguration cfg = new CoreConfiguration().customEndpoint(customEndpoint); @@ -74,7 +75,7 @@ void getCustomEndpoint() { } @Test - void getCredentialsFilePath() { + void testGetCredentialsFilePath() { final String credFilePath = ""; CoreConfiguration cfg = new CoreConfiguration().credentialsFilePath(credFilePath); @@ -85,7 +86,7 @@ void getCredentialsFilePath() { } @Test - void getTokenCustomUrl() { + void testGetTokenCustomUrl() { final String tokenCustomUrl = ""; CoreConfiguration cfg = new CoreConfiguration().tokenCustomUrl(tokenCustomUrl); @@ -96,7 +97,7 @@ void getTokenCustomUrl() { } @Test - void getTokenExpirationLeeway() { + void testGetTokenExpirationLeeway() { final long tokenExpireLeeway = 100; CoreConfiguration cfg = new CoreConfiguration().tokenExpirationLeeway(tokenExpireLeeway); @@ -107,7 +108,7 @@ void getTokenExpirationLeeway() { } @Test - void getDefaultHeader_not_set() { + void testGetDefaultHeaderNotSet() { CoreConfiguration cfg = new CoreConfiguration(); Map defaultHeader = cfg.getDefaultHeader(); @@ -115,7 +116,7 @@ void getDefaultHeader_not_set() { } @Test - void getServiceAccountKey_not_set() { + void testGetServiceAccountKeyNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String serviceAccountKey = cfg.getServiceAccountKey(); @@ -123,7 +124,7 @@ void getServiceAccountKey_not_set() { } @Test - void getServiceAccountKeyPath_not_set() { + void testGetServiceAccountKeyPathNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String serviceAccountKeyPath = cfg.getServiceAccountKeyPath(); @@ -131,7 +132,7 @@ void getServiceAccountKeyPath_not_set() { } @Test - void getPrivateKeyPath_not_set() { + void testGetPrivateKeyPathNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String privateKeyPath = cfg.getPrivateKeyPath(); @@ -139,7 +140,7 @@ void getPrivateKeyPath_not_set() { } @Test - void getPrivateKey_not_set() { + void testGetPrivateKeyNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String privateKey = cfg.getPrivateKey(); @@ -147,7 +148,7 @@ void getPrivateKey_not_set() { } @Test - void getCustomEndpoint_not_set() { + void testGetCustomEndpointNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String customEndpoint = cfg.getCustomEndpoint(); @@ -155,7 +156,7 @@ void getCustomEndpoint_not_set() { } @Test - void getCredentialsFilePath_not_set() { + void testGetCredentialsFilePathNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String credentialsFilePath = cfg.getCredentialsFilePath(); @@ -163,7 +164,7 @@ void getCredentialsFilePath_not_set() { } @Test - void getTokenCustomUrl_not_set() { + void testGetTokenCustomUrlNotSet() { CoreConfiguration cfg = new CoreConfiguration(); String tokenCustomUrl = cfg.getTokenCustomUrl(); @@ -171,7 +172,7 @@ void getTokenCustomUrl_not_set() { } @Test - void getTokenExpirationLeeway_not_set() { + void testGetTokenExpirationLeewayNotSet() { CoreConfiguration cfg = new CoreConfiguration(); Long tokenExpirationLeeway = cfg.getTokenExpirationLeeway(); diff --git a/core/src/test/java/cloud/stackit/sdk/core/utils/UtilsTest.java b/core/src/test/java/cloud/stackit/sdk/core/utils/UtilsTest.java index 3180081..04effd9 100644 --- a/core/src/test/java/cloud/stackit/sdk/core/utils/UtilsTest.java +++ b/core/src/test/java/cloud/stackit/sdk/core/utils/UtilsTest.java @@ -7,36 +7,36 @@ class UtilsTest { @Test - void isStringSet_null_returnsFalse() { + void isStringSetNull() { assertFalse(Utils.isStringSet(null)); } @Test - void isStringSet_nullString_returnsFalse() { + void isStringSetNullString() { String nullString = null; assertFalse(Utils.isStringSet(nullString)); } @Test - void isStringSet_emptyString_returnsFalse() { + void isStringSetEmptyString() { String nullString = ""; assertFalse(Utils.isStringSet(nullString)); } @Test - void isStringSet_stringWithWhitespaces_returnsFalse() { + void isStringSetStringWithWhitespaces() { String nullString = " "; assertFalse(Utils.isStringSet(nullString)); } @Test - void isStringSet_stringWithText_returnsTrue() { + void isStringSetStringWithText() { String nullString = "text"; assertTrue(Utils.isStringSet(nullString)); } @Test - void isStringSet_stringWithTextAndWhitespaces_returnsTrue() { + void isStringSetStringWithTextAndWhitespaces() { String nullString = " text "; assertTrue(Utils.isStringSet(nullString)); } diff --git a/examples/authentication/src/main/java/cloud/stackit/sdk/authentication/examples/AuthenticationExample.java b/examples/authentication/src/main/java/cloud/stackit/sdk/authentication/examples/AuthenticationExample.java index 6dbe00f..eb49083 100644 --- a/examples/authentication/src/main/java/cloud/stackit/sdk/authentication/examples/AuthenticationExample.java +++ b/examples/authentication/src/main/java/cloud/stackit/sdk/authentication/examples/AuthenticationExample.java @@ -1,6 +1,7 @@ package cloud.stackit.sdk.authentication.examples; import cloud.stackit.sdk.core.config.CoreConfiguration; +import cloud.stackit.sdk.core.exception.ApiException; import cloud.stackit.sdk.resourcemanager.api.ResourceManagerApi; import cloud.stackit.sdk.resourcemanager.model.ListOrganizationsResponse; import java.io.File; @@ -8,15 +9,17 @@ import java.io.IOException; import java.util.Scanner; -class AuthenticationExample { - public static void main(String[] args) throws IOException { - /////////////////////////////////////////////////////// - // Option 1: setting the paths to service account key (and private key) as configuration - /////////////////////////////////////////////////////// - final String SERVICE_ACCOUNT_KEY_PATH = "/path/to/sa_key.json"; - final String PRIVATE_KEY_PATH = "/path/to/private_key.pem"; - final String SERVICE_ACCOUNT_MAIL = "name-1234@sa.stackit.cloud"; +final class AuthenticationExample { + + private static final String SERVICE_ACCOUNT_KEY_PATH = "/path/to/sa_key.json"; + private static final String PRIVATE_KEY_PATH = "/path/to/private_key.pem"; + private static final String SERVICE_ACCOUNT_MAIL = "name-1234@sa.stackit.cloud"; + private AuthenticationExample() {} + + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.SystemPrintln"}) + public static void main(String[] args) throws IOException { + /* OPTION 1: setting the paths to service account key (and private key) as configuration */ try { ResourceManagerApi api = new ResourceManagerApi( @@ -29,17 +32,19 @@ public static void main(String[] args) throws IOException { ListOrganizationsResponse response = api.listOrganizations(null, SERVICE_ACCOUNT_MAIL, null, null, null); - System.out.println(response); - } catch (Exception e) { - throw new RuntimeException(e); + System.out.println(response.toString()); + } catch (ApiException | IOException e) { + throw new IllegalStateException(e); } - /////////////////////////////////////////////////////// - // Option 2: setting the service account key (and private key) as configuration - /////////////////////////////////////////////////////// + /* + * OPTION 2: setting the service account key (and private key) as configuration + * + * */ - // read key content from a file, in production you can also read it e.g. from STACKIT - // secrets manager, so it's only kept in-memory + /* read key content from a file, in production you can also read it + * e.g. from STACKIT secrets manager, so it's only kept in-memory + * */ String serviceAccountKeyPath = // replace it with the path to your service account key "examples/authentication/src/main/java/cloud/stackit/sdk/authentication/examples/dummy_credentials/dummy-service-account-key.json"; File serviceAccountKeyFile = new File(serviceAccountKeyPath); @@ -49,7 +54,8 @@ public static void main(String[] args) throws IOException { serviceAccountKeyContent.append(myReader.nextLine()); } } catch (FileNotFoundException e) { - throw new RuntimeException(e); + System.err.println("File not found: " + serviceAccountKeyPath); + return; } String privateKeyPath = // replace it with the path to your private key @@ -61,7 +67,8 @@ public static void main(String[] args) throws IOException { privateKeyContent.append(myReader.nextLine()); } } catch (FileNotFoundException e) { - throw new RuntimeException(e); + System.err.println("File not found: " + privateKeyPath); + return; } String serviceAccountKey = serviceAccountKeyContent.toString(); @@ -79,35 +86,36 @@ public static void main(String[] args) throws IOException { ListOrganizationsResponse response = api.listOrganizations(null, SERVICE_ACCOUNT_MAIL, null, null, null); - System.out.println(response); - } catch (Exception e) { - throw new RuntimeException(e); + System.out.println(response.toString()); + } catch (ApiException | IOException e) { + throw new IllegalStateException(e); } - /////////////////////////////////////////////////////// - // Option 3: setting the service account key (and private key) as environment variable - /////////////////////////////////////////////////////// - // Set the service account key via environment variable: - // - STACKIT_SERVICE_ACCOUNT_KEY_PATH=/path/to/sa_key.json - // - STACKIT_SERVICE_ACCOUNT_KEY="" - // - // If the private key is not included in the service account key, set also: - // - STACKIT_PRIVATE_KEY_PATH=/path/to/private_key.pem - // - STACKIT_PRIVATE_KEY="" - // - // If no environment variable is set, fallback to credentials file in - // "$HOME/.stackit/credentials.json". - // Can be overridden with the environment variable `STACKIT_CREDENTIALS_PATH` - // The credentials file must be a json: - // { - // "STACKIT_SERVICE_ACCOUNT_KEY_PATH": "path/to/sa_key.json", - // "STACKIT_PRIVATE_KEY_PATH": "(OPTIONAL) when the private key isn't included in the - // Service Account key", - // // Alternative: - // "STACKIT_SERVICE_ACCOUNT_KEY": "", - // "STACKIT_PRIVATE_KEY": "(OPTIONAL) when the private key isn't included in the Service - // Account key", - // } + /* + * OPTION 3: setting the service account key (and private key) as environment variable + * + * Set the service account key via environment variable: + * - STACKIT_SERVICE_ACCOUNT_KEY_PATH=/path/to/sa_key.json + * - STACKIT_SERVICE_ACCOUNT_KEY="" + * + * If the private key is not included in the service account key, set also: + * - STACKIT_PRIVATE_KEY_PATH=/path/to/private_key.pem + * - STACKIT_PRIVATE_KEY="" + * + * If no environment variable is set, fallback to credentials file in + * "$HOME/.stackit/credentials.json". + * Can be overridden with the environment variable `STACKIT_CREDENTIALS_PATH` + * The credentials file must be a json: + * { + * "STACKIT_SERVICE_ACCOUNT_KEY_PATH": "path/to/sa_key.json", + * "STACKIT_PRIVATE_KEY_PATH": "(OPTIONAL) when the private key isn't included in the + * Service Account key", + * // Alternative: + * "STACKIT_SERVICE_ACCOUNT_KEY": "", + * "STACKIT_PRIVATE_KEY": "(OPTIONAL) when the private key isn't included in the Service + * Account key", + * } + * */ try { ResourceManagerApi api = new ResourceManagerApi(); @@ -115,9 +123,9 @@ public static void main(String[] args) throws IOException { ListOrganizationsResponse response = api.listOrganizations(null, SERVICE_ACCOUNT_MAIL, null, null, null); - System.out.println(response); - } catch (Exception e) { - throw new RuntimeException(e); + System.out.println(response.toString()); + } catch (ApiException | IOException e) { + throw new IllegalStateException(e); } } } diff --git a/examples/custom-http-client/src/main/java/cloud/stackit/sdk/customhttpclient/examples/CustomHttpClientExample.java b/examples/custom-http-client/src/main/java/cloud/stackit/sdk/customhttpclient/examples/CustomHttpClientExample.java index 7ce4577..4e359e9 100644 --- a/examples/custom-http-client/src/main/java/cloud/stackit/sdk/customhttpclient/examples/CustomHttpClientExample.java +++ b/examples/custom-http-client/src/main/java/cloud/stackit/sdk/customhttpclient/examples/CustomHttpClientExample.java @@ -14,13 +14,18 @@ * * The example shows how to set the authorization header in the OkHttpClient object (required!). * - * NOTE: Passing the http client is optional, see our other examples where no OkHttpClient object is passed. + * NOTE: Passing the http client is optional, see our other examples + * where no OkHttpClient object is passed. * In this case the STACKIT SDK ApiClients will just create their own OkHttpClient objects. - * Nevertheless, for production usage try to use one single OkHttpClient object for everything to take advantage of the - * shared connection pool and to prevent resource leaks. + * Nevertheless, for production usage try to use one single OkHttpClient object + * for everything to take advantage of the shared connection pool and to prevent resource leaks. * * */ -public class CustomHttpClientExample { +final class CustomHttpClientExample { + + private CustomHttpClientExample() {} + + @SuppressWarnings("PMD.SystemPrintln") public static void main(String[] args) throws IOException { // Credentials are read from the credentialsFile in `~/.stackit/credentials.json` or the env // STACKIT_SERVICE_ACCOUNT_KEY_PATH / STACKIT_SERVICE_ACCOUNT_KEY @@ -49,7 +54,7 @@ public static void main(String[] args) throws IOException { System.out.println("* " + server.getId() + " | " + server.getName()); } } catch (ApiException e) { - throw new RuntimeException(e); + throw new IllegalStateException(e); } } } diff --git a/examples/iaas/src/main/java/cloud/stackit/sdk/iaas/examples/IaaSExample.java b/examples/iaas/src/main/java/cloud/stackit/sdk/iaas/examples/IaaSExample.java index d3e1600..57f00fc 100644 --- a/examples/iaas/src/main/java/cloud/stackit/sdk/iaas/examples/IaaSExample.java +++ b/examples/iaas/src/main/java/cloud/stackit/sdk/iaas/examples/IaaSExample.java @@ -4,15 +4,28 @@ import cloud.stackit.sdk.iaas.api.IaasApi; import cloud.stackit.sdk.iaas.model.*; import java.io.IOException; +import java.net.HttpURLConnection; import java.util.Collections; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; -public class IaaSExample { +final class IaaSExample { + + private IaaSExample() {} + + @SuppressWarnings({ + "PMD.CyclomaticComplexity", + "PMD.CognitiveComplexity", + "PMD.NPathComplexity", + "PMD.NcssCount", + "PMD.SystemPrintln" + }) public static void main(String[] args) throws IOException { - // Credentials are read from the credentialsFile in `~/.stackit/credentials.json` or the env - // STACKIT_SERVICE_ACCOUNT_KEY_PATH / STACKIT_SERVICE_ACCOUNT_KEY + /* + * Credentials are read from the credentialsFile in `~/.stackit/credentials.json` or the env + * STACKIT_SERVICE_ACCOUNT_KEY_PATH / STACKIT_SERVICE_ACCOUNT_KEY + * */ IaasApi iaasApi = new IaasApi(); // the id of your STACKIT project, read from env var for this example @@ -24,11 +37,14 @@ public static void main(String[] args) throws IOException { UUID projectId = UUID.fromString(projectIdString); try { - /////////////////////////////////////////////////////// - // N E T W O R K S // - /////////////////////////////////////////////////////// + /* + * /////////////////////////////////////////////////////// + * // N E T W O R K S // + * /////////////////////////////////////////////////////// + * */ /* create a network in the project */ + @SuppressWarnings("PMD.AvoidUsingHardCodedIP") Network newNetwork = iaasApi.createNetwork( projectId, @@ -36,7 +52,7 @@ public static void main(String[] args) throws IOException { .name("java-sdk-example-network-01") .dhcp(true) .routed(false) - .labels(Collections.singletonMap("foo", "bar")) + .labels(Collections.singletonMap("some-network-label", "bar")) .addressFamily( new CreateNetworkAddressFamily() .ipv4( @@ -50,12 +66,12 @@ public static void main(String[] args) throws IOException { newNetwork.getNetworkId(), new PartialUpdateNetworkPayload() .dhcp(false) - .labels(Collections.singletonMap("foo", "bar-updated"))); + .labels(Collections.singletonMap("some-network-label", "bar-updated"))); /* fetch the network we just created */ Network fetchedNetwork = iaasApi.getNetwork(projectId, newNetwork.getNetworkId()); System.out.println("\nFetched network: "); - System.out.println("* Name: " + fetchedNetwork.getName()); + System.out.println("* Network name: " + fetchedNetwork.getName()); System.out.println("* Id: " + fetchedNetwork.getNetworkId()); System.out.println( "* DHCP: " + (Boolean.TRUE.equals(fetchedNetwork.getDhcp()) ? "YES" : "NO")); @@ -69,9 +85,11 @@ public static void main(String[] args) throws IOException { System.out.println("* " + network.getName()); } - /////////////////////////////////////////////////////// - // I M A G E S // - /////////////////////////////////////////////////////// + /* + * /////////////////////////////////////////////////////// + * // I M A G E S // + * /////////////////////////////////////////////////////// + * */ /* list all available images */ ImageListResponse images = iaasApi.listImages(projectId, false, null); @@ -88,15 +106,17 @@ public static void main(String[] args) throws IOException { assert imageId != null; Image fetchedImage = iaasApi.getImage(projectId, imageId); System.out.println("\nFetched image:"); - System.out.println("* Name: " + fetchedImage.getName()); - System.out.println("* Id: " + fetchedImage.getId()); + System.out.println("* Image name: " + fetchedImage.getName()); + System.out.println("* Image id: " + fetchedImage.getId()); System.out.println("* Checksum: " + fetchedImage.getChecksum()); System.out.println("* Created at: " + fetchedImage.getCreatedAt()); System.out.println("* Updated at: " + fetchedImage.getUpdatedAt()); - /////////////////////////////////////////////////////// - // K E Y P A I R S // - /////////////////////////////////////////////////////// + /* + * /////////////////////////////////////////////////////// + * // K E Y P A I R S // + * /////////////////////////////////////////////////////// + * */ /* list all available keypairs */ KeyPairListResponse keypairs = iaasApi.listKeyPairs(null); @@ -119,7 +139,8 @@ public static void main(String[] args) throws IOException { assert newKeypair.getName() != null; iaasApi.updateKeyPair( newKeypair.getName(), - new UpdateKeyPairPayload().labels(Collections.singletonMap("foo", "bar"))); + new UpdateKeyPairPayload() + .labels(Collections.singletonMap("some-keypair-label", "bar"))); /* fetch the keypair we just created / updated */ Keypair fetchedKeypair = iaasApi.getKeyPair(newKeypair.getName()); @@ -131,9 +152,11 @@ public static void main(String[] args) throws IOException { System.out.println("* Fingerprint: " + fetchedKeypair.getFingerprint()); System.out.println("* Public key: " + fetchedKeypair.getPublicKey()); - /////////////////////////////////////////////////////// - // S E R V E R S // - /////////////////////////////////////////////////////// + /* + * /////////////////////////////////////////////////////// + * // S E R V E R S // + * /////////////////////////////////////////////////////// + * */ /* list all available machine types */ MachineTypeListResponse machineTypes = iaasApi.listMachineTypes(projectId, null); @@ -146,16 +169,20 @@ public static void main(String[] args) throws IOException { MachineType fetchedMachineType = iaasApi.getMachineType(projectId, machineTypes.getItems().get(0).getName()); System.out.println("\nFetched machine type: "); - System.out.println("* Name: " + fetchedMachineType.getName()); + System.out.println("* Machine type name: " + fetchedMachineType.getName()); System.out.println("* Description: " + fetchedMachineType.getDescription()); System.out.println("* Disk size: " + fetchedMachineType.getDisk()); System.out.println("* RAM: " + fetchedMachineType.getRam()); System.out.println("* vCPUs: " + fetchedMachineType.getVcpus()); System.out.println("* Extra specs: " + fetchedMachineType.getExtraSpecs()); - /* create a server */ - // NOTE: see https://docs.stackit.cloud/stackit/en/virtual-machine-flavors-75137231.html - // for available machine types + /* + * create a server + * + * NOTE: see the following link for available machine types + * https://docs.stackit.cloud/stackit/en/virtual-machine-flavors-75137231.html + * + * */ Server newServer = iaasApi.createServer( projectId, @@ -231,9 +258,11 @@ public static void main(String[] args) throws IOException { /* reboot the server we just created */ iaasApi.rebootServer(projectId, serverId, null); - /////////////////////////////////////////////////////// - // D E L E T I O N // - /////////////////////////////////////////////////////// + /* + * /////////////////////////////////////////////////////// + * // D E L E T I O N // + * /////////////////////////////////////////////////////// + * */ /* delete the server we just created */ iaasApi.deleteServer(projectId, serverId); @@ -246,7 +275,7 @@ public static void main(String[] args) throws IOException { System.out.println("Waiting for server deletion to complete..."); TimeUnit.SECONDS.sleep(5); } catch (ApiException e) { - if (e.getCode() == 404) { + if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) { break; } } @@ -261,7 +290,7 @@ public static void main(String[] args) throws IOException { System.out.println("Deleted network: " + newNetwork.getNetworkId()); } catch (ApiException | InterruptedException e) { - throw new RuntimeException(e); + throw new IllegalStateException(e); } } } diff --git a/examples/resourcemanager/src/main/java/cloud/stackit/sdk/resourcemanager/examples/ResourcemanagerExample.java b/examples/resourcemanager/src/main/java/cloud/stackit/sdk/resourcemanager/examples/ResourcemanagerExample.java index 34ed2aa..2e4ed5a 100644 --- a/examples/resourcemanager/src/main/java/cloud/stackit/sdk/resourcemanager/examples/ResourcemanagerExample.java +++ b/examples/resourcemanager/src/main/java/cloud/stackit/sdk/resourcemanager/examples/ResourcemanagerExample.java @@ -17,7 +17,11 @@ import java.util.Collections; import java.util.UUID; -class ResourcemanagerExample { +final class ResourcemanagerExample { + + private ResourcemanagerExample() {} + + @SuppressWarnings({"PMD.SystemPrintln"}) public static void main(String[] args) throws IOException { // Credentials are read from the credentialsFile in `~/.stackit/credentials.json` or the env // STACKIT_SERVICE_ACCOUNT_KEY_PATH / STACKIT_SERVICE_ACCOUNT_KEY @@ -49,7 +53,9 @@ public static void main(String[] args) throws IOException { .containerParentId(containerParentId.toString()) .name("java-test-project") .addMembersItem(member) - .labels(Collections.singletonMap("foo", "bar"))); + .labels( + Collections.singletonMap( + "some-project-label", "foo-bar"))); System.out.println("Project:\n" + project.toString()); /* list projects */ @@ -106,7 +112,7 @@ public static void main(String[] args) throws IOException { /* delete folder */ resourceManagerApi.deleteFolder(folder.getContainerId(), true); } catch (ApiException e) { - throw new RuntimeException(e); + throw new IllegalStateException(e); } } }