diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index bb19e5b0c9e8..28bb87f95ed4 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -80,6 +80,7 @@ com.azure.resourcemanager:azure-resourcemanager-redis;2.0.0-beta.3;2.0.0-beta.4 com.azure.resourcemanager:azure-resourcemanager-samples;2.0.0-beta.3;2.0.0-beta.4 com.azure.resourcemanager:azure-resourcemanager-sql;2.0.0-beta.3;2.0.0-beta.4 com.azure.resourcemanager:azure-resourcemanager-storage;2.0.0-beta.3;2.0.0-beta.4 +com.azure.resourcemanager:azure-resourcemanager-test;2.0.0-beta.4;2.0.0-beta.4 com.microsoft.azure:azure-active-directory-b2c-spring-boot-starter;2.3.3;2.4.0-beta.1 com.microsoft.azure:azure-active-directory-spring-boot-starter;2.3.3;2.4.0-beta.1 com.microsoft.azure:azure-cosmosdb-spring-boot-starter;2.3.3;2.4.0-beta.1 diff --git a/sdk/resourcemanager/azure-resourcemanager-test/CHANGELOG.md b/sdk/resourcemanager/azure-resourcemanager-test/CHANGELOG.md new file mode 100644 index 000000000000..003c434ede62 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 2.0.0-beta.4 (Unreleased) + +- TODO diff --git a/sdk/resourcemanager/azure-resourcemanager-test/README.md b/sdk/resourcemanager/azure-resourcemanager-test/README.md new file mode 100644 index 000000000000..cfdf9153d622 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/README.md @@ -0,0 +1,42 @@ +# Azure Resource Manager Test shared library for Java + +Azure Resource Manager test library for Java + +For documentation on how to use this package, please see [Azure Management Libraries for Java](https://aka.ms/azure-sdk-java-mgmt). + +## Getting started + +### Prerequisites + +- Java Development Kit (JDK) with version 8 or above + +### Adding the package to your product + +[//]: # ({x-version-update-start;com.azure.resourcemanager:azure-resourcemanager-test;current}) +```xml + + com.azure.resourcemanager + azure-resourcemanager-test + 2.0.0-beta.4 + +``` +[//]: # ({x-version-update-end}) + +## Key concepts + +## Examples + +## Troubleshooting + +## Next steps + +## Contributing + +If you would like to become an active contributor to this project please follow the instructions provided in [Microsoft +Azure Projects Contribution Guidelines](http://azure.github.io/guidelines.html). + +1. Fork it +1. Create your feature branch (`git checkout -b my-new-feature`) +1. Commit your changes (`git commit -am 'Add some feature'`) +1. Push to the branch (`git push origin my-new-feature`) +1. Create new Pull Request diff --git a/sdk/resourcemanager/azure-resourcemanager-test/pom.xml b/sdk/resourcemanager/azure-resourcemanager-test/pom.xml new file mode 100644 index 000000000000..067124d963fe --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + + com.azure + azure-client-sdk-parent + 1.7.0 + ../../parents/azure-client-sdk-parent + + + com.azure.resourcemanager + azure-resourcemanager-test + 2.0.0-beta.4 + jar + + Microsoft Azure Resource Manager Test Library + This package contains test types for Azure Resource Manager SDK. For documentation on how to use this package, please see https://aka.ms/azure-sdk-java-mgmt + https://github.com/Azure/azure-sdk-for-java + + + + The MIT License (MIT) + http://opensource.org/licenses/MIT + repo + + + + + https://github.com/Azure/azure-sdk-for-java + scm:git:git@github.com:Azure/azure-sdk-for-java.git + HEAD + + + + + 0.10 + 0.10 + + + + + microsoft + Microsoft + + + + + + com.azure + azure-core-test + 1.4.0 + + + com.azure + azure-core-management + 1.0.0-beta.3 + + + com.azure + azure-identity + 1.1.0 + + + com.azure + azure-core-http-netty + 1.5.4 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.2 + + + + test-jar + + + + + + + true + true + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M3 + + + + + com.github.spotbugs:spotbugs-annotations:[4.0.2] + + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + com/azure/resourcemanager/**/fluent/**/* + com/azure/resourcemanager/**/models/**/* + + + + + + + diff --git a/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/ResourceManagerTestBase.java b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/ResourceManagerTestBase.java new file mode 100644 index 000000000000..852ef36a4772 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/ResourceManagerTestBase.java @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.resourcemanager.test; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.ProxyOptions; +import com.azure.core.http.netty.NettyAsyncHttpClientBuilder; +import com.azure.core.http.policy.CookiePolicy; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpLogOptions; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.http.policy.TimeoutPolicy; +import com.azure.core.management.AzureEnvironment; +import com.azure.core.management.profile.AzureProfile; +import com.azure.core.test.TestBase; +import com.azure.core.test.TestMode; +import com.azure.core.test.utils.ResourceNamer; +import com.azure.core.util.Configuration; +import com.azure.core.util.logging.ClientLogger; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.resourcemanager.test.policy.TextReplacementPolicy; +import com.azure.resourcemanager.test.utils.AuthFile; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Test base for resource manager SDK. + */ +public abstract class ResourceManagerTestBase extends TestBase { + private static final String ZERO_SUBSCRIPTION = "00000000-0000-0000-0000-000000000000"; + private static final String ZERO_TENANT = "00000000-0000-0000-0000-000000000000"; + private static final String PLAYBACK_URI_BASE = "http://localhost:"; + private static final String AZURE_AUTH_LOCATION = "AZURE_AUTH_LOCATION"; + private static final String HTTPS_PROXY_HOST = "https.proxyHost"; + private static final String HTTPS_PROXY_PORT = "https.proxyPort"; + private static final String HTTP_PROXY_HOST = "http.proxyHost"; + private static final String HTTP_PROXY_PORT = "http.proxyPort"; + private static final String USE_SYSTEM_PROXY = "java.net.useSystemProxies"; + private static final String VALUE_TRUE = "true"; + private static final String PLAYBACK_URI = PLAYBACK_URI_BASE + "1234"; + private static final AzureProfile PLAYBACK_PROFILE = new AzureProfile( + ZERO_TENANT, + ZERO_SUBSCRIPTION, + new AzureEnvironment( + new HashMap() { + { + put("managementEndpointUrl", PLAYBACK_URI); + put("resourceManagerEndpointUrl", PLAYBACK_URI); + put("sqlManagementEndpointUrl", PLAYBACK_URI); + put("galleryEndpointUrl", PLAYBACK_URI); + put("activeDirectoryEndpointUrl", PLAYBACK_URI); + put("activeDirectoryResourceId", PLAYBACK_URI); + put("activeDirectoryGraphResourceId", PLAYBACK_URI); + }})); + private static final OutputStream EMPTY_OUTPUT_STREAM = new OutputStream() { + @Override + public void write(int b) { + } + }; + + private final ClientLogger logger = new ClientLogger(ResourceManagerTestBase.class); + private AzureProfile testProfile; + private AuthFile testAuthFile; + private boolean isSkipInPlayback; + + protected String generateRandomResourceName(String prefix, int maxLen) { + return testResourceNamer.randomName(prefix, maxLen); + } + + protected String generateRandomUuid() { + return testResourceNamer.randomUuid(); + } + + /** + * @return random password + */ + public static String password() { + // do not record + String password = new ResourceNamer("").randomName("Pa5$", 12); + new ClientLogger(ResourceManagerTestBase.class).info("Password: %s%n", password); + return password; + } + + protected TokenCredential credentialFromFile() { + return testAuthFile.getCredential(); + } + + protected String clientIdFromFile() { + return testAuthFile.getClientId(); + } + + protected AzureProfile profile() { + return testProfile; + } + + protected boolean isPlaybackMode() { + return getTestMode() == TestMode.PLAYBACK; + } + + protected boolean skipInPlayback() { + if (isPlaybackMode()) { + isSkipInPlayback = true; + } + return isSkipInPlayback; + } + + @Override + protected void beforeTest() { + TokenCredential credential; + HttpPipeline httpPipeline; + Map textReplacementRules = new HashMap<>(); + String logLevel = Configuration.getGlobalConfiguration().get(Configuration.PROPERTY_AZURE_LOG_LEVEL); + HttpLogDetailLevel httpLogDetailLevel; + + try { + httpLogDetailLevel = HttpLogDetailLevel.valueOf(logLevel); + } catch (Exception e) { + if (isPlaybackMode()) { + httpLogDetailLevel = HttpLogDetailLevel.NONE; + logger.error("Environment variable '{}' has not been set yet. Using 'NONE' for PLAYBACK.", new Object[]{"AZURE_LOG_LEVEL"}); + } else { + httpLogDetailLevel = HttpLogDetailLevel.BODY_AND_HEADERS; + logger.error("Environment variable '{}' has not been set yet. Using 'BODY_AND_HEADERS' for RECORD/LIVE.", new Object[]{"AZURE_LOG_LEVEL"}); + } + } + + + if (httpLogDetailLevel == HttpLogDetailLevel.NONE) { + try { + System.setOut(new PrintStream(EMPTY_OUTPUT_STREAM, false, Charset.defaultCharset().name())); + System.setErr(new PrintStream(EMPTY_OUTPUT_STREAM, false, Charset.defaultCharset().name())); + } catch (UnsupportedEncodingException e) { + } + } + + if (isPlaybackMode()) { + if (interceptorManager.getRecordedData() == null) { + skipInPlayback(); + return; + } + + testProfile = PLAYBACK_PROFILE; + List policies = new ArrayList<>(); + policies.add(new TextReplacementPolicy(interceptorManager.getRecordedData(), textReplacementRules)); + policies.add(new CookiePolicy()); + httpPipeline = buildHttpPipeline( + null, + testProfile, + new HttpLogOptions().setLogLevel(httpLogDetailLevel), + policies, + interceptorManager.getPlaybackClient()); + textReplacementRules.put(PLAYBACK_URI_BASE + "1234", PLAYBACK_URI); + addTextReplacementRules(textReplacementRules); + } else { + if (System.getenv(AZURE_AUTH_LOCATION) != null) { // Record mode + final File credFile = new File(System.getenv(AZURE_AUTH_LOCATION)); + try { + testAuthFile = AuthFile.parse(credFile); + } catch (IOException e) { + throw logger.logExceptionAsError(new RuntimeException("Cannot parse auth file. Please check file format.")); + } + credential = testAuthFile.getCredential(); + testProfile = new AzureProfile(testAuthFile.getTenantId(), testAuthFile.getSubscriptionId(), testAuthFile.getEnvironment()); + } else { + Configuration configuration = Configuration.getGlobalConfiguration(); + String clientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID); + String tenantId = configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID); + String clientSecret = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_SECRET); + String subscriptionId = configuration.get(Configuration.PROPERTY_AZURE_SUBSCRIPTION_ID); + if (clientId == null || tenantId == null || clientSecret == null || subscriptionId == null) { + throw logger.logExceptionAsError( + new IllegalArgumentException("When running tests in record mode either 'AZURE_AUTH_LOCATION' or 'AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET and AZURE_SUBSCRIPTION_ID' needs to be set")); + } + + credential = new ClientSecretCredentialBuilder() + .tenantId(tenantId) + .clientId(clientId) + .clientSecret(clientSecret) + .authorityHost(AzureEnvironment.AZURE.getActiveDirectoryEndpoint()) + .build(); + testProfile = new AzureProfile(tenantId, subscriptionId, AzureEnvironment.AZURE); + } + + List policies = new ArrayList<>(); + policies.add(new TimeoutPolicy(Duration.ofMinutes(1))); + policies.add(new CookiePolicy()); + if (!interceptorManager.isLiveMode()) { + policies.add(new TextReplacementPolicy(interceptorManager.getRecordedData(), textReplacementRules)); + } + httpPipeline = buildHttpPipeline( + credential, + testProfile, + new HttpLogOptions().setLogLevel(httpLogDetailLevel), + policies, + generateHttpClientWithProxy(null)); + + textReplacementRules.put(testProfile.getSubscriptionId(), ZERO_SUBSCRIPTION); + textReplacementRules.put(testProfile.getTenantId(), ZERO_TENANT); + textReplacementRules.put(AzureEnvironment.AZURE.getResourceManagerEndpoint(), PLAYBACK_URI + "/"); + textReplacementRules.put(AzureEnvironment.AZURE.getGraphEndpoint(), PLAYBACK_URI + "/"); + addTextReplacementRules(textReplacementRules); + } + initializeClients(httpPipeline, testProfile); + } + + private HttpClient generateHttpClientWithProxy(ProxyOptions proxyOptions) { + NettyAsyncHttpClientBuilder clientBuilder = new NettyAsyncHttpClientBuilder(); + if (proxyOptions != null) { + clientBuilder.proxy(proxyOptions); + } else { + try { + System.setProperty(USE_SYSTEM_PROXY, VALUE_TRUE); + List proxies = ProxySelector.getDefault().select(new URI(AzureEnvironment.AZURE.getResourceManagerEndpoint())); + if (!proxies.isEmpty()) { + for (Proxy proxy : proxies) { + if (proxy.address() instanceof InetSocketAddress) { + String host = ((InetSocketAddress) proxy.address()).getHostName(); + int port = ((InetSocketAddress) proxy.address()).getPort(); + switch (proxy.type()) { + case HTTP: + return clientBuilder.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(host, port))).build(); + case SOCKS: + return clientBuilder.proxy(new ProxyOptions(ProxyOptions.Type.SOCKS5, new InetSocketAddress(host, port))).build(); + default: + } + } + } + } + String host = null; + int port = 0; + if (System.getProperty(HTTPS_PROXY_HOST) != null && System.getProperty(HTTPS_PROXY_PORT) != null) { + host = System.getProperty(HTTPS_PROXY_HOST); + port = Integer.parseInt(System.getProperty(HTTPS_PROXY_PORT)); + } else if (System.getProperty(HTTP_PROXY_HOST) != null && System.getProperty(HTTP_PROXY_PORT) != null) { + host = System.getProperty(HTTP_PROXY_HOST); + port = Integer.parseInt(System.getProperty(HTTP_PROXY_PORT)); + } + if (host != null) { + clientBuilder.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(host, port))); + } + } catch (URISyntaxException e) { } + } + return clientBuilder.build(); + } + + @Override + protected void afterTest() { + if (!isSkipInPlayback) { + cleanUpResources(); + } + } + + private void addTextReplacementRules(Map rules) { + for (Map.Entry entry : rules.entrySet()) { + interceptorManager.addTextReplacementRule(entry.getKey(), entry.getValue()); + } + } + + protected abstract HttpPipeline buildHttpPipeline( + TokenCredential credential, + AzureProfile profile, + HttpLogOptions httpLogOptions, + List policies, + HttpClient httpClient); + + protected abstract void initializeClients(HttpPipeline httpPipeline, AzureProfile profile); + + protected abstract void cleanUpResources(); +} diff --git a/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/policy/TextReplacementPolicy.java b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/policy/TextReplacementPolicy.java new file mode 100644 index 000000000000..e964245a6484 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/policy/TextReplacementPolicy.java @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.resourcemanager.test.policy; + +import com.azure.core.http.ContentType; +import com.azure.core.http.HttpHeader; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.http.HttpPipelineNextPolicy; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.test.models.NetworkCallError; +import com.azure.core.test.models.NetworkCallRecord; +import com.azure.core.test.models.RecordedData; +import com.azure.core.test.models.RecordingRedactor; +import com.azure.core.util.UrlBuilder; +import com.azure.core.util.logging.ClientLogger; +import reactor.core.Exceptions; +import reactor.core.publisher.Mono; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.zip.GZIPInputStream; + +/** + * TextReplacementPolicy to support session text replacement rules for tests. + */ +public class TextReplacementPolicy implements HttpPipelinePolicy { + private static final int DEFAULT_BUFFER_LENGTH = 1024; + private static final String CONTENT_TYPE = "Content-Type"; + private static final String CONTENT_ENCODING = "Content-Encoding"; + private static final String CONTENT_LENGTH = "Content-Length"; + private static final String X_MS_CLIENT_REQUEST_ID = "x-ms-client-request-id"; + private static final String X_MS_ENCRYPTION_KEY_SHA256 = "x-ms-encryption-key-sha256"; + private static final String X_MS_VERSION = "x-ms-version"; + private static final String USER_AGENT = "User-Agent"; + private static final String STATUS_CODE = "StatusCode"; + private static final String BODY = "Body"; + private static final String SIG = "sig"; + + private final ClientLogger logger = new ClientLogger(TextReplacementPolicy.class); + private final RecordedData recordedData; + private final Map textReplacementRules; + + /** + * Creates a policy that records network calls into {@code recordedData} with replacement rules. + * + * @param recordedData The record to persist network calls into. + * @param textReplacementRules The replacement rules + */ + public TextReplacementPolicy(RecordedData recordedData, Map textReplacementRules) { + //Objects.requireNonNull(recordedData, "'recordedData' cannot be null."); + Objects.requireNonNull(textReplacementRules, "'textReplacementRules' cannot be null."); + this.recordedData = recordedData; + this.textReplacementRules = textReplacementRules; + } + + @Override + public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { + final NetworkCallRecord networkCallRecord = new NetworkCallRecord(); + Map headers = new HashMap<>(); + + captureRequestHeaders(context.getHttpRequest().getHeaders(), headers, + X_MS_CLIENT_REQUEST_ID, + CONTENT_TYPE, + X_MS_VERSION, + USER_AGENT); + + networkCallRecord.setHeaders(headers); + networkCallRecord.setMethod(context.getHttpRequest().getHttpMethod().toString()); + + // Remove sensitive information such as SAS token signatures from the recording. + UrlBuilder urlBuilder = UrlBuilder.parse(context.getHttpRequest().getUrl()); + if (urlBuilder.getQuery().containsKey(SIG)) { + urlBuilder.setQueryParameter(SIG, "REDACTED"); + } + networkCallRecord.setUri(applyReplacementRule(urlBuilder.toString().replaceAll("\\?$", ""))); + + return next.process() + .doOnError(throwable -> { + networkCallRecord.setException(new NetworkCallError(throwable)); + recordedData.addNetworkCall(networkCallRecord); + throw logger.logExceptionAsWarning(Exceptions.propagate(throwable)); + }).flatMap(httpResponse -> { + final HttpResponse bufferedResponse = httpResponse.buffer(); + + return extractResponseData(bufferedResponse).map(responseData -> { + networkCallRecord.setResponse(responseData); + String body = responseData.get(BODY); + + // Remove pre-added header if this is a waiting or redirection + if (body != null && body.contains("InProgress") + || Integer.parseInt(responseData.get(STATUS_CODE)) == HttpURLConnection.HTTP_MOVED_TEMP) { + logger.info("Waiting for a response or redirection."); + } else { + recordedData.addNetworkCall(networkCallRecord); + } + + return bufferedResponse; + }); + }); + } + + private String applyReplacementRule(String text) { + for (Map.Entry rule : textReplacementRules.entrySet()) { + if (rule.getValue() != null) { + text = text.replaceAll(rule.getKey(), rule.getValue()); + } + } + return text; + } + + private void captureRequestHeaders(HttpHeaders requestHeaders, Map captureHeaders, + String... headerNames) { + for (String headerName : headerNames) { + if (requestHeaders.getValue(headerName) != null) { + captureHeaders.put(headerName, requestHeaders.getValue(headerName)); + } + } + } + + private Mono> extractResponseData(final HttpResponse response) { + final Map responseData = new HashMap<>(); + responseData.put(STATUS_CODE, Integer.toString(response.getStatusCode())); + + boolean addedRetryAfter = false; + for (HttpHeader header : response.getHeaders()) { + String headerValueToStore = header.getValue(); + + if (header.getName().equalsIgnoreCase("retry-after")) { + headerValueToStore = "0"; + addedRetryAfter = true; + } else if (header.getName().equalsIgnoreCase(X_MS_ENCRYPTION_KEY_SHA256)) { + // The encryption key is sensitive information so capture it with a hidden value. + headerValueToStore = "REDACTED"; + } + + responseData.put(header.getName(), applyReplacementRule(headerValueToStore)); + } + + if (!addedRetryAfter) { + responseData.put("retry-after", "0"); + } + + String contentType = response.getHeaderValue(CONTENT_TYPE); + if (contentType == null) { + return response.getBodyAsByteArray().switchIfEmpty(Mono.just(new byte[0])).map(bytes -> { + if (bytes.length == 0) { + return responseData; + } + + String content = new String(bytes, StandardCharsets.UTF_8); + responseData.put(CONTENT_LENGTH, Integer.toString(content.length())); + responseData.put(BODY, content); + return responseData; + }); + } else if (contentType.equalsIgnoreCase(ContentType.APPLICATION_OCTET_STREAM) + || contentType.equalsIgnoreCase("avro/binary")) { + return response.getBodyAsByteArray().switchIfEmpty(Mono.just(new byte[0])).map(bytes -> { + if (bytes.length == 0) { + return responseData; + } + + responseData.put(BODY, Arrays.toString(bytes)); + return responseData; + }); + } else if (contentType.contains("json") || response.getHeaderValue(CONTENT_ENCODING) == null) { + return response.getBodyAsString(StandardCharsets.UTF_8).switchIfEmpty(Mono.just("")).map(content -> { + responseData.put(BODY, applyReplacementRule(new RecordingRedactor().redact(content))); + return responseData; + }); + } else { + return response.getBodyAsByteArray().switchIfEmpty(Mono.just(new byte[0])).map(bytes -> { + if (bytes.length == 0) { + return responseData; + } + + String content; + if ("gzip".equalsIgnoreCase(response.getHeaderValue(CONTENT_ENCODING))) { + try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes)); + ByteArrayOutputStream output = new ByteArrayOutputStream()) { + byte[] buffer = new byte[DEFAULT_BUFFER_LENGTH]; + int position = 0; + int bytesRead = gis.read(buffer, position, buffer.length); + + while (bytesRead != -1) { + output.write(buffer, 0, bytesRead); + position += bytesRead; + bytesRead = gis.read(buffer, position, buffer.length); + } + + content = new String(output.toByteArray(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw logger.logExceptionAsWarning(Exceptions.propagate(e)); + } + } else { + content = new String(bytes, StandardCharsets.UTF_8); + } + + responseData.remove(CONTENT_ENCODING); + responseData.put(CONTENT_LENGTH, Integer.toString(content.length())); + + responseData.put(BODY, content); + return responseData; + }); + } + } +} diff --git a/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/AuthFile.java b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/AuthFile.java new file mode 100644 index 000000000000..188227440ff2 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/AuthFile.java @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.resourcemanager.test.utils; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.management.AzureEnvironment; +import com.azure.core.management.serializer.AzureJacksonAdapter; +import com.azure.core.util.logging.ClientLogger; +import com.azure.core.util.serializer.SerializerAdapter; +import com.azure.core.util.serializer.SerializerEncoding; +import com.azure.identity.ClientCertificateCredentialBuilder; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * AuthFile class to help authenticate in tests. + */ +public final class AuthFile { + private String clientId; + private String tenantId; + private String clientSecret; + private String clientCertificate; + private String clientCertificatePassword; + private String subscriptionId; + private TokenCredential credential; + + @JsonIgnore + private final AzureEnvironment environment; + @JsonIgnore + private static final SerializerAdapter ADAPTER = new AzureJacksonAdapter(); + + private AuthFile() { + environment = new AzureEnvironment(new HashMap<>()); + environment.endpoints().putAll(AzureEnvironment.AZURE.endpoints()); + } + + /** + * Create a parameterized type from a raw class and its type arguments. + * + * @param rawClass the raw class to construct the parameterized type + * @param genericTypes the generic arguments + * @return the parameterized type + */ + static ParameterizedType createParameterizedType(Class rawClass, Type... genericTypes) { + return new ParameterizedType() { + @Override + public Type[] getActualTypeArguments() { + return genericTypes; + } + + @Override + public Type getRawType() { + return rawClass; + } + + @Override + public Type getOwnerType() { + return null; + } + }; + } + + /** + * Parses an auth file and read into an AuthFile object. + * + * @param file the auth file to read + * @return the AuthFile object created + * @throws IOException thrown when the auth file or the certificate file cannot be read or parsed + */ + public static AuthFile parse(File file) throws IOException { + String content = new String(Files.readAllBytes(Paths.get(file.getPath())), StandardCharsets.UTF_8); + AuthFile authFile; + if (isJsonBased(content)) { + authFile = ADAPTER.deserialize(content, AuthFile.class, SerializerEncoding.JSON); + Map endpoints = ADAPTER.deserialize(content, + createParameterizedType(Map.class, String.class, String.class), + SerializerEncoding.JSON); + authFile.environment.endpoints().putAll(endpoints); + } else { + // Set defaults + Properties authSettings = new Properties(); + authSettings.put(AuthFile.CredentialSettings.AUTH_URL.toString(), + AzureEnvironment.AZURE.getActiveDirectoryEndpoint()); + authSettings.put(AuthFile.CredentialSettings.BASE_URL.toString(), + AzureEnvironment.AZURE.getResourceManagerEndpoint()); + authSettings.put(AuthFile.CredentialSettings.MANAGEMENT_URI.toString(), + AzureEnvironment.AZURE.getManagementEndpoint()); + authSettings.put(AuthFile.CredentialSettings.GRAPH_URL.toString(), + AzureEnvironment.AZURE.getGraphEndpoint()); + authSettings.put(AuthFile.CredentialSettings.VAULT_SUFFIX.toString(), + AzureEnvironment.AZURE.getKeyVaultDnsSuffix()); + + // Load the credentials from the file + StringReader credentialsReader = new StringReader(content); + authSettings.load(credentialsReader); + credentialsReader.close(); + + authFile = new AuthFile(); + authFile.clientId = authSettings.getProperty(AuthFile.CredentialSettings.CLIENT_ID.toString()); + authFile.tenantId = authSettings.getProperty(AuthFile.CredentialSettings.TENANT_ID.toString()); + authFile.clientSecret = authSettings.getProperty(AuthFile.CredentialSettings.CLIENT_KEY.toString()); + authFile.clientCertificate = authSettings.getProperty(AuthFile.CredentialSettings.CLIENT_CERT.toString()); + authFile.clientCertificatePassword = + authSettings.getProperty(AuthFile.CredentialSettings.CLIENT_CERT_PASS.toString()); + authFile.subscriptionId = authSettings.getProperty(AuthFile.CredentialSettings.SUBSCRIPTION_ID.toString()); + + authFile.environment.endpoints().put(AzureEnvironment.Endpoint.MANAGEMENT.identifier(), + authSettings.getProperty(AuthFile.CredentialSettings.MANAGEMENT_URI.toString())); + authFile.environment.endpoints().put(AzureEnvironment.Endpoint.ACTIVE_DIRECTORY.identifier(), + authSettings.getProperty(AuthFile.CredentialSettings.AUTH_URL.toString())); + authFile.environment.endpoints().put(AzureEnvironment.Endpoint.RESOURCE_MANAGER.identifier(), + authSettings.getProperty(AuthFile.CredentialSettings.BASE_URL.toString())); + authFile.environment.endpoints().put(AzureEnvironment.Endpoint.GRAPH.identifier(), + authSettings.getProperty(AuthFile.CredentialSettings.GRAPH_URL.toString())); + authFile.environment.endpoints().put(AzureEnvironment.Endpoint.KEYVAULT.identifier(), + authSettings.getProperty(AuthFile.CredentialSettings.VAULT_SUFFIX.toString())); + } + return authFile; + } + + private static boolean isJsonBased(String content) { + return content.startsWith("{"); + } + + /** + * @return an ApplicationTokenCredentials object from the information in this class + */ + private TokenCredential generateCredential() { + if (clientSecret != null) { + return new ClientSecretCredentialBuilder() + .tenantId(tenantId) + .clientId(clientId) + .clientSecret(clientSecret) + .authorityHost(environment.getActiveDirectoryEndpoint()) + .build(); + } else if (clientCertificate != null) { + ClientCertificateCredentialBuilder builder = new ClientCertificateCredentialBuilder() + .tenantId(tenantId) + .clientId(clientId) + .authorityHost(environment.getActiveDirectoryEndpoint()); + if (clientCertificatePassword != null) { + builder.pfxCertificate(clientCertificate, clientCertificatePassword); + } else { + builder.pemCertificate(clientCertificate); + } + return builder.build(); + } else { + ClientLogger logger = new ClientLogger(this.getClass()); + throw logger.logExceptionAsError( + new IllegalArgumentException("Please specify either a client key or a client certificate.")); + } + } + + /** + * @return the subscription ID. + */ + public String getSubscriptionId() { + return this.subscriptionId; + } + + /** + * @return the tenant ID. + */ + public String getTenantId() { + return this.tenantId; + } + + /** + * @return the environment. + */ + public AzureEnvironment getEnvironment() { + return this.environment; + } + + /** + * @return the client ID. + */ + public String getClientId() { + return this.clientId; + } + + /** + * @return the credential. + */ + public TokenCredential getCredential() { + if (this.credential == null) { + this.credential = generateCredential(); + } + return credential; + } + + /** + * Contains the keys of the settings in a Properties file to read credentials from. + */ + private enum CredentialSettings { + /** + * The subscription GUID. + */ + SUBSCRIPTION_ID("subscription"), + /** + * The tenant GUID or domain. + */ + TENANT_ID("tenant"), + /** + * The client id for the client application. + */ + CLIENT_ID("client"), + /** + * The client secret for the service principal. + */ + CLIENT_KEY("key"), + /** + * The client certificate for the service principal. + */ + CLIENT_CERT("certificate"), + /** + * The password for the client certificate for the service principal. + */ + CLIENT_CERT_PASS("certificatePassword"), + /** + * The management endpoint. + */ + MANAGEMENT_URI("managementURI"), + /** + * The base URL to the current Azure environment. + */ + BASE_URL("baseURL"), + /** + * The URL to Active Directory authentication. + */ + AUTH_URL("authURL"), + /** + * The URL to Active Directory Graph. + */ + GRAPH_URL("graphURL"), + /** + * The suffix of Key Vaults. + */ + VAULT_SUFFIX("vaultSuffix"); + + /** + * The name of the key in the properties file. + */ + private final String name; + + CredentialSettings(String name) { + this.name = name; + } + + @Override + public String toString() { + return this.name; + } + } +} diff --git a/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/TestDelayProvider.java b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/TestDelayProvider.java new file mode 100644 index 000000000000..45fc43fc22f2 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/TestDelayProvider.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.resourcemanager.test.utils; + +import com.azure.core.management.provider.DelayProvider; + +import java.time.Duration; + +/** + * Class helps thread sleep in tests. + */ +public class TestDelayProvider implements DelayProvider { + + private final boolean isLiveMode; + + /** + * Constructor of TestDelayProvider + * + * @param isLiveMode the boolean flag for test mode + */ + public TestDelayProvider(boolean isLiveMode) { + this.isLiveMode = isLiveMode; + } + + @Override + public Duration getDelayDuration(Duration delay) { + return isLiveMode ? delay : Duration.ofSeconds(1); + } +} diff --git a/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/TestIdentifierProvider.java b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/TestIdentifierProvider.java new file mode 100644 index 000000000000..bb53eb737f75 --- /dev/null +++ b/sdk/resourcemanager/azure-resourcemanager-test/src/main/java/com/azure/resourcemanager/test/utils/TestIdentifierProvider.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.resourcemanager.test.utils; + +import com.azure.core.management.provider.IdentifierProvider; +import com.azure.core.test.utils.TestResourceNamer; + +/** + * Class helps generate unique identifier. + */ +public class TestIdentifierProvider implements IdentifierProvider { + + private final TestResourceNamer testResourceNamer; + + /** + * Constructor of TestIdentifierProvider + * + * @param testResourceNamer the test resource namer + */ + public TestIdentifierProvider(TestResourceNamer testResourceNamer) { + this.testResourceNamer = testResourceNamer; + } + + @Override + public String randomName(String prefix, int maxLen) { + return testResourceNamer.randomName(prefix, maxLen); + } + + @Override + public String randomUuid() { + return testResourceNamer.randomUuid(); + } +} diff --git a/sdk/resourcemanager/pom.xml b/sdk/resourcemanager/pom.xml index 7161cbc31f1a..2fa6d0f6ba8e 100644 --- a/sdk/resourcemanager/pom.xml +++ b/sdk/resourcemanager/pom.xml @@ -29,5 +29,6 @@ azure-resourcemanager-samples azure-resourcemanager-sql azure-resourcemanager-storage + azure-resourcemanager-test