diff --git a/sdk/core/azure-core-test/pom.xml b/sdk/core/azure-core-test/pom.xml index e6a918698e92a..45501824e3708 100644 --- a/sdk/core/azure-core-test/pom.xml +++ b/sdk/core/azure-core-test/pom.xml @@ -59,6 +59,12 @@ 5.4.2 test + + org.junit.jupiter + junit-jupiter-params + 5.4.2 + test + org.slf4j slf4j-api diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/InterceptorManager.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/InterceptorManager.java index 5b07dd2051654..2c411e68b96e7 100644 --- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/InterceptorManager.java +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/InterceptorManager.java @@ -14,6 +14,7 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.URL; import java.util.HashMap; import java.util.Map; @@ -21,14 +22,16 @@ /** * A class that keeps track of network calls by either reading the data from an existing test session record or - * recording the network calls in memory. Test session records are saved or read from: - * "session-records/{@code testName}.json" + * recording the network calls in memory. Test session records are saved or read from: "session-records/{@code + * testName}.json" * * * * When the {@link InterceptorManager} is disposed, if the {@code testMode} is {@link TestMode#RECORD}, the network @@ -41,6 +44,8 @@ public class InterceptorManager implements AutoCloseable { private final Map textReplacementRules; private final String testName; private final TestMode testMode; + private final boolean allowedToReadRecordedValues; + private final boolean allowedToRecordValues; // Stores a map of all the HTTP properties in a session // A state machine ensuring a test is always reset before another one is setup @@ -60,44 +65,104 @@ public class InterceptorManager implements AutoCloseable { * * @param testName Name of the test session record. * @param testMode The {@link TestMode} for this interceptor. - * @throws IOException If {@code testMode} is {@link TestMode#PLAYBACK} and an existing test session record could - * not be located or the data could not be deserialized into an instance of {@link RecordedData}. + * @throws UncheckedIOException If {@code testMode} is {@link TestMode#PLAYBACK} and an existing test session record + * could not be located or the data could not be deserialized into an instance of {@link RecordedData}. * @throws NullPointerException If {@code testName} is {@code null}. + * @deprecated Use {@link #InterceptorManager(TestContextManager)} instead. */ - public InterceptorManager(String testName, TestMode testMode) throws IOException { + @Deprecated + public InterceptorManager(String testName, TestMode testMode) { + this(testName, testMode, false); + } + + /** + * Creates a new InterceptorManager that either replays test-session records or saves them. + * + * + * + * The test session records are persisted in the path: "session-records/{@code testName}.json" + * + * @param testContextManager Contextual information about the test being ran, such as test name, {@link TestMode}, + * and others. + * @throws UncheckedIOException If {@code testMode} is {@link TestMode#PLAYBACK} and an existing test session record + * could not be located or the data could not be deserialized into an instance of {@link RecordedData}. + * @throws NullPointerException If {@code testName} is {@code null}. + */ + public InterceptorManager(TestContextManager testContextManager) { + this(testContextManager.getTestName(), testContextManager.getTestMode(), testContextManager.doNotRecordTest()); + } + + private InterceptorManager(String testName, TestMode testMode, boolean doNotRecord) { Objects.requireNonNull(testName, "'testName' cannot be null."); this.testName = testName; this.testMode = testMode; this.textReplacementRules = new HashMap<>(); - this.recordedData = testMode == TestMode.PLAYBACK - ? readDataFromFile() - : new RecordedData(); + this.allowedToReadRecordedValues = (testMode == TestMode.PLAYBACK && !doNotRecord); + this.allowedToRecordValues = (testMode == TestMode.RECORD && !doNotRecord); + + if (allowedToReadRecordedValues) { + this.recordedData = readDataFromFile(); + } else if (allowedToRecordValues) { + this.recordedData = new RecordedData(); + } else { + this.recordedData = null; + } } /** * Creates a new InterceptorManager that replays test session records. It takes a set of - * {@code textReplacementRules}, that can be used by {@link PlaybackClient} to replace values in a - * {@link NetworkCallRecord#getResponse()}. + * {@code textReplacementRules}, that can be used by {@link PlaybackClient} to replace values in a {@link + * NetworkCallRecord#getResponse()}. * * The test session records are read from: "session-records/{@code testName}.json" * * @param testName Name of the test session record. - * @param textReplacementRules A set of rules to replace text in {@link NetworkCallRecord#getResponse()} when playing - * back network calls. - * @throws IOException An existing test session record could not be located or the data could not be deserialized - * into an instance of {@link RecordedData}. + * @param textReplacementRules A set of rules to replace text in {@link NetworkCallRecord#getResponse()} when + * playing back network calls. + * @throws UncheckedIOException An existing test session record could not be located or the data could not be + * deserialized into an instance of {@link RecordedData}. * @throws NullPointerException If {@code testName} or {@code textReplacementRules} is {@code null}. + * @deprecated Use {@link #InterceptorManager(String, Map, boolean)} instead. */ - public InterceptorManager(String testName, Map textReplacementRules) throws IOException { + @Deprecated + public InterceptorManager(String testName, Map textReplacementRules) { + this(testName, textReplacementRules, false); + } + + /** + * Creates a new InterceptorManager that replays test session records. It takes a set of + * {@code textReplacementRules}, that can be used by {@link PlaybackClient} to replace values in a {@link + * NetworkCallRecord#getResponse()}. + * + * The test session records are read from: "session-records/{@code testName}.json" + * + * @param testName Name of the test session record. + * @param textReplacementRules A set of rules to replace text in {@link NetworkCallRecord#getResponse()} when + * playing back network calls. + * @param doNotRecord Flag indicating whether network calls should be record or played back. + * @throws UncheckedIOException An existing test session record could not be located or the data could not be + * deserialized into an instance of {@link RecordedData}. + * @throws NullPointerException If {@code testName} or {@code textReplacementRules} is {@code null}. + */ + public InterceptorManager(String testName, Map textReplacementRules, boolean doNotRecord) { Objects.requireNonNull(testName, "'testName' cannot be null."); Objects.requireNonNull(textReplacementRules, "'textReplacementRules' cannot be null."); this.testName = testName; this.testMode = TestMode.PLAYBACK; + this.allowedToReadRecordedValues = !doNotRecord; + this.allowedToRecordValues = false; - this.recordedData = readDataFromFile(); + this.recordedData = allowedToReadRecordedValues ? readDataFromFile() : null; this.textReplacementRules = textReplacementRules; } @@ -120,7 +185,8 @@ public RecordedData getRecordedData() { } /** - * Gets a new HTTP pipeline policy that records network calls and its data is managed by {@link InterceptorManager}. + * Gets a new HTTP pipeline policy that records network calls and its data is managed by {@link + * InterceptorManager}. * * @return HttpPipelinePolicy to record network calls. */ @@ -145,27 +211,24 @@ public HttpClient getPlaybackClient() { */ @Override public void close() { - switch (testMode) { - case RECORD: - try { - writeDataToFile(); - } catch (IOException e) { - logger.error("Unable to write data to playback file.", e); - } - break; - case PLAYBACK: - // Do nothing - break; - default: - logger.error("==> Unknown AZURE_TEST_MODE: {}", testMode); - break; + if (allowedToRecordValues) { + try { + writeDataToFile(); + } catch (IOException e) { + logger.error("Unable to write data to playback file.", e); + } } } - private RecordedData readDataFromFile() throws IOException { + private RecordedData readDataFromFile() { File recordFile = getRecordFile(testName); ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); - return mapper.readValue(recordFile, RecordedData.class); + + try { + return mapper.readValue(recordFile, RecordedData.class); + } catch (IOException ex) { + throw logger.logExceptionAsWarning(new UncheckedIOException(ex)); + } } /* @@ -183,10 +246,11 @@ private File getRecordFile(String testName) { File playbackFile = new File(getRecordFolder(), testName + ".json"); if (!playbackFile.exists()) { - throw logger.logExceptionAsError(new RuntimeException(String.format( - "Missing playback file. File path: %s. ", playbackFile))); + throw logger.logExceptionAsError(new RuntimeException(String.format( + "Missing playback file. File path: %s. ", playbackFile.getPath()))); } - logger.info("==> Playback file path: " + playbackFile); + + logger.info("==> Playback file path: " + playbackFile.getPath()); return playbackFile; } @@ -218,7 +282,8 @@ private File createRecordFile(String testName) throws IOException { } /** - * Add text replacement rule (regex as key, the replacement text as value) into {@link InterceptorManager#textReplacementRules} + * Add text replacement rule (regex as key, the replacement text as value) into {@link + * InterceptorManager#textReplacementRules} * * @param regex the pattern to locate the position of replacement * @param replacement the replacement text diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java index e1d16afb4d100..a1294ddebbe42 100644 --- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java @@ -2,21 +2,21 @@ // Licensed under the MIT License. package com.azure.core.test; -import com.azure.core.util.Configuration; import com.azure.core.test.utils.TestResourceNamer; +import com.azure.core.util.Configuration; import com.azure.core.util.logging.ClientLogger; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeAll; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.Locale; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.util.Locale; + /** * Base class for running live and playback tests using {@link InterceptorManager}. */ @@ -29,6 +29,8 @@ public abstract class TestBase implements BeforeEachCallback { protected InterceptorManager interceptorManager; protected TestResourceNamer testResourceNamer; + protected TestContextManager testContextManager; + private ExtensionContext extensionContext; /** @@ -53,16 +55,16 @@ public void beforeEach(ExtensionContext extensionContext) { */ @BeforeEach public void setupTest(TestInfo testInfo) { - final String testName = testInfo.getTestMethod().get().getName(); - logger.info("Test Mode: {}, Name: {}", testMode, testName); + this.testContextManager = new TestContextManager(testInfo.getTestMethod().get(), testMode); + logger.info("Test Mode: {}, Name: {}", testMode, testContextManager.getTestName()); try { - interceptorManager = new InterceptorManager(testName, testMode); - } catch (IOException e) { - logger.error("Could not create interceptor for {}", testName, e); + interceptorManager = new InterceptorManager(testContextManager); + } catch (UncheckedIOException e) { + logger.error("Could not create interceptor for {}", testContextManager.getTestName(), e); Assertions.fail(); } - testResourceNamer = new TestResourceNamer(testName, testMode, interceptorManager.getRecordedData()); + testResourceNamer = new TestResourceNamer(testContextManager, interceptorManager.getRecordedData()); beforeTest(); } @@ -73,8 +75,10 @@ public void setupTest(TestInfo testInfo) { */ @AfterEach public void teardownTest(TestInfo testInfo) { - afterTest(); - interceptorManager.close(); + if (testContextManager.didTestRun()) { + afterTest(); + interceptorManager.close(); + } } /** @@ -133,4 +137,22 @@ private static TestMode initializeTestMode() { logger.info("Environment variable '{}' has not been set yet. Using 'Playback' mode.", AZURE_TEST_MODE); return TestMode.PLAYBACK; } + + /** + * Sleeps the test for the given amount of milliseconds if {@link TestMode} isn't {@link TestMode#PLAYBACK}. + * + * @param millis Number of milliseconds to sleep the test. + * @throws IllegalStateException If the sleep is interrupted. + */ + protected void sleepIfRunningAgainstService(long millis) { + if (testMode == TestMode.PLAYBACK) { + return; + } + + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + throw logger.logExceptionAsWarning(new IllegalStateException(ex)); + } + } } diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestContextManager.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestContextManager.java new file mode 100644 index 0000000000000..6e7eb696b77c9 --- /dev/null +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestContextManager.java @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.core.test; + +import com.azure.core.test.annotation.DoNotRecord; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * This class handles managing context about a test, such as custom testing annotations and verifying whether the test + * is capable of running. + */ +public class TestContextManager { + private final String testName; + private final TestMode testMode; + private final boolean doNotRecord; + private final boolean skipInPlayback; + private final boolean testRan; + + /** + * Constructs a {@link TestContextManager} based on the test method. + * + * @param testMethod Test method being ran. + * @param testMode The {@link TestMode} the test is running in. + */ + public TestContextManager(Method testMethod, TestMode testMode) { + this.testName = testMethod.getName(); + this.testMode = testMode; + + DoNotRecord doNotRecordAnnotation = testMethod.getAnnotation(DoNotRecord.class); + if (doNotRecordAnnotation != null) { + this.doNotRecord = true; + this.skipInPlayback = doNotRecordAnnotation.skipInPlayback(); + } else { + this.doNotRecord = false; + this.skipInPlayback = false; + } + + this.testRan = !(skipInPlayback && testMode == TestMode.PLAYBACK); + assumeTrue(testRan, "Test does not allow playback and was ran in 'TestMode.PLAYBACK'"); + } + + /** + * Returns the name of the test being ran. + * + * @return The test name. + */ + public String getTestName() { + return testName; + } + + /** + * Returns the mode being used to run tests. + * + * @return The {@link TestMode} being used to run tests. + */ + public TestMode getTestMode() { + return testMode; + } + + /** + * Returns whether the test should have its network calls recorded during a {@link TestMode#RECORD record} test + * run. + * + * @return Flag indicating whether to record test network calls. + */ + public boolean doNotRecordTest() { + return doNotRecord; + } + + /** + * Returns whether the current test was ran. + * + * @return Flag indicating whether the current test was ran. + */ + public boolean didTestRun() { + return testRan; + } +} diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java index 27b1224ff19f6..5d89b4de093eb 100644 --- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java @@ -10,6 +10,10 @@ public enum TestMode { * Record data from a live test. */ RECORD, + /** + * Run a live test without recording. + */ + LIVE, /** * Playback data from an existing test session. */ diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/DoNotRecord.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/DoNotRecord.java new file mode 100644 index 0000000000000..f2241c79de296 --- /dev/null +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/DoNotRecord.java @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.test.annotation; + +import com.azure.core.test.TestMode; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation given to some tests to indicate that network calls made during the test shouldn't be recorded. + * + *

+ * Pass {@code true} for {@link #skipInPlayback() skipInPlayback} to indicate that the test shouldn't run when tests are + * ran in {@link TestMode#PLAYBACK}. A common case for setting this to {@code true} is when the test has either + * sensitive content that cannot be redacted or calls into code that cannot be mocked. + */ +@Retention(RUNTIME) +@Target({METHOD}) +public @interface DoNotRecord { + + /** + * Returns whether the test will be ignored during a {@link TestMode#PLAYBACK playback} test run. + * + * @return Flag indicating if the test will be ignored during a playback test run. + */ + boolean skipInPlayback() default false; +} diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/package-info.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/package-info.java new file mode 100644 index 0000000000000..f114506b11efd --- /dev/null +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/package-info.java @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +/** + * Package containing annotations test classes for Azure client libraries. + */ +package com.azure.core.test.annotation; diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java index 481d6be0607ba..3b38a1d2e0852 100644 --- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java +++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java @@ -3,12 +3,15 @@ package com.azure.core.test.utils; +import com.azure.core.test.TestContextManager; import com.azure.core.test.TestMode; import com.azure.core.test.models.RecordedData; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; /** * Provides random string names. If the test mode is {@link TestMode#PLAYBACK}, then names are fetched from @@ -16,21 +19,48 @@ * persisted to {@link RecordedData}. */ public class TestResourceNamer extends ResourceNamer { - private final TestMode testMode; private final RecordedData recordedData; + private final boolean allowedToReadRecordedValues; + private final boolean allowedToRecordValues; /** * Constructor of TestResourceNamer * + * @deprecated Use {@link #TestResourceNamer(TestContextManager, RecordedData)} instead. * @param name test name as prefix - * @param testMode the test mode {@link TestMode#PLAYBACK} or {@link TestMode#RECORD} + * @param testMode The {@link TestMode} which the test is running in. * @param recordedData the recorded data with list of network call */ + @Deprecated public TestResourceNamer(String name, TestMode testMode, RecordedData recordedData) { + this(name, testMode, false, recordedData); + } + + /** + * Constructor of TestResourceNamer + * + * @param testContextManager Contextual information about the test being ran, such as test name, {@link TestMode}, + * and others. + * @param recordedData the recorded data with list of network call + * @throws NullPointerException If {@code testMode} isn't {@link TestMode#LIVE}, {@code doNotRecord} is + * {@code false}, and {@code recordedData} is {@code null}. + */ + public TestResourceNamer(TestContextManager testContextManager, RecordedData recordedData) { + this(testContextManager.getTestName(), testContextManager.getTestMode(), testContextManager.doNotRecordTest(), + recordedData); + } + + private TestResourceNamer(String name, TestMode testMode, boolean doNotRecord, RecordedData recordedData) { super(name); - Objects.requireNonNull(recordedData, "'recordedData' cannot be null."); + + // Only need recordedData if the test is running in playback or record. + if (testMode != TestMode.LIVE && !doNotRecord) { + Objects.requireNonNull(recordedData, "'recordedData' cannot be null."); + } + this.recordedData = recordedData; - this.testMode = testMode; + this.allowedToReadRecordedValues = (testMode == TestMode.PLAYBACK && !doNotRecord); + this.allowedToRecordValues = (testMode == TestMode.RECORD && !doNotRecord); } /** @@ -42,13 +72,7 @@ public TestResourceNamer(String name, TestMode testMode, RecordedData recordedDa */ @Override public String randomName(String prefix, int maxLen) { - if (testMode == TestMode.PLAYBACK) { - return recordedData.removeVariable(); - } else { - String name = super.randomName(prefix, maxLen); - recordedData.addVariable(name); - return name; - } + return getValue(readValue -> readValue, () -> super.randomName(prefix, maxLen)); } /** @@ -58,13 +82,7 @@ public String randomName(String prefix, int maxLen) { */ @Override public String randomUuid() { - if (testMode == TestMode.PLAYBACK) { - return recordedData.removeVariable(); - } else { - String uuid = super.randomUuid(); - recordedData.addVariable(uuid); - return uuid; - } + return getValue(readValue -> readValue, super::randomUuid); } /** @@ -73,13 +91,7 @@ public String randomUuid() { * @return OffsetDateTime of UTC now. */ public OffsetDateTime now() { - if (testMode == TestMode.PLAYBACK) { - return OffsetDateTime.parse(recordedData.removeVariable()); - } else { - OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC); - recordedData.addVariable(now.toString()); - return now; - } + return getValue(OffsetDateTime::parse, () -> OffsetDateTime.now(ZoneOffset.UTC)); } /** @@ -89,10 +101,19 @@ public OffsetDateTime now() { * @return the recorded value. */ public String recordValueFromConfig(String value) { - if (testMode == TestMode.PLAYBACK) { - return recordedData.removeVariable(); + return getValue(readValue -> readValue, () -> value); + } + + private T getValue(Function readHandler, Supplier valueSupplier) { + if (allowedToReadRecordedValues) { + return readHandler.apply(recordedData.removeVariable()); } else { - recordedData.addVariable(value); + T value = valueSupplier.get(); + + if (allowedToRecordValues) { + recordedData.addVariable(value.toString()); + } + return value; } } diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/FakeTestClass.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/FakeTestClass.java new file mode 100644 index 0000000000000..7dfe22900ffb5 --- /dev/null +++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/FakeTestClass.java @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.core.test; + +import com.azure.core.test.annotation.DoNotRecord; +import com.azure.core.util.logging.ClientLogger; + +import java.lang.reflect.Method; + +/** + * This is a dummy class used to mock scenarios for {@link TestContextManager}. + */ +public final class FakeTestClass { + public static final Method METHOD_WITHOUT_DONOTRECORD = getTestMethod("testWithoutDoNotRecord"); + public static final Method DONOTRECORD_FALSE_SKIPINPLAYBACK = getTestMethod("testWithDoNotRecordRunInPlayback"); + public static final Method DONOTRECORD_SKIPINPLAYBACK = getTestMethod("testWithDoNotRecordSkipInPlayback"); + + public void testWithoutDoNotRecord() { + } + + @DoNotRecord + public void testWithDoNotRecordRunInPlayback() { + + } + + @DoNotRecord(skipInPlayback = true) + public void testWithDoNotRecordSkipInPlayback() { + } + + private static Method getTestMethod(String methodName) { + try { + return FakeTestClass.class.getMethod(methodName); + } catch (NoSuchMethodException e) { + throw new ClientLogger(FakeTestClass.class).logExceptionAsWarning(new RuntimeException(e)); + } + } +} diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/InterceptorManagerTests.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/InterceptorManagerTests.java new file mode 100644 index 0000000000000..4d953e8a278eb --- /dev/null +++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/InterceptorManagerTests.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.test; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; + +import static com.azure.core.test.FakeTestClass.DONOTRECORD_FALSE_SKIPINPLAYBACK; +import static com.azure.core.test.FakeTestClass.METHOD_WITHOUT_DONOTRECORD; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests for {@link InterceptorManager}. + */ +public class InterceptorManagerTests { + /** + * Validates that {@link InterceptorManager#getRecordedData()} is {@code null} when testing in {@link + * TestMode#LIVE}. + */ + @Test + public void recordedDataIsNullInLiveMode() { + assertNull(new InterceptorManager(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.LIVE)) + .getRecordedData()); + assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.LIVE)) + .getRecordedData()); + } + + /** + * Validates that {@link InterceptorManager#getRecordedData()} is {@code null} when {@code doNotRecord} is passed as + * {@code true}. + */ + @Test + public void recordedDataIsNullWhenDoNotRecord() { + assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.RECORD)) + .getRecordedData()); + assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.LIVE)) + .getRecordedData()); + assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.PLAYBACK)) + .getRecordedData()); + assertNull(new InterceptorManager("testName", new HashMap<>(), true).getRecordedData()); + } +} diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/TestContextManagerTests.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/TestContextManagerTests.java new file mode 100644 index 0000000000000..b36654d276b31 --- /dev/null +++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/TestContextManagerTests.java @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.test; + +import com.azure.core.test.annotation.DoNotRecord; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.opentest4j.TestAbortedException; + +import java.lang.reflect.Method; + +import static com.azure.core.test.FakeTestClass.DONOTRECORD_FALSE_SKIPINPLAYBACK; +import static com.azure.core.test.FakeTestClass.DONOTRECORD_SKIPINPLAYBACK; +import static com.azure.core.test.FakeTestClass.METHOD_WITHOUT_DONOTRECORD; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for {@link TestContextManager}. + */ +public class TestContextManagerTests { + + /** + * Validates that a test method without the {@link DoNotRecord} annotation is allowed to run in all test modes and + * will record network calls and test values. + */ + @ParameterizedTest(name = "[{index}] {displayName}") + @EnumSource(TestMode.class) + public void testWithoutDoNotRecord(TestMode testMode) { + TestContextManager testContextManager = new TestContextManager(METHOD_WITHOUT_DONOTRECORD, testMode); + + assertFalse(testContextManager.doNotRecordTest()); + assertTrue(testContextManager.didTestRun()); + } + + /** + * Validates that a test method with the default {@link DoNotRecord} annotation is allowed to run in all test modes + * but doesn't have its network calls or test values recorded. + */ + @ParameterizedTest(name = "[{index}] {displayName}") + @EnumSource(TestMode.class) + public void testWithDoNotRecordRunInPlayback(TestMode testMode) { + TestContextManager testContextManager = new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, testMode); + + assertTrue(testContextManager.doNotRecordTest()); + assertTrue(testContextManager.didTestRun()); + } + + /** + * Validates that a test method with the {@link DoNotRecord} annotation having {@code skipInPlayback} set to true is + * only allowed to run in {@link TestMode#RECORD} and {@link TestMode#LIVE} and won't have its network calls or test + * values recorded. + */ + @Test + public void testWithDoNotRecordSkipInPlayback() { + Method testMethod = DONOTRECORD_SKIPINPLAYBACK; + + assertThrows(TestAbortedException.class, () -> new TestContextManager(testMethod, TestMode.PLAYBACK)); + + TestContextManager testContextManager = new TestContextManager(testMethod, TestMode.LIVE); + assertTrue(testContextManager.doNotRecordTest()); + assertTrue(testContextManager.didTestRun()); + + testContextManager = new TestContextManager(testMethod, TestMode.RECORD); + assertTrue(testContextManager.doNotRecordTest()); + assertTrue(testContextManager.didTestRun()); + } +} diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/utils/TestResourceNamerTests.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/utils/TestResourceNamerTests.java new file mode 100644 index 0000000000000..94046262148fe --- /dev/null +++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/utils/TestResourceNamerTests.java @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.test.utils; + +import com.azure.core.test.TestContextManager; +import com.azure.core.test.TestMode; +import com.azure.core.test.models.RecordedData; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.util.NoSuchElementException; + +import static com.azure.core.test.FakeTestClass.DONOTRECORD_FALSE_SKIPINPLAYBACK; +import static com.azure.core.test.FakeTestClass.METHOD_WITHOUT_DONOTRECORD; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link TestResourceNamer}. + */ +public class TestResourceNamerTests { + private static final String A_VARIABLE = "aVariable"; + private static final String RANDOM_NAME_PREFIX = "prefix"; + private static final int RANDOM_NAME_LENGTH = 12; + private static final String CONFIG_VALUE = "value"; + + /** + * Validates that a {@link NullPointerException} is thrown if {@code testMode} isn't {@link TestMode#LIVE} and + * {@code doNotRecord} is {@code false}, otherwise no exception is thrown as having no {@link RecordedData} is + * valid in those cases. + */ + @Test + public void nullRecordedData() { + // Doesn't throw when TestMode.LIVE. + assertDoesNotThrow(() -> + new TestResourceNamer(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.LIVE), null)); + + // Doesn't throw when 'doNotRecord' is true. + assertDoesNotThrow(() -> + new TestResourceNamer(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.RECORD), null)); + + // Does throw when TestMode isn't LIVE and doNotRecord = false + assertThrows(NullPointerException.class, () -> + new TestResourceNamer(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.RECORD), null)); + assertThrows(NullPointerException.class, () -> + new TestResourceNamer(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.PLAYBACK), null)); + } + + /** + * Validates that when {@code doNotRecord} is {@code true} and {@code testMode} is {@link TestMode#PLAYBACK} then + * the {@link RecordedData} within the {@link TestResourceNamer} cannot be read. + */ + @Test + public void recordedDataIsNotAllowedToReadRecordedValues() { + TestResourceNamer resourceNamer = new TestResourceNamer(new TestContextManager( + DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.PLAYBACK), getRecordedDataWithValue()); + + assertNotEquals(A_VARIABLE, resourceNamer.randomName("prefix", 12)); + assertNotEquals(A_VARIABLE, resourceNamer.randomUuid()); + assertNotEquals(A_VARIABLE, resourceNamer.now()); + assertEquals(A_VARIABLE, resourceNamer.recordValueFromConfig(A_VARIABLE)); + } + + /** + * Validates that when {@code testMode} is {@link TestMode#LIVE} or {@code doNotRecord} is {@code true} then the + * {@link RecordedData} within the {@link TestResourceNamer} cannot record values generated. + */ + @Test + public void recordedDataIsNotAllowedToRecordValues() { + RecordedData recordedData = new RecordedData(); + + callNamerMethds(new TestResourceNamer( + new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.LIVE), recordedData)); + validateNoRecordingsMade(new TestResourceNamer( + new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.PLAYBACK), recordedData)); + + // Reset the recording data. + recordedData = new RecordedData(); + + callNamerMethds(new TestResourceNamer( + new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.RECORD), recordedData)); + validateNoRecordingsMade(new TestResourceNamer( + new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.PLAYBACK), recordedData)); + } + + private void callNamerMethds(TestResourceNamer resourceNamer) { + resourceNamer.randomName(RANDOM_NAME_PREFIX, RANDOM_NAME_LENGTH); + resourceNamer.randomUuid(); + resourceNamer.now(); + resourceNamer.recordValueFromConfig(CONFIG_VALUE); + } + + private void validateNoRecordingsMade(TestResourceNamer resourceNamer) { + assertNoSuchElementException(() -> resourceNamer.randomName(RANDOM_NAME_PREFIX, RANDOM_NAME_LENGTH)); + assertNoSuchElementException(resourceNamer::randomUuid); + assertNoSuchElementException(resourceNamer::now); + assertNoSuchElementException(() -> resourceNamer.recordValueFromConfig(CONFIG_VALUE)); + } + + private void assertNoSuchElementException(Executable executable) { + assertThrows(NoSuchElementException.class, executable, "Expected 'NoSuchElementException' to be thrown."); + } + + private RecordedData getRecordedDataWithValue() { + RecordedData recordedData = new RecordedData(); + recordedData.addVariable(A_VARIABLE); + + return recordedData; + } +}