From bd6c6be4bebac7ae145fbec771f4847cff94343b Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Sun, 30 Jun 2024 10:29:01 +0800 Subject: [PATCH 01/21] add feature management common test cases --- .../JsonPropertySourceFactory.java | 22 ++ .../validationstests/NoFiltersTest.java | 21 ++ .../validationstests/models/IsEnabled.java | 24 ++ .../models/ValidationJson.java | 52 ++++ .../validationstests/models/Variant.java | 24 ++ .../validations-tests/NoFilters.sample.json | 44 ++++ .../validations-tests/NoFilters.tests.json | 68 ++++++ .../RequirementType.sample.json | 144 +++++++++++ .../RequirementType.tests.json | 68 ++++++ .../TargetingFilter.modified.sample.json | 60 +++++ .../TargetingFilter.modified.tests.json | 98 ++++++++ .../TargetingFilter.sample.json | 60 +++++ .../TargetingFilter.tests.json | 230 ++++++++++++++++++ .../TimeWindowFilter.sample.json | 84 +++++++ .../TimeWindowFilter.tests.json | 57 +++++ 15 files changed, 1056 insertions(+) create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.sample.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.tests.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.sample.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.tests.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.sample.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.tests.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.sample.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.tests.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.sample.json create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java new file mode 100644 index 000000000000..7f6728e15849 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.cloud.feature.management.validationstests; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; + +import java.io.IOException; +import java.util.Map; + +public class JsonPropertySourceFactory + implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + Map readValue = new ObjectMapper().readValue(resource.getInputStream(), Map.class); + return new MapPropertySource("json-property", readValue); + } +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java new file mode 100644 index 000000000000..0e396bcd7d02 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.feature.management.validationstests; + +import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@EnableConfigurationProperties(value = FeatureManagementProperties.class) +@TestPropertySource(value = { "classpath:/validations-tests/NoFilters.sample.json" }, factory= JsonPropertySourceFactory.class) +public class NoFiltersTest { + + @Autowired + private FeatureManagementProperties properties; + +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java new file mode 100644 index 000000000000..4570f0a77cbf --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java @@ -0,0 +1,24 @@ +// 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; + + public String getResult() { + return result; + } + + public String getException() { + return exception; + } + + public void setResult(String result) { + this.result = result; + } + + public void setException(String exception) { + this.exception = exception; + } +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java new file mode 100644 index 000000000000..6ea6f96168c1 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.cloud.feature.management.validationstests.models; + +public class ValidationJson { + private String featureFlagName; + private Object inputs; + private IsEnabled isEnabled; + private Variant variant; + private String description; + + public String getFeatureFlagName() { + return featureFlagName; + } + + public void setFeatureFlagName(String featureFlagName) { + this.featureFlagName = featureFlagName; + } + + public Object getInputs() { + return inputs; + } + + public void setInputs(Object inputs) { + this.inputs = inputs; + } + + public IsEnabled getIsEnabled() { + return isEnabled; + } + + public void setIsEnabled(IsEnabled isEnabled) { + this.isEnabled = isEnabled; + } + + public Variant getVariant() { + return variant; + } + + public void setVariant(Variant variant) { + this.variant = variant; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} + diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java new file mode 100644 index 000000000000..b69f4e51f444 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.cloud.feature.management.validationstests.models; + +public class Variant { + private String result; + private String exception; + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public String getException() { + return exception; + } + + public void setException(String exception) { + this.exception = exception; + } +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.sample.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.sample.json new file mode 100644 index 000000000000..6860ba53c506 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.sample.json @@ -0,0 +1,44 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "BooleanTrue", + "description": "A feature flag with no Filters, that returns true.", + "enabled": true, + "conditions": { + "client_filters": [] + } + }, + { + "id": "BooleanFalse", + "description": "A feature flag with no Filters, that returns false.", + "enabled": false, + "conditions": { + "client_filters": [] + } + }, + { + "id": "InvalidEnabled", + "description": "A feature flag with an invalid 'enabled' value, that returns false.", + "enabled": "invalid", + "conditions": { + "client_filters": [] + } + }, + { + "id": "Minimal", + "enabled": true + }, + { + "id": "NoEnabled" + }, + { + "id": "EmptyConditions", + "description": "A feature flag with no values in conditions, that returns true.", + "enabled": true, + "conditions": { + } + } + ] + } +} \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.tests.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.tests.json new file mode 100644 index 000000000000..01445d679299 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/NoFilters.tests.json @@ -0,0 +1,68 @@ +[ + { + "FeatureFlagName": "BooleanTrue", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "An Enabled Feature Flag with no Filters." + }, + { + "FeatureFlagName": "BooleanFalse", + "Inputs": {}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "A Disabled Feature Flag with no Filters." + }, + { + "FeatureFlagName": "InvalidEnabled", + "Inputs": {}, + "IsEnabled": { + "Exception": "Invalid setting 'enabled' with value 'invalid' for feature 'InvalidEnabled'." + }, + "Variant": { + "Result": null + }, + "Description": "A Feature Flag with an invalid Enabled Value." + }, + { + "FeatureFlagName": "Minimal", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "A Feature Flag with just a key and enabled." + }, + { + "FeatureFlagName": "NoEnabled", + "Inputs": {}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Validates that the default value of enabled is False." + }, + { + "FeatureFlagName": "EmptyConditions", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "A feature flag with no Conditions, returns true as it's enabled." + } +] \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.sample.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.sample.json new file mode 100644 index 000000000000..5bf5b6ecfe5e --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.sample.json @@ -0,0 +1,144 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "DefaultRequirementTypeFirstFilterPassed", + "description": "A feature flag that has multiple filters, but doesn't specify any requirement type, which is the default. Will always return true.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Tue, 27 Jun 2023 06:00:00 GMT" + } + }, + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 29 Jun 2023 07:00:00 GMT", + "End": "Wed, 30 Aug 2023 07:00:00 GMT" + } + } + ] + } + }, + { + "id": "DefaultRequirementTypeLastFilterPassed", + "description": "Same as DefaultRequirementTypeFirstFilterPassed, but filter order is switched. Will always return true.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 29 Jun 2023 07:00:00 GMT", + "End": "Wed, 30 Aug 2023 07:00:00 GMT" + } + }, + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Tue, 27 Jun 2023 06:00:00 GMT" + } + } + ] + } + }, + { + "id": "RequirementTypeAnyFirstFilterPassed", + "description": "Same as DefaultRequirementTypeFirstFilterPassed, but requirement type is specified. Will always return true.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Tue, 27 Jun 2023 06:00:00 GMT" + } + }, + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 29 Jun 2023 07:00:00 GMT", + "End": "Wed, 30 Aug 2023 07:00:00 GMT" + } + } + ], + "requirement_type": "Any" + } + }, + { + "id": "RequirementTypeAnyLastFilterPassed", + "description": "Same as DefaultRequirementTypeLastFilterPassed, but requirement type is specified. Will always return true.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 29 Jun 2023 07:00:00 GMT", + "End": "Wed, 30 Aug 2023 07:00:00 GMT" + } + }, + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Tue, 27 Jun 2023 06:00:00 GMT" + } + } + ], + "requirement_type": "Any" + } + }, + { + "id": "RequirementTypeAllPassed", + "description": "Requirement type All. Will always return true.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "DefaultRolloutPercentage": 100 + } + } + }, + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Tue, 27 Jun 2023 06:00:00 GMT" + } + } + ], + "requirement_type": "All" + } + }, + { + "id": "RequirementTypeAllLastFilterFailed", + "description": "Requirement type All. Will always return false.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "DefaultRolloutPercentage": 100 + } + } + }, + { + "name": "Microsoft.TimeWindow", + "parameters": { + "End": "Tue, 27 Jun 2023 06:00:00 GMT" + } + } + ], + "requirement_type": "All" + } + } + ] + } +} \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.tests.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.tests.json new file mode 100644 index 000000000000..74df4d8c4133 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/RequirementType.tests.json @@ -0,0 +1,68 @@ +[ + { + "FeatureFlagName": "DefaultRequirementTypeFirstFilterPassed", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Feature Flag with two feature filters, first returns true, so it's enabled." + }, + { + "FeatureFlagName": "DefaultRequirementTypeLastFilterPassed", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Feature Flag with two feature filters, second returns true, so it's enabled." + }, + { + "FeatureFlagName": "RequirementTypeAnyFirstFilterPassed", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Feature Flag with two feature filters and requirement type specified as Any. Second filter returns true." + }, + { + "FeatureFlagName": "RequirementTypeAnyLastFilterPassed", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Feature Flag with two feature filters and requirement type specified as Any. Neither filter returns true." + }, + { + "FeatureFlagName": "RequirementTypeAllPassed", + "Inputs": {"user":"Adam"}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Feature Flag with two feature filters and requirement type specified as All. Both filters return true." + }, + { + "FeatureFlagName": "RequirementTypeAllLastFilterFailed", + "Inputs": {"user":"Adam"}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Feature Flag with two feature filters and requirement type specified as All. Only the first filter returns true." + } +] diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.sample.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.sample.json new file mode 100644 index 000000000000..8f332e65c804 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.sample.json @@ -0,0 +1,60 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "ComplexTargeting", + "description": "A feature flag using a targeting filter, that will return true for Alice, Stage1, and 50% of Stage2, and false for Dave and Stage3. The default rollout percentage is 25%.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "Users": [ + "Alice" + ], + "Groups": [ + { + "Name": "Stage1", + "RolloutPercentage": 100 + }, + { + "Name": "Stage2", + "RolloutPercentage": 50 + } + ], + "DefaultRolloutPercentage": 25, + "Exclusion": { + "Users": ["Dave"], + "Groups": ["Stage3"] + } + } + } + } + ] + } + }, + { + "id": "RolloutPercentageUpdate", + "description": "A feature flag using a targeting filter, that will return true 62% of the time.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "Users": [], + "Groups": [], + "DefaultRolloutPercentage": 62, + "Exclusion": {} + } + } + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.tests.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.tests.json new file mode 100644 index 000000000000..1c1425bf3d47 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.modified.tests.json @@ -0,0 +1,98 @@ +[ + { + "FriendlyName": "Aiden62", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden"}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, 62% default rollout Aiden is part of it." + }, + { + "FriendlyName": "Aiden62 - Stage1", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden", "groups":["Stage1"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Aiden62 - Stage2", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden", "groups":["Stage2"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Aiden62 - Stage3", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden", "groups":["Stage3"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Brittney62", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney"}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, 62% default rollout Brittney is part of it." + }, + { + "FriendlyName": "Brittney62 - Stage1", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney", "groups":["Stage1"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Brittney62 - Stage2", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney", "groups":["Stage2"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Brittney62 - Stage3", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney", "groups":["Stage3"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + } +] \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.sample.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.sample.json new file mode 100644 index 000000000000..4342df5d20b0 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.sample.json @@ -0,0 +1,60 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "ComplexTargeting", + "description": "A feature flag using a targeting filter, that will return true for Alice, Stage1, and 50% of Stage2. Dave and Stage3 are excluded. The default rollout percentage is 25%.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "Users": [ + "Alice" + ], + "Groups": [ + { + "Name": "Stage1", + "RolloutPercentage": 100 + }, + { + "Name": "Stage2", + "RolloutPercentage": 50 + } + ], + "DefaultRolloutPercentage": 25, + "Exclusion": { + "Users": ["Dave"], + "Groups": ["Stage3"] + } + } + } + } + ] + } + }, + { + "id": "RolloutPercentageUpdate", + "description": "A feature flag using a targeting filter, that will return true 61% of the time. Changing to 62% makes the user Brittney true.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.Targeting", + "parameters": { + "Audience": { + "Users": [], + "Groups": [], + "DefaultRolloutPercentage": 61, + "Exclusion": {} + } + } + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.tests.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.tests.json new file mode 100644 index 000000000000..60e2f412ab5e --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TargetingFilter.tests.json @@ -0,0 +1,230 @@ +[ + { + "FriendlyName": "DisabledDefaultRollout", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Aiden"}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Aiden is not part of the default rollout." + }, + { + "FriendlyName": "EnabledDefaultRollout", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Blossom"}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Blossom is part of the default rollout." + }, + { + "FriendlyName": "TargetedUser", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Alice"}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Alice is a targeted user." + }, + { + "FriendlyName": "TargetedGroup", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Aiden", "groups":["Stage1"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Aiden is now targeted because Stage1 is 100% rolled out." + }, + { + "FriendlyName": "DisabledTargetedGroup", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"groups":["Stage2"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "empty/no user will hit the 50% rollout of group stage 2, so it is targeted." + }, + { + "FriendlyName": "EnabledTargetedGroup50", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Aiden", "groups":["Stage2"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Aiden who is not part of the default rollout is part of the first 50% of Stage 2." + }, + { + "FriendlyName": "DisabledTargetedGroup50", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Chris", "groups":["Stage2"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Chris is neither part of the default rollout nor part of the first 50% of Stage 2." + }, + { + "FriendlyName": "ExcludedGroup", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"groups":["Stage3"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, the Stage 3 is the group on the exclusion list." + }, + { + "FriendlyName": "ExcludedGroupTargetedUser", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Alice", "groups":["Stage3"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Alice is excluded because she is part of the Stage 3 group, even if she is an included user. " + }, + { + "FriendlyName": "ExcludedGroupDefaultRollout", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Blossom", "groups":["Stage3"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Blossom who was Expected Result by the default rollout is now excluded as part of the Stage 3 group." + }, + { + "FriendlyName": "ExcludedUser", + "FeatureFlagName": "ComplexTargeting", + "Inputs": {"user":"Dave", "groups":["Stage1"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, Dave is on the exclusion list, is still excluded even though he is part of the 100% rolled out Stage 1." + }, + { + "FriendlyName": "Aiden61", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden"}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, 62% default rollout Aiden is part of it." + }, + { + "FriendlyName": "Aiden61 - Stage1", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden", "groups":["Stage1"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Aiden61 - Stage2", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden", "groups":["Stage2"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Aiden61 - Stage3", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Aiden", "groups":["Stage3"]}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Brittney61", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney"}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, 62% default rollout Brittney is not part of it." + }, + { + "FriendlyName": "Brittney61 - Stage1", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney", "groups":["Stage1"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Brittney61 - Stage2", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney", "groups":["Stage2"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + }, + { + "FriendlyName": "Brittney61 - Stage3", + "FeatureFlagName": "RolloutPercentageUpdate", + "Inputs": {"user":"Brittney", "groups":["Stage3"]}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Targeting Filter, group is not part of default rollout calculation, no change." + } +] \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.sample.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.sample.json new file mode 100644 index 000000000000..462277b8f62c --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.sample.json @@ -0,0 +1,84 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "PastTimeWindow", + "description": "A feature flag using a time window filter, that is active from 2023-06-29 07:00:00 to 2023-08-30 07:00:00. Will always return false as the current time is outside the time window.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 29 Jun 2023 07:00:00 GMT", + "End": "Wed, 30 Aug 2023 07:00:00 GMT" + } + } + ] + } + }, + { + "id": "FutureTimeWindow", + "description": "A feature flag using a time window filter, that is active from 3023-06-27 06:00:00 to 3023-06-28 06:05:00. Will always return false as the time window has yet been reached.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Fri, 27 Jun 3023 06:00:00 GMT", + "End": "Sat, 28 Jun 3023 06:05:00 GMT" + } + } + ] + } + }, + { + "id": "PresentTimeWindow", + "description": "A feature flag using a time window filter, that is active from 2023-06-27 06:00:00 to 3023-06-28 06:05:00. Will always return true as we are in the time window.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Thu, 29 Jun 2023 07:00:00 GMT", + "End": "Sat, 28 Jun 3023 06:05:00 GMT" + } + } + ] + } + }, + { + "id": "StartedTimeWindow", + "description": "A feature flag using a time window filter, that will always return true as the current time is within the time window.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "Start": "Tue, 27 Jun 2023 06:00:00 GMT" + } + } + ] + } + }, + { + "id": "WillEndTimeWindow", + "description": "A feature flag using a time window filter, that will always return true as the current time is within the time window.", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Microsoft.TimeWindow", + "parameters": { + "End": "Sat, 28 Jun 3023 06:05:00 GMT" + } + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json new file mode 100644 index 000000000000..2a824fbcd7e8 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json @@ -0,0 +1,57 @@ +[ + { + "FeatureFlagName": "PastTimeWindow", + "Inputs": {}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Time Window filter where both Start and End have already passed." + }, + { + "FeatureFlagName": "FutureTimeWindow", + "Inputs": {}, + "IsEnabled": { + "Result": "false" + }, + "Variant": { + "Result": null + }, + "Description": "Time Window filter where neither Start nor End have happened." + }, + { + "FeatureFlagName": "PresentTimeWindow", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Time Window filter where Start has happend but End hasn't happend." + }, + { + "FeatureFlagName": "StartedTimeWindow", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Time Window filter with a Start that has passed." + }, + { + "FeatureFlagName": "WillEndTimeWindow", + "Inputs": {}, + "IsEnabled": { + "Result": "true" + }, + "Variant": { + "Result": null + }, + "Description": "Time Window filter where the End hasn't passed." + } +] From 7a582c40cd24205e1ea8abad5eae5f64c8227e6d Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Sun, 30 Jun 2024 13:55:01 +0800 Subject: [PATCH 02/21] add test case for no filters --- .../validationstests/NoFiltersTest.java | 25 +++++++++++++++-- .../ValidationsTestsConstants.java | 18 ++++++++++++ .../ValidationsTestsUtils.java | 28 +++++++++++++++++++ ...ationJson.java => ValidationTestCase.java} | 2 +- 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java rename sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/{ValidationJson.java => ValidationTestCase.java} (97%) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java index 0e396bcd7d02..8fc283ac0d31 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java @@ -4,18 +4,39 @@ package com.azure.spring.cloud.feature.management.validationstests; import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties; +import com.azure.spring.cloud.feature.management.validationstests.models.ValidationTestCase; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + @ExtendWith(SpringExtension.class) @EnableConfigurationProperties(value = FeatureManagementProperties.class) -@TestPropertySource(value = { "classpath:/validations-tests/NoFilters.sample.json" }, factory= JsonPropertySourceFactory.class) +@TestPropertySource(value = { ValidationsTestsConstants.NO_FILTERS_SAMPLE_PATH }, factory= JsonPropertySourceFactory.class) public class NoFiltersTest { - @Autowired private FeatureManagementProperties properties; + @Test + void noFiltersTest() throws IOException { + final List testCases = ValidationsTestsUtils.readTestcasesFromFile(ValidationsTestsConstants.NO_FILTERS_TESTS_PATH); + for (ValidationTestCase testCase : testCases) { + final String exceptionStr = testCase.getIsEnabled().getException(); + if (exceptionStr == null || exceptionStr.isEmpty()) { + final Boolean result = properties.getOnOff().get(testCase.getFeatureFlagName()); + assertEquals(result.toString(), testCase.getIsEnabled().getResult()); + } else { // todo how to get exception? + assertNull(properties.getOnOff().get(testCase.getFeatureFlagName())); + } + } + } + } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java new file mode 100644 index 000000000000..ff26fec34473 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.feature.management.validationstests; + +public final class ValidationsTestsConstants { + public static final String NO_FILTERS_SAMPLE_PATH = "classpath:/validations-tests/NoFilters.sample.json"; + public static final String NO_FILTERS_TESTS_PATH = "src/test/resources/validations-tests/NoFilters.tests.json"; + public static final String REQUIREMENT_TYPE_SAMPLE_PATH = "classpath:/validations-tests/RequirementType.sample.json"; + public static final String REQUIREMENT_TYPE_TESTS_PATH = "src/test/resources/validations-tests/RequirementType.tests.json"; + public static final String TARGETING_FILTER_MODIFIED_SAMPLE_PATH = "classpath:/validations-tests/TargetingFilter.modified.sample.json"; + public static final String TARGETING_FILTER_MODIFIED_TESTS_PATH = "src/test/resources/validations-tests/TargetingFilter.modified.tests.json"; + public static final String TARGETING_FILTER_SAMPLE_PATH = "classpath:/validations-tests/TargetingFilter.sample.json"; + public static final String TARGETING_FILTER_TESTS_PATH = "src/test/resources/validations-tests/TargetingFilter.tests.json"; + public static final String TIME_WINDOW_FILTER_SAMPLE_PATH = "classpath:/validations-tests/TimeWindowFilter.sample.json"; + public static final String TIME_WINDOW_FILTER_TESTS_PATH = "src/test/resources/validations-tests/TimeWindowFilter.tests.json"; + +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java new file mode 100644 index 000000000000..3fc3a332d942 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.feature.management.validationstests; + +import com.azure.spring.cloud.feature.management.validationstests.models.ValidationTestCase; +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 java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +public class ValidationsTestsUtils { + public static List readTestcasesFromFile(String filePath) throws IOException { + final File file = new File(filePath); + final String jsonString = Files.readString(file.toPath()); + final ObjectMapper om = JsonMapper.builder() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); + final CollectionType typeReference = + TypeFactory.defaultInstance().constructCollectionType(List.class, ValidationTestCase.class); + return om.readValue(jsonString, typeReference); + } +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java similarity index 97% rename from sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java rename to sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java index 6ea6f96168c1..9fc93f421b13 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationJson.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java @@ -2,7 +2,7 @@ // Licensed under the MIT License. package com.azure.spring.cloud.feature.management.validationstests.models; -public class ValidationJson { +public class ValidationTestCase { private String featureFlagName; private Object inputs; private IsEnabled isEnabled; From 73b770281ae9be4a8c640669b2039a700b17066d Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 1 Jul 2024 13:43:03 +0800 Subject: [PATCH 03/21] initialize feature manager and call `featureManager.isEnabled()` to compare the result --- .../feature/management/FeatureManager.java | 2 +- .../filters/TargetingFilterTest.java | 45 +++------- .../TargetingFilterTestContextAccessor.java | 27 ++++++ .../JsonPropertySourceFactory.java | 22 ----- .../validationstests/NoFiltersTest.java | 42 --------- .../validationstests/ValidationsTest.java | 90 +++++++++++++++++++ .../ValidationsTestsConstants.java | 22 ++--- .../ValidationsTestsUtils.java | 26 ++++-- .../models/ValidationTestCase.java | 8 +- 9 files changed, 168 insertions(+), 116 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTestContextAccessor.java delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java create mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java index 04be3548066f..25faaa2c4eb1 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java @@ -43,7 +43,7 @@ public class FeatureManager { * @param featureManagementConfigurations Configuration Properties for Feature Flags * @param properties FeatureManagementConfigProperties */ - FeatureManager(ApplicationContext context, FeatureManagementProperties featureManagementConfigurations, + public FeatureManager(ApplicationContext context, FeatureManagementProperties featureManagementConfigurations, FeatureManagementConfigProperties properties) { this.context = context; this.featureManagementConfigurations = featureManagementConfigurations; diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java index 19c06b3ce4c7..5d1323510c7f 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTest.java @@ -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 }) @@ -48,10 +46,10 @@ public void targetedUser() { parameters.put(GROUPS, new LinkedHashMap()); parameters.put(DEFAULT_ROLLOUT_PERCENTAGE, 0); parameters.put("Exclusion", emptyExclusion()); - + Map excludes = new LinkedHashMap<>(); Map excludedGroups = new LinkedHashMap<>(); - + excludes.put(GROUPS, excludedGroups); context.setParameters(parameters); @@ -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 excludes = new LinkedHashMap<>(); Map 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(); @@ -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 excludes = new LinkedHashMap<>(); Map excludedGroups = new LinkedHashMap<>(); excludedGroups.put("0", "g1"); - + excludes.put(GROUPS, excludedGroups); parameters.put("Exclusion", excludes); - + context.setParameters(parameters); - + assertFalse(filter.evaluate(context)); } - + private Map emptyExclusion() { Map excludes = new LinkedHashMap<>(); List excludedUsers = new ArrayList<>(); @@ -402,23 +400,4 @@ private Map emptyExclusion() { excludes.put(GROUPS, excludedGroups); return excludes; } - - class TargetingFilterTestContextAccessor implements TargetingContextAccessor { - - private String user; - - private ArrayList groups; - - TargetingFilterTestContextAccessor(String user, ArrayList groups) { - this.user = user; - this.groups = groups; - } - - @Override - public void configureTargetingContext(TargetingContext context) { - context.setUserId(user); - context.setGroups(groups); - } - - } } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTestContextAccessor.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTestContextAccessor.java new file mode 100644 index 000000000000..a61de4cb0919 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/filters/TargetingFilterTestContextAccessor.java @@ -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 groups; + + public TargetingFilterTestContextAccessor(String user, List groups) { + this.user = user; + this.groups = groups; + } + + @Override + public void configureTargetingContext(TargetingContext context) { + context.setUserId(user); + context.setGroups(groups); + } + +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java deleted file mode 100644 index 7f6728e15849..000000000000 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/JsonPropertySourceFactory.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.azure.spring.cloud.feature.management.validationstests; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.support.EncodedResource; -import org.springframework.core.io.support.PropertySourceFactory; - -import java.io.IOException; -import java.util.Map; - -public class JsonPropertySourceFactory - implements PropertySourceFactory { - - @Override - public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { - Map readValue = new ObjectMapper().readValue(resource.getInputStream(), Map.class); - return new MapPropertySource("json-property", readValue); - } -} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java deleted file mode 100644 index 8fc283ac0d31..000000000000 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/NoFiltersTest.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.cloud.feature.management.validationstests; - -import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties; -import com.azure.spring.cloud.feature.management.validationstests.models.ValidationTestCase; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -@ExtendWith(SpringExtension.class) -@EnableConfigurationProperties(value = FeatureManagementProperties.class) -@TestPropertySource(value = { ValidationsTestsConstants.NO_FILTERS_SAMPLE_PATH }, factory= JsonPropertySourceFactory.class) -public class NoFiltersTest { - @Autowired - private FeatureManagementProperties properties; - - @Test - void noFiltersTest() throws IOException { - final List testCases = ValidationsTestsUtils.readTestcasesFromFile(ValidationsTestsConstants.NO_FILTERS_TESTS_PATH); - for (ValidationTestCase testCase : testCases) { - final String exceptionStr = testCase.getIsEnabled().getException(); - if (exceptionStr == null || exceptionStr.isEmpty()) { - final Boolean result = properties.getOnOff().get(testCase.getFeatureFlagName()); - assertEquals(result.toString(), testCase.getIsEnabled().getResult()); - } else { // todo how to get exception? - assertNull(properties.getOnOff().get(testCase.getFeatureFlagName())); - } - } - } - -} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java new file mode 100644 index 000000000000..993154b22461 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.cloud.feature.management.validationstests; + +import com.azure.spring.cloud.feature.management.FeatureManager; +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 org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +public class ValidationsTest { + @Mock + private ApplicationContext context; + @Mock + private FeatureManagementConfigProperties configProperties; + + @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 inputsMap = testCase.getInputs(); + return inputsMap != null && !inputsMap.isEmpty(); + } + + private void runTestcases(String sampleFileName, String resultFileName) throws IOException { + // initialize feature manager + final FeatureManagementProperties managementProperties = new FeatureManagementProperties(); + managementProperties.putAll(ValidationsTestsUtils.readConfigurationFromFile(sampleFileName)); + final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties); + + final List testCases = ValidationsTestsUtils.readTestcasesFromFile(resultFileName); + for (ValidationTestCase testCase : testCases) { + if (hasException(testCase)) { // todo how to get exception? + assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName())); + } else { + if (hasInput(testCase)) { // Set inputs + final Object userObj = testCase.getInputs().get(ValidationsTestsConstants.INPUTS_USER); + final Object groupsObj = testCase.getInputs().get(ValidationsTestsConstants.INPUTS_GROUPS); + final String user = userObj != null ? userObj.toString() : null; + final List groups = groupsObj != null ? (List) 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()); + } + } + } + + @Test + void validationsTest() throws IOException { + runTestcases(ValidationsTestsConstants.NO_FILTERS_SAMPLE_PATH, ValidationsTestsConstants.NO_FILTERS_TESTS_PATH); + runTestcases(ValidationsTestsConstants.REQUIREMENT_TYPE_SAMPLE_PATH, ValidationsTestsConstants.REQUIREMENT_TYPE_TESTS_PATH); + } + +} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java index ff26fec34473..ab4aa31e9d2e 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java @@ -4,15 +4,17 @@ package com.azure.spring.cloud.feature.management.validationstests; public final class ValidationsTestsConstants { - public static final String NO_FILTERS_SAMPLE_PATH = "classpath:/validations-tests/NoFilters.sample.json"; - public static final String NO_FILTERS_TESTS_PATH = "src/test/resources/validations-tests/NoFilters.tests.json"; - public static final String REQUIREMENT_TYPE_SAMPLE_PATH = "classpath:/validations-tests/RequirementType.sample.json"; - public static final String REQUIREMENT_TYPE_TESTS_PATH = "src/test/resources/validations-tests/RequirementType.tests.json"; - public static final String TARGETING_FILTER_MODIFIED_SAMPLE_PATH = "classpath:/validations-tests/TargetingFilter.modified.sample.json"; - public static final String TARGETING_FILTER_MODIFIED_TESTS_PATH = "src/test/resources/validations-tests/TargetingFilter.modified.tests.json"; - public static final String TARGETING_FILTER_SAMPLE_PATH = "classpath:/validations-tests/TargetingFilter.sample.json"; - public static final String TARGETING_FILTER_TESTS_PATH = "src/test/resources/validations-tests/TargetingFilter.tests.json"; - public static final String TIME_WINDOW_FILTER_SAMPLE_PATH = "classpath:/validations-tests/TimeWindowFilter.sample.json"; - public static final String TIME_WINDOW_FILTER_TESTS_PATH = "src/test/resources/validations-tests/TimeWindowFilter.tests.json"; + public static final String NO_FILTERS_SAMPLE_PATH = "NoFilters.sample.json"; + public static final String NO_FILTERS_TESTS_PATH = "NoFilters.tests.json"; + public static final String REQUIREMENT_TYPE_SAMPLE_PATH = "RequirementType.sample.json"; + public static final String REQUIREMENT_TYPE_TESTS_PATH = "RequirementType.tests.json"; + public static final String TARGETING_FILTER_MODIFIED_SAMPLE_PATH = "TargetingFilter.modified.sample.json"; + public static final String TARGETING_FILTER_MODIFIED_TESTS_PATH = "TargetingFilter.modified.tests.json"; + public static final String TARGETING_FILTER_SAMPLE_PATH = "TargetingFilter.sample.json"; + public static final String TARGETING_FILTER_TESTS_PATH = "TargetingFilter.tests.json"; + public static final String TIME_WINDOW_FILTER_SAMPLE_PATH = "TimeWindowFilter.sample.json"; + public static final String TIME_WINDOW_FILTER_TESTS_PATH = "TimeWindowFilter.tests.json"; + public static final String INPUTS_USER = "user"; + public static final String INPUTS_GROUPS = "groups"; } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java index 3fc3a332d942..8bd8836c2e64 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java @@ -4,6 +4,7 @@ package com.azure.spring.cloud.feature.management.validationstests; 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; @@ -13,16 +14,31 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.LinkedHashMap; import java.util.List; public class ValidationsTestsUtils { - public static List readTestcasesFromFile(String filePath) throws IOException { - final File file = new File(filePath); + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); + private static final String TEST_RESOURCES_FOLDER = "src/test/resources/validations-tests"; + + public static List readTestcasesFromFile(String fileName) throws IOException { + final File file = new File(TEST_RESOURCES_FOLDER + "/" + fileName); final String jsonString = Files.readString(file.toPath()); - final ObjectMapper om = JsonMapper.builder() - .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); final CollectionType typeReference = TypeFactory.defaultInstance().constructCollectionType(List.class, ValidationTestCase.class); - return om.readValue(jsonString, typeReference); + return OBJECT_MAPPER.readValue(jsonString, typeReference); + } + + public static LinkedHashMap readConfigurationFromFile(String fileName) throws IOException { + final File file = new File(TEST_RESOURCES_FOLDER + "/" + fileName); + final String jsonString = Files.readString(file.toPath()); + final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() { + }); + final Object featureManagementSection = configurations.get("feature_management"); + if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) { + return (LinkedHashMap) featureManagementSection; + } + return new LinkedHashMap<>(); } } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java index 9fc93f421b13..2a4b9165cd0c 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java @@ -2,9 +2,11 @@ // Licensed under the MIT License. package com.azure.spring.cloud.feature.management.validationstests.models; +import java.util.LinkedHashMap; + public class ValidationTestCase { private String featureFlagName; - private Object inputs; + private LinkedHashMap inputs; private IsEnabled isEnabled; private Variant variant; private String description; @@ -17,11 +19,11 @@ public void setFeatureFlagName(String featureFlagName) { this.featureFlagName = featureFlagName; } - public Object getInputs() { + public LinkedHashMap getInputs() { return inputs; } - public void setInputs(Object inputs) { + public void setInputs(LinkedHashMap inputs) { this.inputs = inputs; } From 6c80093c6c1280feec7d95f5c5a91689c48753e3 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Tue, 2 Jul 2024 13:24:21 +0800 Subject: [PATCH 04/21] update the schema of test case --- .../validationstests/models/IsEnabled.java | 20 +++++++-- .../models/ValidationTestCase.java | 45 +++++++++++++++++++ .../validationstests/models/Variant.java | 12 +++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java index 4570f0a77cbf..0f10f4175e5e 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/IsEnabled.java @@ -6,18 +6,30 @@ public class IsEnabled { private String result; private String exception; + /** + * @return result + * */ public String getResult() { return result; } - public String getException() { - return exception; - } - + /** + * @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; } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java index 2a4b9165cd0c..3d2891b148e9 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/ValidationTestCase.java @@ -5,48 +5,93 @@ import java.util.LinkedHashMap; public class ValidationTestCase { + private String friendlyName; private String featureFlagName; private LinkedHashMap inputs; private IsEnabled isEnabled; private Variant variant; private String description; + /** + * @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 Variant getVariant() { return variant; } + /** + * @param variant the variant of test case + * */ public void setVariant(Variant 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; } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java index b69f4e51f444..ee209ab255e3 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/models/Variant.java @@ -6,18 +6,30 @@ public class Variant { private String result; private String exception; + /** + * @return result + * */ public String getResult() { return result; } + /** + * @param result the result of variant feature flag + * */ public void setResult(String result) { this.result = result; } + /** + * @return exception + * */ public String getException() { return exception; } + /** + * @param exception the exception message throws when run variant test case + * */ public void setException(String exception) { this.exception = exception; } From f9c7db7a8b53ffe88fa6aae8616db54611215616 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Wed, 3 Jul 2024 12:34:40 +0800 Subject: [PATCH 05/21] get sample file and tests file by listFiles api --- .../validationstests/ValidationsTest.java | 62 ++++++++++++++++--- .../ValidationsTestsConstants.java | 20 ------ .../ValidationsTestsUtils.java | 44 ------------- 3 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java delete mode 100644 sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index 993154b22461..a47b6dc1106f 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -9,6 +9,12 @@ 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; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,7 +25,9 @@ import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.LinkedHashMap; import java.util.List; @@ -34,6 +42,14 @@ public class ValidationsTest { @Mock private FeatureManagementConfigProperties configProperties; + private final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); + private final String TEST_CAE_FOLDER_PATH = "src/test/resources/validations-tests"; + private final String INPUTS_USER = "user"; + private final String INPUTS_GROUPS = "groups"; + private final String SAMPLE_FILE_NAME_FILTER = "sample"; + private final String TESTS_FILE_NAME_FILTER = "tests"; + @BeforeEach public void setup() { MockitoAnnotations.openMocks(this); @@ -56,20 +72,43 @@ private boolean hasInput(ValidationTestCase testCase) { return inputsMap != null && !inputsMap.isEmpty(); } - private void runTestcases(String sampleFileName, String resultFileName) throws IOException { + private File[] getFileList(String directoryPath, String fileNameFilter) { + final File folder = new File(directoryPath); + return folder.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); + } + + private List 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); + } + + private LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException { + final String jsonString = Files.readString(sampleFile.toPath()); + final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() { + }); + final Object featureManagementSection = configurations.get("feature_management"); + if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) { + return (LinkedHashMap) featureManagementSection; + } + return new LinkedHashMap<>(); + } + + private void runTestcases(File sampleFile, File testsFile) throws IOException { // initialize feature manager final FeatureManagementProperties managementProperties = new FeatureManagementProperties(); - managementProperties.putAll(ValidationsTestsUtils.readConfigurationFromFile(sampleFileName)); + managementProperties.putAll(readConfigurationFromFile(sampleFile)); final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties); - final List testCases = ValidationsTestsUtils.readTestcasesFromFile(resultFileName); + final List testCases = readTestcasesFromFile(testsFile); for (ValidationTestCase testCase : testCases) { if (hasException(testCase)) { // todo how to get exception? assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName())); } else { if (hasInput(testCase)) { // Set inputs - final Object userObj = testCase.getInputs().get(ValidationsTestsConstants.INPUTS_USER); - final Object groupsObj = testCase.getInputs().get(ValidationsTestsConstants.INPUTS_GROUPS); + final Object userObj = testCase.getInputs().get(INPUTS_USER); + final Object groupsObj = testCase.getInputs().get(INPUTS_GROUPS); final String user = userObj != null ? userObj.toString() : null; final List groups = groupsObj != null ? (List) groupsObj : null; when(context.getBean(Mockito.contains("Targeting"))).thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups))); @@ -83,8 +122,17 @@ private void runTestcases(String sampleFileName, String resultFileName) throws I @Test void validationsTest() throws IOException { - runTestcases(ValidationsTestsConstants.NO_FILTERS_SAMPLE_PATH, ValidationsTestsConstants.NO_FILTERS_TESTS_PATH); - runTestcases(ValidationsTestsConstants.REQUIREMENT_TYPE_SAMPLE_PATH, ValidationsTestsConstants.REQUIREMENT_TYPE_TESTS_PATH); + final File[] sampleFiles = getFileList(TEST_CAE_FOLDER_PATH, SAMPLE_FILE_NAME_FILTER); + final File[] testsFiles = getFileList(TEST_CAE_FOLDER_PATH, TESTS_FILE_NAME_FILTER); + if (sampleFiles.length != testsFiles.length) { + throw new IllegalArgumentException("The sample files and tests files should have same count."); + } + for (int i = 0; i < sampleFiles.length; i++) { + if (sampleFiles[i].getName().equals("TargetingFilter.sample.json")) { // todo little endian fix + continue; + } + runTestcases(sampleFiles[i], testsFiles[i]); + } } } diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java deleted file mode 100644 index ab4aa31e9d2e..000000000000 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsConstants.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.cloud.feature.management.validationstests; - -public final class ValidationsTestsConstants { - public static final String NO_FILTERS_SAMPLE_PATH = "NoFilters.sample.json"; - public static final String NO_FILTERS_TESTS_PATH = "NoFilters.tests.json"; - public static final String REQUIREMENT_TYPE_SAMPLE_PATH = "RequirementType.sample.json"; - public static final String REQUIREMENT_TYPE_TESTS_PATH = "RequirementType.tests.json"; - public static final String TARGETING_FILTER_MODIFIED_SAMPLE_PATH = "TargetingFilter.modified.sample.json"; - public static final String TARGETING_FILTER_MODIFIED_TESTS_PATH = "TargetingFilter.modified.tests.json"; - public static final String TARGETING_FILTER_SAMPLE_PATH = "TargetingFilter.sample.json"; - public static final String TARGETING_FILTER_TESTS_PATH = "TargetingFilter.tests.json"; - public static final String TIME_WINDOW_FILTER_SAMPLE_PATH = "TimeWindowFilter.sample.json"; - public static final String TIME_WINDOW_FILTER_TESTS_PATH = "TimeWindowFilter.tests.json"; - - public static final String INPUTS_USER = "user"; - public static final String INPUTS_GROUPS = "groups"; -} diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java deleted file mode 100644 index 8bd8836c2e64..000000000000 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTestsUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.cloud.feature.management.validationstests; - -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; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.LinkedHashMap; -import java.util.List; - -public class ValidationsTestsUtils { - private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() - .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); - private static final String TEST_RESOURCES_FOLDER = "src/test/resources/validations-tests"; - - public static List readTestcasesFromFile(String fileName) throws IOException { - final File file = new File(TEST_RESOURCES_FOLDER + "/" + fileName); - final String jsonString = Files.readString(file.toPath()); - final CollectionType typeReference = - TypeFactory.defaultInstance().constructCollectionType(List.class, ValidationTestCase.class); - return OBJECT_MAPPER.readValue(jsonString, typeReference); - } - - public static LinkedHashMap readConfigurationFromFile(String fileName) throws IOException { - final File file = new File(TEST_RESOURCES_FOLDER + "/" + fileName); - final String jsonString = Files.readString(file.toPath()); - final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() { - }); - final Object featureManagementSection = configurations.get("feature_management"); - if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) { - return (LinkedHashMap) featureManagementSection; - } - return new LinkedHashMap<>(); - } -} From 745943e64ba8301783aa83d84deb9fbad10e55c5 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Tue, 13 Aug 2024 14:26:54 +0800 Subject: [PATCH 06/21] update the todo comment. Throw exception and use little endian are breaking change, will have both when update to 6.xx version. --- .../feature/management/validationstests/ValidationsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index a47b6dc1106f..9149771aaf54 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -103,7 +103,7 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { final List testCases = readTestcasesFromFile(testsFile); for (ValidationTestCase testCase : testCases) { - if (hasException(testCase)) { // todo how to get exception? + if (hasException(testCase)) { // TODO. Currently we didn't throw the exception when parameter is invalid assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName())); } else { if (hasInput(testCase)) { // Set inputs @@ -128,7 +128,7 @@ void validationsTest() throws IOException { throw new IllegalArgumentException("The sample files and tests files should have same count."); } for (int i = 0; i < sampleFiles.length; i++) { - if (sampleFiles[i].getName().equals("TargetingFilter.sample.json")) { // todo little endian fix + if (sampleFiles[i].getName().equals("TargetingFilter.sample.json")) { // TODO. Not run the test case until we release the little endian fix continue; } runTestcases(sampleFiles[i], testsFiles[i]); From 32086604499f6ac7fbbd070ce5cef680b76a9bd7 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Tue, 13 Aug 2024 16:32:55 +0800 Subject: [PATCH 07/21] use `getContextClassLoader().getResource` to get the resource folder path --- .../management/validationstests/ValidationsTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index 9149771aaf54..b38df4e6c7bf 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.IOException; +import java.net.URL; import java.nio.file.Files; import java.util.LinkedHashMap; import java.util.List; @@ -44,7 +45,7 @@ public class ValidationsTest { private final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); - private final String TEST_CAE_FOLDER_PATH = "src/test/resources/validations-tests"; + private final String TEST_CAE_FOLDER_PATH = "validations-tests"; private final String INPUTS_USER = "user"; private final String INPUTS_GROUPS = "groups"; private final String SAMPLE_FILE_NAME_FILTER = "sample"; @@ -72,9 +73,10 @@ private boolean hasInput(ValidationTestCase testCase) { return inputsMap != null && !inputsMap.isEmpty(); } - private File[] getFileList(String directoryPath, String fileNameFilter) { - final File folder = new File(directoryPath); - return folder.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); + private File[] getFileList(String folderPath, String fileNameFilter) { + final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(folderPath); + final File folderFile = new File(folderUrl.getFile()); + return folderFile.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); } private List readTestcasesFromFile(File testFile) throws IOException { From 9f90610ebf26ba32cc3e24d4fc12b3c13a6cafb9 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 19 Aug 2024 10:24:33 +0800 Subject: [PATCH 08/21] address comment: typo wording error fix, add "@SuppressWarnings" --- .../validationstests/ValidationsTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index b38df4e6c7bf..ad97e2bbbd58 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -45,7 +45,7 @@ public class ValidationsTest { private final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); - private final String TEST_CAE_FOLDER_PATH = "validations-tests"; + private final String TEST_CASE_FOLDER_PATH = "validations-tests"; private final String INPUTS_USER = "user"; private final String INPUTS_GROUPS = "groups"; private final String SAMPLE_FILE_NAME_FILTER = "sample"; @@ -73,8 +73,9 @@ private boolean hasInput(ValidationTestCase testCase) { return inputsMap != null && !inputsMap.isEmpty(); } - private File[] getFileList(String folderPath, String fileNameFilter) { - final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(folderPath); + private 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()); return folderFile.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); } @@ -86,6 +87,7 @@ private List readTestcasesFromFile(File testFile) throws IOE return OBJECT_MAPPER.readValue(jsonString, typeReference); } + @SuppressWarnings("unchecked") private LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException { final String jsonString = Files.readString(sampleFile.toPath()); final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() { @@ -97,6 +99,7 @@ private LinkedHashMap readConfigurationFromFile(File sampleFile) return new LinkedHashMap<>(); } + @SuppressWarnings("unchecked") private void runTestcases(File sampleFile, File testsFile) throws IOException { // initialize feature manager final FeatureManagementProperties managementProperties = new FeatureManagementProperties(); @@ -124,8 +127,8 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { @Test void validationsTest() throws IOException { - final File[] sampleFiles = getFileList(TEST_CAE_FOLDER_PATH, SAMPLE_FILE_NAME_FILTER); - final File[] testsFiles = getFileList(TEST_CAE_FOLDER_PATH, TESTS_FILE_NAME_FILTER); + final File[] sampleFiles = getFileList(SAMPLE_FILE_NAME_FILTER); + final File[] testsFiles = getFileList(TESTS_FILE_NAME_FILTER); if (sampleFiles.length != testsFiles.length) { throw new IllegalArgumentException("The sample files and tests files should have same count."); } From 9cff351ba2935f526ab3c98d82e695089962ab0d Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 19 Aug 2024 10:27:57 +0800 Subject: [PATCH 09/21] address comment: typo wording error fix --- .../resources/validations-tests/TimeWindowFilter.tests.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json index 2a824fbcd7e8..1bbd40f57d47 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/resources/validations-tests/TimeWindowFilter.tests.json @@ -30,7 +30,7 @@ "Variant": { "Result": null }, - "Description": "Time Window filter where Start has happend but End hasn't happend." + "Description": "Time Window filter where Start has happened but End hasn't happened." }, { "FeatureFlagName": "StartedTimeWindow", From 9a7c5447e968f15364b47c43adfeccbbf2cae1cc Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 19 Aug 2024 11:00:57 +0800 Subject: [PATCH 10/21] address comment: update member name --- .../validationstests/ValidationsTest.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index ad97e2bbbd58..7e59ecf5b5d0 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -43,13 +43,13 @@ public class ValidationsTest { @Mock private FeatureManagementConfigProperties configProperties; - private final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + private final ObjectMapper objectMapper = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); - private final String TEST_CASE_FOLDER_PATH = "validations-tests"; - private final String INPUTS_USER = "user"; - private final String INPUTS_GROUPS = "groups"; - private final String SAMPLE_FILE_NAME_FILTER = "sample"; - private final String TESTS_FILE_NAME_FILTER = "tests"; + private final String testCaseFolderPath = "validations-tests"; + private final String inputsUser = "user"; + private final String inputsGroups = "groups"; + private final String sampleFileNameFilter = "sample"; + private final String testsFileNameFilter = "tests"; @BeforeEach public void setup() { @@ -74,7 +74,7 @@ private boolean hasInput(ValidationTestCase testCase) { } private File[] getFileList(String fileNameFilter) { - final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(TEST_CASE_FOLDER_PATH); + final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(testCaseFolderPath); assert folderUrl != null; final File folderFile = new File(folderUrl.getFile()); return folderFile.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); @@ -84,13 +84,13 @@ private List readTestcasesFromFile(File testFile) throws IOE final String jsonString = Files.readString(testFile.toPath()); final CollectionType typeReference = TypeFactory.defaultInstance().constructCollectionType(List.class, ValidationTestCase.class); - return OBJECT_MAPPER.readValue(jsonString, typeReference); + return objectMapper.readValue(jsonString, typeReference); } @SuppressWarnings("unchecked") private LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException { final String jsonString = Files.readString(sampleFile.toPath()); - final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() { + final LinkedHashMap configurations = objectMapper.readValue(jsonString, new TypeReference<>() { }); final Object featureManagementSection = configurations.get("feature_management"); if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) { @@ -112,8 +112,8 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName())); } else { if (hasInput(testCase)) { // Set inputs - final Object userObj = testCase.getInputs().get(INPUTS_USER); - final Object groupsObj = testCase.getInputs().get(INPUTS_GROUPS); + final Object userObj = testCase.getInputs().get(inputsUser); + final Object groupsObj = testCase.getInputs().get(inputsGroups); final String user = userObj != null ? userObj.toString() : null; final List groups = groupsObj != null ? (List) groupsObj : null; when(context.getBean(Mockito.contains("Targeting"))).thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups))); @@ -127,8 +127,8 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { @Test void validationsTest() throws IOException { - final File[] sampleFiles = getFileList(SAMPLE_FILE_NAME_FILTER); - final File[] testsFiles = getFileList(TESTS_FILE_NAME_FILTER); + final File[] sampleFiles = getFileList(sampleFileNameFilter); + final File[] testsFiles = getFileList(testsFileNameFilter); if (sampleFiles.length != testsFiles.length) { throw new IllegalArgumentException("The sample files and tests files should have same count."); } From f4e8833c736f6ee873533db6ddd8ba823a05693e Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 19 Aug 2024 12:53:20 +0800 Subject: [PATCH 11/21] add running log --- .../feature/management/validationstests/ValidationsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index 7e59ecf5b5d0..071c390cfff9 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -108,6 +108,7 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { final List testCases = readTestcasesFromFile(testsFile); for (ValidationTestCase testCase : testCases) { + System.out.println("Running test case " + testCase.getFriendlyName()); if (hasException(testCase)) { // TODO. Currently we didn't throw the exception when parameter is invalid assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName())); } else { From b23e1af36c9d88699c6c897a873ad64506d48e92 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 19 Aug 2024 13:41:18 +0800 Subject: [PATCH 12/21] use contains to filter out the "TargetingFilter.sample.json" --- .../feature/management/validationstests/ValidationsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index 071c390cfff9..4f778d772f4f 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -134,7 +134,8 @@ void validationsTest() throws IOException { throw new IllegalArgumentException("The sample files and tests files should have same count."); } for (int i = 0; i < sampleFiles.length; i++) { - if (sampleFiles[i].getName().equals("TargetingFilter.sample.json")) { // TODO. Not run the test case until we release the little endian fix + System.out.println("sampleFiles[i].getName(): " + sampleFiles[i].getName()); + if (sampleFiles[i].getName().contains("TargetingFilter.sample")) { // TODO. Not run the test case until we release the little endian fix continue; } runTestcases(sampleFiles[i], testsFiles[i]); From a7ffe469705c3930bca32890deaf008bd43fbbdc Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Mon, 19 Aug 2024 15:57:16 +0800 Subject: [PATCH 13/21] add some info log --- .../validationstests/ValidationsTest.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index 4f778d772f4f..a3192416cd76 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -107,21 +107,23 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties); final List testCases = readTestcasesFromFile(testsFile); - for (ValidationTestCase testCase : testCases) { - System.out.println("Running test case " + testCase.getFriendlyName()); - if (hasException(testCase)) { // TODO. Currently we didn't throw the exception when parameter is invalid - assertNull(managementProperties.getOnOff().get(testCase.getFeatureFlagName())); + System.out.println("Running test case from file: " + testsFile.getName()); + + for (int i = 0; i < testCases.size(); i++) { + System.out.println("Test case " + i + " : " + testCases.get(i).getDescription()); + if (hasException(testCases.get(i))) { // TODO. Currently we didn't throw the exception when parameter is invalid + assertNull(managementProperties.getOnOff().get(testCases.get(i).getFeatureFlagName())); } else { - if (hasInput(testCase)) { // Set inputs - final Object userObj = testCase.getInputs().get(inputsUser); - final Object groupsObj = testCase.getInputs().get(inputsGroups); + if (hasInput(testCases.get(i))) { // Set inputs + final Object userObj = testCases.get(i).getInputs().get(inputsUser); + final Object groupsObj = testCases.get(i).getInputs().get(inputsGroups); final String user = userObj != null ? userObj.toString() : null; final List groups = groupsObj != null ? (List) 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()); + final Boolean result = featureManager.isEnabled(testCases.get(i).getFeatureFlagName()); + assertEquals(result.toString(), testCases.get(i).getIsEnabled().getResult()); } } } @@ -134,7 +136,6 @@ void validationsTest() throws IOException { throw new IllegalArgumentException("The sample files and tests files should have same count."); } for (int i = 0; i < sampleFiles.length; i++) { - System.out.println("sampleFiles[i].getName(): " + sampleFiles[i].getName()); if (sampleFiles[i].getName().contains("TargetingFilter.sample")) { // TODO. Not run the test case until we release the little endian fix continue; } From 82344799c3ec86dd3e6e8557e67910a14cbe247f Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Tue, 20 Aug 2024 09:42:58 +0800 Subject: [PATCH 14/21] sort the file list --- .../management/validationstests/ValidationsTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java index a3192416cd76..8df27e8a8d2b 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.net.URL; import java.nio.file.Files; +import java.util.Arrays; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; @@ -76,8 +78,13 @@ private boolean hasInput(ValidationTestCase testCase) { private File[] getFileList(String fileNameFilter) { final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(testCaseFolderPath); assert folderUrl != null; + final File folderFile = new File(folderUrl.getFile()); - return folderFile.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); + 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 readTestcasesFromFile(File testFile) throws IOException { From 94ceef28f772905c43e6734a18ddccfe5145cd96 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Tue, 20 Aug 2024 11:14:36 +0800 Subject: [PATCH 15/21] move to another the package to avoid making feature manager construction as public --- .../azure/spring/cloud/feature/management/FeatureManager.java | 2 +- .../management/{validationstests => }/ValidationsTest.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/{validationstests => }/ValidationsTest.java (98%) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java index 25faaa2c4eb1..04be3548066f 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/main/java/com/azure/spring/cloud/feature/management/FeatureManager.java @@ -43,7 +43,7 @@ public class FeatureManager { * @param featureManagementConfigurations Configuration Properties for Feature Flags * @param properties FeatureManagementConfigProperties */ - public FeatureManager(ApplicationContext context, FeatureManagementProperties featureManagementConfigurations, + FeatureManager(ApplicationContext context, FeatureManagementProperties featureManagementConfigurations, FeatureManagementConfigProperties properties) { this.context = context; this.featureManagementConfigurations = featureManagementConfigurations; diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java similarity index 98% rename from sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java rename to sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index 8df27e8a8d2b..bb9ec2ed0aa2 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/validationstests/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.feature.management.validationstests; +package com.azure.spring.cloud.feature.management; -import com.azure.spring.cloud.feature.management.FeatureManager; 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; From c50b3a6664b52b9b4136a63fd7f84628d707b937 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Thu, 22 Aug 2024 13:48:05 +0800 Subject: [PATCH 16/21] address comment: use logging, remove else --- .../feature/management/ValidationsTest.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index bb9ec2ed0aa2..0a393e31ecc0 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -21,6 +21,8 @@ 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; @@ -44,6 +46,8 @@ public class ValidationsTest { @Mock private FeatureManagementConfigProperties configProperties; + private static final Logger logger = LoggerFactory.getLogger(ValidationsTest.class); + private final ObjectMapper objectMapper = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); private final String testCaseFolderPath = "validations-tests"; @@ -52,6 +56,7 @@ public class ValidationsTest { private final String sampleFileNameFilter = "sample"; private final String testsFileNameFilter = "tests"; + @BeforeEach public void setup() { MockitoAnnotations.openMocks(this); @@ -113,24 +118,24 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties); final List testCases = readTestcasesFromFile(testsFile); - System.out.println("Running test case from file: " + testsFile.getName()); + logger.debug("Running test case from file: " + testsFile.getName()); for (int i = 0; i < testCases.size(); i++) { - System.out.println("Test case " + i + " : " + testCases.get(i).getDescription()); + logger.debug("Test case " + i + " : " + testCases.get(i).getDescription()); if (hasException(testCases.get(i))) { // TODO. Currently we didn't throw the exception when parameter is invalid assertNull(managementProperties.getOnOff().get(testCases.get(i).getFeatureFlagName())); - } else { - if (hasInput(testCases.get(i))) { // Set inputs - final Object userObj = testCases.get(i).getInputs().get(inputsUser); - final Object groupsObj = testCases.get(i).getInputs().get(inputsGroups); - final String user = userObj != null ? userObj.toString() : null; - final List groups = groupsObj != null ? (List) groupsObj : null; - when(context.getBean(Mockito.contains("Targeting"))).thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups))); - } - - final Boolean result = featureManager.isEnabled(testCases.get(i).getFeatureFlagName()); - assertEquals(result.toString(), testCases.get(i).getIsEnabled().getResult()); + continue; } + if (hasInput(testCases.get(i))) { // Set inputs + final Object userObj = testCases.get(i).getInputs().get(inputsUser); + final Object groupsObj = testCases.get(i).getInputs().get(inputsGroups); + final String user = userObj != null ? userObj.toString() : null; + final List groups = groupsObj != null ? (List) groupsObj : null; + when(context.getBean(Mockito.contains("Targeting"))).thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups))); + } + + final Boolean result = featureManager.isEnabled(testCases.get(i).getFeatureFlagName()); + assertEquals(result.toString(), testCases.get(i).getIsEnabled().getResult()); } } From df174c1810aed2a03400e24ba947288fbb316af2 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Thu, 22 Aug 2024 14:25:53 +0800 Subject: [PATCH 17/21] address comment: throw exception when empty "feature_management" section. --- .../cloud/feature/management/ValidationsTest.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index 0a393e31ecc0..c5de709c3206 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -30,10 +30,7 @@ import java.io.IOException; import java.net.URL; import java.nio.file.Files; -import java.util.Arrays; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; +import java.util.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -107,7 +104,7 @@ private LinkedHashMap readConfigurationFromFile(File sampleFile) if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) { return (LinkedHashMap) featureManagementSection; } - return new LinkedHashMap<>(); + throw new IllegalArgumentException("feature_management part is not a map"); } @SuppressWarnings("unchecked") @@ -122,7 +119,7 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { for (int i = 0; i < testCases.size(); i++) { logger.debug("Test case " + i + " : " + testCases.get(i).getDescription()); - if (hasException(testCases.get(i))) { // TODO. Currently we didn't throw the exception when parameter is invalid + if (hasException(testCases.get(i))) { // TODO(mametcal). Currently we didn't throw the exception when parameter is invalid assertNull(managementProperties.getOnOff().get(testCases.get(i).getFeatureFlagName())); continue; } @@ -147,7 +144,7 @@ void validationsTest() throws IOException { throw new IllegalArgumentException("The sample files and tests files should have same count."); } for (int i = 0; i < sampleFiles.length; i++) { - if (sampleFiles[i].getName().contains("TargetingFilter.sample")) { // TODO. Not run the test case until we release the little endian fix + if (sampleFiles[i].getName().contains("TargetingFilter.sample")) { // TODO(mametcal). Not run the test case until we release the little endian fix continue; } runTestcases(sampleFiles[i], testsFiles[i]); From 111737456bdc8a25ff1587f66ed719540e0046c2 Mon Sep 17 00:00:00 2001 From: Yu Wei Date: Thu, 22 Aug 2024 14:57:43 +0800 Subject: [PATCH 18/21] address comment: rename symbol --- .../spring/cloud/feature/management/ValidationsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index c5de709c3206..69e200b76be3 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -43,7 +43,7 @@ public class ValidationsTest { @Mock private FeatureManagementConfigProperties configProperties; - private static final Logger logger = LoggerFactory.getLogger(ValidationsTest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ValidationsTest.class); private final ObjectMapper objectMapper = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); @@ -115,10 +115,10 @@ private void runTestcases(File sampleFile, File testsFile) throws IOException { final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties); final List testCases = readTestcasesFromFile(testsFile); - logger.debug("Running test case from file: " + testsFile.getName()); + LOGGER.debug("Running test case from file: " + testsFile.getName()); for (int i = 0; i < testCases.size(); i++) { - logger.debug("Test case " + i + " : " + testCases.get(i).getDescription()); + LOGGER.debug("Test case " + i + " : " + testCases.get(i).getDescription()); if (hasException(testCases.get(i))) { // TODO(mametcal). Currently we didn't throw the exception when parameter is invalid assertNull(managementProperties.getOnOff().get(testCases.get(i).getFeatureFlagName())); continue; From 7d77d46fba61bae334ef35970690a801ca47de74 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 26 Aug 2024 12:04:28 -0700 Subject: [PATCH 19/21] Update ValidationsTest.java --- .../feature/management/ValidationsTest.java | 150 ++++++++++-------- 1 file changed, 85 insertions(+), 65 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index 69e200b76be3..08b3467e8b8a 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -2,6 +2,35 @@ // 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; @@ -14,45 +43,29 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.TypeFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -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 java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.Mockito.when; @ExtendWith(SpringExtension.class) public class ValidationsTest { @Mock private ApplicationContext context; + @Mock private FeatureManagementConfigProperties configProperties; private static final Logger LOGGER = LoggerFactory.getLogger(ValidationsTest.class); - private final ObjectMapper objectMapper = JsonMapper.builder() + private static final ObjectMapper objectMapper = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); - private final String testCaseFolderPath = "validations-tests"; + + private static final String testCaseFolderPath = "validations-tests"; + private final String inputsUser = "user"; + private final String inputsGroups = "groups"; - private final String sampleFileNameFilter = "sample"; - private final String testsFileNameFilter = "tests"; + private static final String sampleFileNameFilter = "sample"; + + private static final String testsFileNameFilter = "tests"; @BeforeEach public void setup() { @@ -76,12 +89,13 @@ private boolean hasInput(ValidationTestCase testCase) { return inputsMap != null && !inputsMap.isEmpty(); } - private File[] getFileList(String fileNameFilter) { + private static File[] getFileList(String fileNameFilter) { final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(testCaseFolderPath); assert folderUrl != null; final File folderFile = new File(folderUrl.getFile()); - final File[] filteredFiles = folderFile.listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); + final File[] filteredFiles = folderFile + .listFiles(pathname -> pathname.getName().toLowerCase().contains(fileNameFilter)); assert filteredFiles != null; Arrays.sort(filteredFiles, Comparator.comparing(File::getName)); @@ -90,13 +104,13 @@ private File[] getFileList(String fileNameFilter) { private List readTestcasesFromFile(File testFile) throws IOException { final String jsonString = Files.readString(testFile.toPath()); - final CollectionType typeReference = - TypeFactory.defaultInstance().constructCollectionType(List.class, ValidationTestCase.class); + final CollectionType typeReference = TypeFactory.defaultInstance().constructCollectionType(List.class, + ValidationTestCase.class); return objectMapper.readValue(jsonString, typeReference); } @SuppressWarnings("unchecked") - private LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException { + private static LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException { final String jsonString = Files.readString(sampleFile.toPath()); final LinkedHashMap configurations = objectMapper.readValue(jsonString, new TypeReference<>() { }); @@ -107,48 +121,54 @@ private LinkedHashMap readConfigurationFromFile(File sampleFile) throw new IllegalArgumentException("feature_management part is not a map"); } - @SuppressWarnings("unchecked") - private void runTestcases(File sampleFile, File testsFile) throws IOException { - // initialize feature manager - final FeatureManagementProperties managementProperties = new FeatureManagementProperties(); - managementProperties.putAll(readConfigurationFromFile(sampleFile)); - final FeatureManager featureManager = new FeatureManager(context, managementProperties, configProperties); - - final List testCases = readTestcasesFromFile(testsFile); - LOGGER.debug("Running test case from file: " + testsFile.getName()); + static Stream testProvider() throws IOException { + List arguments = new ArrayList<>(); + File[] files = getFileList(testsFileNameFilter); + + final File[] sampleFiles = getFileList(sampleFileNameFilter); + List properties = new ArrayList<>(); + for (File sampleFile : sampleFiles) { + final FeatureManagementProperties managementProperties = new FeatureManagementProperties(); + managementProperties.putAll(readConfigurationFromFile(sampleFile)); + properties.add(managementProperties); + } + + for(int i = 0; i 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(testCases.get(i))) { // Set inputs - final Object userObj = testCases.get(i).getInputs().get(inputsUser); - final Object groupsObj = testCases.get(i).getInputs().get(inputsGroups); + 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 groups = groupsObj != null ? (List) groupsObj : null; - when(context.getBean(Mockito.contains("Targeting"))).thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups))); + when(context.getBean(Mockito.contains("Targeting"))) + .thenReturn(new TargetingFilter(new TargetingFilterTestContextAccessor(user, groups))); } - final Boolean result = featureManager.isEnabled(testCases.get(i).getFeatureFlagName()); - assertEquals(result.toString(), testCases.get(i).getIsEnabled().getResult()); + final Boolean result = featureManager.isEnabled(testCase.getFeatureFlagName()); + assertEquals(result.toString(), testCase.getIsEnabled().getResult()); } } - - @Test - void validationsTest() throws IOException { - final File[] sampleFiles = getFileList(sampleFileNameFilter); - final File[] testsFiles = getFileList(testsFileNameFilter); - if (sampleFiles.length != testsFiles.length) { - throw new IllegalArgumentException("The sample files and tests files should have same count."); - } - for (int i = 0; i < sampleFiles.length; i++) { - if (sampleFiles[i].getName().contains("TargetingFilter.sample")) { // TODO(mametcal). Not run the test case until we release the little endian fix - continue; - } - runTestcases(sampleFiles[i], testsFiles[i]); - } - } - } From 0f96591d592e2b2a4b60d48e1acec0a0e1b4e452 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 26 Aug 2024 14:53:49 -0700 Subject: [PATCH 20/21] fixing linting --- .../feature/management/ValidationsTest.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index 08b3467e8b8a..6ab909bf947a 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -54,18 +54,18 @@ public class ValidationsTest { private static final Logger LOGGER = LoggerFactory.getLogger(ValidationsTest.class); - private static final ObjectMapper objectMapper = JsonMapper.builder() + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build(); - private static final String testCaseFolderPath = "validations-tests"; + private static final String TEST_CASE_FOLDER_PATH = "validations-tests"; private final String inputsUser = "user"; private final String inputsGroups = "groups"; - private static final String sampleFileNameFilter = "sample"; + private static final String SAMPLE_FILE_NAME_FILTER = "sample"; - private static final String testsFileNameFilter = "tests"; + private static final String TESTS_FILE_NAME_FILTER = "tests"; @BeforeEach public void setup() { @@ -90,7 +90,7 @@ private boolean hasInput(ValidationTestCase testCase) { } private static File[] getFileList(String fileNameFilter) { - final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(testCaseFolderPath); + final URL folderUrl = Thread.currentThread().getContextClassLoader().getResource(TEST_CASE_FOLDER_PATH); assert folderUrl != null; final File folderFile = new File(folderUrl.getFile()); @@ -106,13 +106,13 @@ private List readTestcasesFromFile(File testFile) throws IOE final String jsonString = Files.readString(testFile.toPath()); final CollectionType typeReference = TypeFactory.defaultInstance().constructCollectionType(List.class, ValidationTestCase.class); - return objectMapper.readValue(jsonString, typeReference); + return OBJECT_MAPPER.readValue(jsonString, typeReference); } @SuppressWarnings("unchecked") private static LinkedHashMap readConfigurationFromFile(File sampleFile) throws IOException { final String jsonString = Files.readString(sampleFile.toPath()); - final LinkedHashMap configurations = objectMapper.readValue(jsonString, new TypeReference<>() { + final LinkedHashMap configurations = OBJECT_MAPPER.readValue(jsonString, new TypeReference<>() { }); final Object featureManagementSection = configurations.get("feature_management"); if (featureManagementSection.getClass().isAssignableFrom(LinkedHashMap.class)) { @@ -123,23 +123,23 @@ private static LinkedHashMap readConfigurationFromFile(File samp static Stream testProvider() throws IOException { List arguments = new ArrayList<>(); - File[] files = getFileList(testsFileNameFilter); - - final File[] sampleFiles = getFileList(sampleFileNameFilter); + File[] files = getFileList(TESTS_FILE_NAME_FILTER); + + final File[] sampleFiles = getFileList(SAMPLE_FILE_NAME_FILTER); List properties = new ArrayList<>(); for (File sampleFile : sampleFiles) { final FeatureManagementProperties managementProperties = new FeatureManagementProperties(); managementProperties.putAll(readConfigurationFromFile(sampleFile)); properties.add(managementProperties); } - - for(int i = 0; i Date: Tue, 27 Aug 2024 13:27:31 +0800 Subject: [PATCH 21/21] address comment: ignore both targeting filter test case --- .../azure/spring/cloud/feature/management/ValidationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java index 6ab909bf947a..35d43967f85e 100644 --- a/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java +++ b/sdk/spring/spring-cloud-azure-feature-management/src/test/java/com/azure/spring/cloud/feature/management/ValidationsTest.java @@ -134,7 +134,7 @@ static Stream testProvider() throws IOException { } for (int i = 0; i < files.length; i++) { - if (files[i].getName().contains(("TargetingFilter.tests"))) { + 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)));