Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bd6c6be
add feature management common test cases
ivywei0125 Jun 30, 2024
7a582c4
add test case for no filters
ivywei0125 Jun 30, 2024
73b7702
initialize feature manager and call `featureManager.isEnabled()` to c…
ivywei0125 Jul 1, 2024
6c80093
update the schema of test case
ivywei0125 Jul 2, 2024
f9c7db7
get sample file and tests file by listFiles api
ivywei0125 Jul 3, 2024
745943e
update the todo comment.
ivywei0125 Aug 13, 2024
3208660
use `getContextClassLoader().getResource` to get the resource folder …
ivywei0125 Aug 13, 2024
9f90610
address comment: typo wording error fix, add "@SuppressWarnings"
ivywei0125 Aug 19, 2024
9cff351
address comment: typo wording error fix
ivywei0125 Aug 19, 2024
9a7c544
address comment: update member name
ivywei0125 Aug 19, 2024
f4e8833
add running log
ivywei0125 Aug 19, 2024
b23e1af
use contains to filter out the "TargetingFilter.sample.json"
ivywei0125 Aug 19, 2024
a7ffe46
add some info log
ivywei0125 Aug 19, 2024
8234479
sort the file list
ivywei0125 Aug 20, 2024
94ceef2
move to another the package to avoid making feature manager construct…
ivywei0125 Aug 20, 2024
c50b3a6
address comment: use logging, remove else
ivywei0125 Aug 22, 2024
df174c1
address comment: throw exception when empty "feature_management" sect…
ivywei0125 Aug 22, 2024
1117374
address comment: rename symbol
ivywei0125 Aug 22, 2024
7d77d46
Update ValidationsTest.java
mrm9084 Aug 26, 2024
0f96591
fixing linting
mrm9084 Aug 26, 2024
7eccbdc
address comment: ignore both targeting filter test case
ivywei0125 Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.when;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.azure.spring.cloud.feature.management.filters.TargetingFilter;
import com.azure.spring.cloud.feature.management.filters.TargetingFilterTestContextAccessor;
import com.azure.spring.cloud.feature.management.filters.TimeWindowFilter;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
import com.azure.spring.cloud.feature.management.validationstests.models.ValidationTestCase;
import com.fasterxml.jackson.core.type.TypeReference;
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;

@ExtendWith(SpringExtension.class)
public class ValidationsTest {
@Mock
private ApplicationContext context;

@Mock
private FeatureManagementConfigProperties configProperties;

private static final Logger LOGGER = LoggerFactory.getLogger(ValidationsTest.class);

private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();

private static final String TEST_CASE_FOLDER_PATH = "validations-tests";

private final String inputsUser = "user";

private final String inputsGroups = "groups";

private static final String SAMPLE_FILE_NAME_FILTER = "sample";

private static final String TESTS_FILE_NAME_FILTER = "tests";

@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
when(configProperties.isFailFast()).thenReturn(true);
when(context.getBean(Mockito.contains("TimeWindow"))).thenReturn(new TimeWindowFilter());
}

@AfterEach
public void cleanup() throws Exception {
MockitoAnnotations.openMocks(this).close();
}

private boolean hasException(ValidationTestCase testCase) {
final String exceptionStr = testCase.getIsEnabled().getException();
return exceptionStr != null && !exceptionStr.isEmpty();
}

private boolean hasInput(ValidationTestCase testCase) {
final LinkedHashMap<String, Object> inputsMap = testCase.getInputs();
return inputsMap != null && !inputsMap.isEmpty();
}

private static File[] getFileList(String fileNameFilter) {
final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(TEST_CASE_FOLDER_PATH);
assert folderUrl != null;

final File folderFile = new File(folderUrl.getFile());
final File[] filteredFiles = folderFile
.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter));
assert filteredFiles != null;

Arrays.sort(filteredFiles, Comparator.comparing(File::getName));
return filteredFiles;
}

private List<ValidationTestCase> readTestcasesFromFile(File testFile) throws IOException {
final String jsonString = Files.readString(testFile.toPath());
final CollectionType typeReference = TypeFactory.defaultInstance().constructCollectionType(List.class,
ValidationTestCase.class);
return OBJECT_MAPPER.readValue(jsonString, typeReference);
}

@SuppressWarnings("unchecked")
private static LinkedHashMap<String, Object> readConfigurationFromFile(File sampleFile) throws IOException {
final String jsonString = Files.readString(sampleFile.toPath());
final LinkedHashMap<String, Object> configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() {
});
final Object featureManagementSection = configurations.get("feature_management");
if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) {
return (LinkedHashMap<String, Object>) featureManagementSection;
}
throw new IllegalArgumentException("feature_management part is not a map");
}

static Stream<Arguments> testProvider() throws IOException {
List<Arguments> arguments = new ArrayList<>();
File[] files = getFileList(TESTS_FILE_NAME_FILTER);

final File[] sampleFiles = getFileList(SAMPLE_FILE_NAME_FILTER);
List<FeatureManagementProperties> properties = new ArrayList<>();
for (File sampleFile : sampleFiles) {
final FeatureManagementProperties managementProperties = new FeatureManagementProperties();
managementProperties.putAll(readConfigurationFromFile(sampleFile));
properties.add(managementProperties);
}

for (int i = 0; i < files.length; i++) {
if (files[i].getName().contains(("TargetingFilter"))) {
continue; // TODO(mametcal). Not run the test case until we release the little endian fix
}
arguments.add(Arguments.of(files[i].getName(), files[i], properties.get(i)));
}

return arguments.stream();
}

@ParameterizedTest(name = "{0}")
@MethodSource("testProvider")
void validationTest(String name, File testsFile, FeatureManagementProperties managementProperties)
throws IOException {
LOGGER.debug("Running test case from file: " + name);
final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties);
List<ValidationTestCase> testCases = readTestcasesFromFile(testsFile);
for (ValidationTestCase testCase : testCases) {
LOGGER.debug("Test case : " + testCase.getDescription());
if (hasException(testCase)) { // TODO(mametcal). Currently we didn't throw the exception when parameter is
// invalid
assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName()));
continue;
}
if (hasInput(testCase)) { // Set inputs
final Object userObj = testCase.getInputs().get(inputsUser);
final Object groupsObj = testCase.getInputs().get(inputsGroups);
final String user = userObj != null ? userObj.toString() : null;
@SuppressWarnings("unchecked")
final List<String> groups = groupsObj != null ? (List<String>) groupsObj : null;
when(context.getBean(Mockito.contains("Targeting")))
.thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups)));
}

final Boolean result = featureManager.isEnabled(testCase.getFeatureFlagName());
assertEquals(result.toString(), testCase.getIsEnabled().getResult());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
import com.azure.spring.cloud.feature.management.implementation.TestConfiguration;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.TargetingException;
import com.azure.spring.cloud.feature.management.targeting.TargetingContext;
import com.azure.spring.cloud.feature.management.targeting.TargetingContextAccessor;
import com.azure.spring.cloud.feature.management.targeting.TargetingEvaluationOptions;

@SpringBootTest(classes = { TestConfiguration.class, SpringBootTest.class })
Expand Down Expand Up @@ -48,10 +46,10 @@ public void targetedUser() {
parameters.put(GROUPS, new LinkedHashMap<String, Object>());
parameters.put(DEFAULT_ROLLOUT_PERCENTAGE, 0);
parameters.put("Exclusion", emptyExclusion());

Map<String, Object> excludes = new LinkedHashMap<>();
Map<String, String> excludedGroups = new LinkedHashMap<>();

excludes.put(GROUPS, excludedGroups);

context.setParameters(parameters);
Expand Down Expand Up @@ -341,20 +339,20 @@ public void excludeUser() {
TargetingFilter filter = new TargetingFilter(new TargetingFilterTestContextAccessor("Doe", null));

assertTrue(filter.evaluate(context));

// Now the users is excluded
Map<String, Object> excludes = new LinkedHashMap<>();
Map<String, String> excludedUsers = new LinkedHashMap<>();
excludedUsers.put("0", "Doe");

excludes.put(USERS, excludedUsers);
parameters.put("Exclusion", excludes);

context.setParameters(parameters);

assertFalse(filter.evaluate(context));
}

@Test
public void excludeGroup() {
FeatureFilterEvaluationContext context = new FeatureFilterEvaluationContext();
Expand All @@ -380,20 +378,20 @@ public void excludeGroup() {
TargetingFilter filter = new TargetingFilter(new TargetingFilterTestContextAccessor(null, targetedGroups));

assertTrue(filter.evaluate(context));

// Now the users is excluded
Map<String, Object> excludes = new LinkedHashMap<>();
Map<String, String> excludedGroups = new LinkedHashMap<>();
excludedGroups.put("0", "g1");

excludes.put(GROUPS, excludedGroups);
parameters.put("Exclusion", excludes);

context.setParameters(parameters);

assertFalse(filter.evaluate(context));
}

private Map<String, Object> emptyExclusion() {
Map<String, Object> excludes = new LinkedHashMap<>();
List<String> excludedUsers = new ArrayList<>();
Expand All @@ -402,23 +400,4 @@ private Map<String, Object> emptyExclusion() {
excludes.put(GROUPS, excludedGroups);
return excludes;
}

class TargetingFilterTestContextAccessor implements TargetingContextAccessor {

private String user;

private ArrayList<String> groups;

TargetingFilterTestContextAccessor(String user, ArrayList<String> groups) {
this.user = user;
this.groups = groups;
}

@Override
public void configureTargetingContext(TargetingContext context) {
context.setUserId(user);
context.setGroups(groups);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management.filters;

import com.azure.spring.cloud.feature.management.targeting.TargetingContext;
import com.azure.spring.cloud.feature.management.targeting.TargetingContextAccessor;

import java.util.List;

public class TargetingFilterTestContextAccessor implements TargetingContextAccessor {

private String user;

private List<String> groups;

public TargetingFilterTestContextAccessor(String user, List<String> groups) {
this.user = user;
this.groups = groups;
}

@Override
public void configureTargetingContext(TargetingContext context) {
context.setUserId(user);
context.setGroups(groups);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management.validationstests.models;

public class IsEnabled {
private String result;
private String exception;

/**
* @return result
* */
public String getResult() {
return result;
}

/**
* @param result the result of validation test case
* */
public void setResult(String result) {
this.result = result;
}

/**
* @return exception
* */
public String getException() {
return exception;
}

/**
* @param exception the exception message throws when run test case
* */
public void setException(String exception) {
this.exception = exception;
}
}
Loading