diff --git a/libraryValidations/Spring/validation-tests/pom.xml b/libraryValidations/Spring/validation-tests/pom.xml index b13988a..7736b2b 100644 --- a/libraryValidations/Spring/validation-tests/pom.xml +++ b/libraryValidations/Spring/validation-tests/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.3 + 3.5.5 com.microsoft @@ -37,7 +37,7 @@ com.azure.spring spring-cloud-azure-feature-management - 5.17.0 + 6.0.0-beta.2 diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicTelemetryTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicTelemetryTests.java new file mode 100644 index 0000000..7444a03 --- /dev/null +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicTelemetryTests.java @@ -0,0 +1,81 @@ +package com.microsoft.validation_tests; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.IOException; +import java.util.Iterator; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import com.azure.spring.cloud.feature.management.telemetry.LoggerTelemetryPublisher; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.read.ListAppender; + +@SpringJUnitConfig +@TestPropertySource(locations = "file:./../../../Samples/BasicTelemetry.sample.json", factory = YamlPropertySourceFactory.class) +@SpringBootTest(classes = { SpringBootTest.class, Filters.class }) +@EnableConfigurationProperties +@ComponentScan(basePackages = { "com.azure.spring.cloud.feature.management" }) +class BasicTelemetryTests extends ValidationTestsApplicationTests { + + private Logger publisherLogger; + + private ListAppender listAppender; + + @BeforeAll + public static void setUpLogging() { + // Force SLF4J to initialize + LoggerFactory.getLogger(BasicTelemetryTests.class).info("Initializing SLF4J in test"); + } + + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + + org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger(LoggerTelemetryPublisher.class); + + // Check if we can cast to Logback's Logger + if (slf4jLogger instanceof ch.qos.logback.classic.Logger) { + publisherLogger = (ch.qos.logback.classic.Logger) slf4jLogger; + + // Create a new ListAppender for each test + listAppender = new ListAppender<>(); + listAppender.start(); + + // Remove any existing appenders of this type first + for (Iterator> it = publisherLogger.iteratorForAppenders(); it.hasNext();) { + Appender appender = it.next(); + if (appender instanceof ListAppender) { + publisherLogger.detachAppender(appender); + } + } + + // Add the fresh appender + publisherLogger.addAppender(listAppender); + } else { + assumeTrue( + false, + "Tests require Logback implementation, but found: " + slf4jLogger.getClass().getName() + ); + } + } + + @Test + void validateTest() throws IOException { + runTests("BasicTelemetry", listAppender); + } + +} diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicVariantTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicVariantTests.java index 03534a6..e871552 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicVariantTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/BasicVariantTests.java @@ -18,7 +18,7 @@ class BasicVariantTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("BasicVariant"); + runTests("BasicVariant", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/NoFiltersTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/NoFiltersTests.java index 8c86301..330d878 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/NoFiltersTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/NoFiltersTests.java @@ -18,7 +18,7 @@ class NoFiltersTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("NoFilters"); + runTests("NoFilters", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/RequirementTypeTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/RequirementTypeTests.java index 41ef5e4..624b84b 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/RequirementTypeTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/RequirementTypeTests.java @@ -18,7 +18,7 @@ class RequirementTypeTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("RequirementType"); + runTests("RequirementType", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterModifiedTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterModifiedTests.java index 9f08493..41884b5 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterModifiedTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterModifiedTests.java @@ -18,7 +18,7 @@ class TargetingFilterModifiedTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("TargetingFilter.modified"); + runTests("TargetingFilter.modified", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterTests.java index d14306a..ecd9f3c 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TargetingFilterTests.java @@ -18,7 +18,7 @@ class TargetingFilterTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("TargetingFilter"); + runTests("TargetingFilter", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TimeWindowFilterTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TimeWindowFilterTests.java index 31eccbb..c87f83a 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TimeWindowFilterTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/TimeWindowFilterTests.java @@ -18,7 +18,7 @@ class TimeWindowFilterTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("TimeWindowFilter"); + runTests("TimeWindowFilter", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/ValidationTestsApplicationTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/ValidationTestsApplicationTests.java index 3f79a85..25bb72b 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/ValidationTestsApplicationTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/ValidationTestsApplicationTests.java @@ -1,13 +1,14 @@ package com.microsoft.validation_tests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,13 +16,17 @@ import com.azure.spring.cloud.feature.management.FeatureManager; import com.azure.spring.cloud.feature.management.models.Variant; -import com.azure.spring.cloud.feature.management.validation_tests.models.ValidationTestCase; -import com.azure.spring.cloud.feature.management.validation_tests.models.VariantResult; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.TypeFactory; +import com.microsoft.validation_tests.models.ValidationTestCase; +import com.microsoft.validation_tests.models.VariantResult; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; class ValidationTestsApplicationTests { @@ -37,13 +42,27 @@ class ValidationTestsApplicationTests { private final String inputsUser = "User"; private final String inputsGroups = "Groups"; + + static final String EVENT_NAME = "FeatureEvaluation"; + + static final String FEATURE_NAME = "FeatureName"; + + static final String ENABLED = "Enabled"; + + static final String REASON = "VariantAssignmentReason"; + + static final String VERSION = "Version"; + + static final String EVALUATION_EVENT_VERSION = "1.1.0"; + + static final String APPLICATION_INSIGHTS_CUSTOM_EVENT_KEY = "microsoft.custom_event.name"; @Autowired private FeatureManager featureManager; @Autowired private TargetingFilterTestContextAccessor accessor; - void runTests(String name) throws IOException { + void runTests(String name, ListAppender listAppender) throws IOException { LOGGER.debug("Running test case from file: " + name); final File testsFile = new File(PATH + name + TEST_FILE_POSTFIX); List testCases = readTestcasesFromFile(testsFile); @@ -76,6 +95,21 @@ void runTests(String name) throws IOException { assertEquals(variantResult.getResult().getConfigurationValue(), getVariantResult.getValue()); } + + if (testCase.getTelemetry() != null) { + ILoggingEvent logEvent = getEvent(listAppender.list, testCase.getFeatureFlagName()); + Map mdcMap = logEvent.getMDCPropertyMap(); + Map expectedProperties = testCase.getTelemetry().getEventProperties(); + + assertEquals(EVENT_NAME, logEvent.getMessage()); + assertEquals(Level.INFO, logEvent.getLevel()); + assertEquals(expectedProperties.get(REASON), mdcMap.get(REASON)); + assertEquals(testCase.getFeatureFlagName(), mdcMap.get(FEATURE_NAME)); + assertEquals("false", mdcMap.get(ENABLED)); + assertEquals(EVALUATION_EVENT_VERSION, mdcMap.get(VERSION)); + assertEquals(EVENT_NAME, mdcMap.get(APPLICATION_INSIGHTS_CUSTOM_EVENT_KEY)); + + } } } @@ -96,5 +130,18 @@ private List readTestcasesFromFile(File testFile) throws IOE ValidationTestCase.class); return OBJECT_MAPPER.readValue(jsonString, typeReference); } + + ILoggingEvent getEvent(List events, String featureName) { + for (ILoggingEvent event : events) { + if (featureName.equals(event.getMDCPropertyMap().get(FEATURE_NAME))) { + return event; + } + } + assumeTrue( + false, + "Log event not found for feature: " + featureName + ); + return null; // This line will never be reached due to the assumption above + } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/VariantAssignmentTests.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/VariantAssignmentTests.java index 56d1121..824fbaf 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/VariantAssignmentTests.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/VariantAssignmentTests.java @@ -18,7 +18,7 @@ class VariantAssignmentTests extends ValidationTestsApplicationTests { @Test void validateTest() throws IOException { - runTests("VariantAssignment"); + runTests("VariantAssignment", null); } } diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/TelemetryResult.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/TelemetryResult.java new file mode 100644 index 0000000..5efeb91 --- /dev/null +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/TelemetryResult.java @@ -0,0 +1,34 @@ +package com.microsoft.validation_tests.models; + +import java.util.Map; + +public class TelemetryResult { + + private String eventName; + private Map eventProperties; + /** + * @return the eventName + */ + public String getEventName() { + return eventName; + } + /** + * @param eventName the eventName to set + */ + public void setEventName(String eventName) { + this.eventName = eventName; + } + /** + * @return the eventProperties + */ + public Map getEventProperties() { + return eventProperties; + } + /** + * @param eventProperties the eventProperties to set + */ + public void setEventProperties(Map eventProperties) { + this.eventProperties = eventProperties; + } + +} diff --git a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/ValidationTestCase.java b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/ValidationTestCase.java index 218d1c5..ce3b211 100644 --- a/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/ValidationTestCase.java +++ b/libraryValidations/Spring/validation-tests/src/test/java/com/microsoft/validation_tests/models/ValidationTestCase.java @@ -6,94 +6,115 @@ public class ValidationTestCase { private String friendlyName; + private String featureFlagName; + private LinkedHashMap inputs; + private IsEnabled isEnabled; + private VariantResult variant; + private String description; + private TelemetryResult telemetry; + /** * @return friendly name of test case - * */ + */ public String getFriendlyName() { return friendlyName; } /** * @param friendlyName the friendly name of test case - * */ + */ public void setFriendlyName(String friendlyName) { this.friendlyName = friendlyName; } /** * @return the name of feature flag - * */ + */ public String getFeatureFlagName() { return featureFlagName; } /** * @param featureFlagName the name of feature flag - * */ + */ public void setFeatureFlagName(String featureFlagName) { this.featureFlagName = featureFlagName; } /** * @return the inputs of feature flag - * */ + */ public LinkedHashMap getInputs() { return inputs; } /** * @param inputs the inputs of feature flag - * */ + */ public void setInputs(LinkedHashMap inputs) { this.inputs = inputs; } /** * @return IsEnabled object to represent result of feature flag, enabled or exception - * */ + */ public IsEnabled getIsEnabled() { return isEnabled; } /** * @param isEnabled the result of feature flag, enabled or exception - * */ + */ public void setIsEnabled(IsEnabled isEnabled) { this.isEnabled = isEnabled; } /** * @return variant - * */ + */ public VariantResult getVariant() { return variant; } /** * @param variant the variant of test case - * */ + */ public void setVariant(VariantResult variant) { this.variant = variant; } /** * @return description - * */ + */ public String getDescription() { return description; } /** * @param description the description of test case - * */ + */ public void setDescription(String description) { this.description = description; } -} + /** + * @return the telemetry + */ + public TelemetryResult getTelemetry() { + return telemetry; + } + + /** + * @param telemetry the telemetry to set + */ + public void setTelemetry(TelemetryResult telemetry) { + this.telemetry = telemetry; + } + +}