From 18fa25710d36bdb7198a9a9779fa416845f0b467 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:02:15 -0700 Subject: [PATCH 1/2] Added switcher.check context configuration to validate Switchers during start --- README.md | 11 ++++++ .../switcherapi/client/ContextBuilder.java | 9 +++++ .../switcherapi/client/SwitcherConfig.java | 27 ++++++++++++++ .../client/SwitcherContextBase.java | 14 ++++++-- .../client/SwitcherPropertiesImpl.java | 2 ++ .../switcherapi/client/model/ContextKey.java | 5 +++ .../client/remote/dto/SwitchersCheck.java | 2 ++ .../SwitcherBasicCriteriaResponseTest.java | 1 - .../switcherapi/client/SwitcherBasicTest.java | 1 - .../client/SwitcherConfigTest.java | 5 +++ .../client/SwitcherForceResolveTest.java | 1 - .../client/remote/ClientRemoteTest.java | 35 ++++++++++++++++--- .../fixture/MockWebServerHelper.java | 8 ++--- src/test/resources/switcherapi.properties | 2 ++ 14 files changed, 107 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 43bcc235..8fa78be2 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ switcher.domain -> Domain name #optional switcher.environment -> Environment name switcher.local -> true/false When local, it will only use a local snapshot +switcher.check -> true/false When true, it will check Switcher Keys switcher.relay.restrict -> true/false When true, it will check snapshot relay status switcher.snapshot.location -> Folder from where snapshots will be saved/read switcher.snapshot.auto -> true/false Automated lookup for snapshot when initializing the client @@ -314,6 +315,16 @@ void testSwitchers() { } ``` +Alternatively, you can also set the Switcher Context configuration to check during the client initialization. + +```java +MyAppFeatures.configure(ContextBuilder.builder() + ... + .checkSwitchers(true)); + +MyAppFeatures.initializeClient(); +``` + #### SwitcherTest annotation - Requires JUnit 5 Jupiter Predefine Switchers result outside your test methods with the SwitcherTest annotation.
It encapsulates the test and makes sure that the Switcher returns to its original state after concluding the test. diff --git a/src/main/java/com/github/switcherapi/client/ContextBuilder.java b/src/main/java/com/github/switcherapi/client/ContextBuilder.java index fa8a5d42..e8b4dea5 100644 --- a/src/main/java/com/github/switcherapi/client/ContextBuilder.java +++ b/src/main/java/com/github/switcherapi/client/ContextBuilder.java @@ -170,6 +170,15 @@ public ContextBuilder local(boolean local) { return this; } + /** + * @param checkSwitchers true/false When true, it will check switcher keys + * @return ContextBuilder + */ + public ContextBuilder checkSwitchers(boolean checkSwitchers) { + switcherProperties.setValue(ContextKey.CHECK_SWITCHERS, checkSwitchers); + return this; + } + /** * @param restrictRelay true/false When true, it will check snapshot relay status * @return ContextBuilder diff --git a/src/main/java/com/github/switcherapi/client/SwitcherConfig.java b/src/main/java/com/github/switcherapi/client/SwitcherConfig.java index baf9266a..ee3ed204 100644 --- a/src/main/java/com/github/switcherapi/client/SwitcherConfig.java +++ b/src/main/java/com/github/switcherapi/client/SwitcherConfig.java @@ -11,13 +11,16 @@ abstract class SwitcherConfig { protected String environment; protected boolean local; + protected boolean check; protected String silent; protected Integer timeout; protected Integer poolSize; + protected RelayConfig relay; protected SnapshotConfig snapshot; protected TruststoreConfig truststore; SwitcherConfig() { + this.relay = new RelayConfig(); this.snapshot = new SnapshotConfig(); this.truststore = new TruststoreConfig(); } @@ -34,10 +37,15 @@ protected void updateSwitcherConfig(SwitcherProperties properties) { setComponent(properties.getValue(ContextKey.COMPONENT)); setEnvironment(properties.getValue(ContextKey.ENVIRONMENT)); setLocal(properties.getBoolean(ContextKey.LOCAL_MODE)); + setCheck(properties.getBoolean(ContextKey.CHECK_SWITCHERS)); setSilent(properties.getValue(ContextKey.SILENT_MODE)); setTimeout(properties.getInt(ContextKey.TIMEOUT_MS)); setPoolSize(properties.getInt(ContextKey.POOL_CONNECTION_SIZE)); + RelayConfig relayConfig = new RelayConfig(); + relayConfig.setRestrict(properties.getBoolean(ContextKey.RESTRICT_RELAY)); + setRelay(relayConfig); + SnapshotConfig snapshotConfig = new SnapshotConfig(); snapshotConfig.setLocation(properties.getValue(ContextKey.SNAPSHOT_LOCATION)); snapshotConfig.setAuto(properties.getBoolean(ContextKey.SNAPSHOT_AUTO_LOAD)); @@ -92,6 +100,10 @@ public void setLocal(boolean local) { this.local = local; } + public void setCheck(boolean check) { + this.check = check; + } + public void setSilent(String silent) { this.silent = silent; } @@ -104,6 +116,9 @@ public void setPoolSize(Integer poolSize) { this.poolSize = poolSize; } + public void setRelay(RelayConfig relay) { + this.relay = relay; + } public void setSnapshot(SnapshotConfig snapshot) { this.snapshot = snapshot; } @@ -112,6 +127,18 @@ public void setTruststore(TruststoreConfig truststore) { this.truststore = truststore; } + public static class RelayConfig { + private boolean restrict; + + public boolean isRestrict() { + return restrict; + } + + public void setRestrict(boolean restrict) { + this.restrict = restrict; + } + } + public static class SnapshotConfig { private String location; private boolean auto; diff --git a/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java b/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java index 3cf8f191..a43b6991 100644 --- a/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java +++ b/src/main/java/com/github/switcherapi/client/SwitcherContextBase.java @@ -107,6 +107,8 @@ protected void configureClient() { .environment(environment) .component(component) .local(local) + .checkSwitchers(check) + .restrictRelay(relay.isRestrict()) .silentMode(silent) .timeoutMs(timeout) .poolConnectionSize(poolSize) @@ -180,7 +182,7 @@ public static void initializeClient() { validateContext(); registerSwitcherKeys(); switcherExecutor = buildInstance(); - + loadSwitchers(); scheduleSnapshotAutoUpdate(contextStr(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL)); ContextBuilder.preConfigure(switcherProperties); @@ -249,8 +251,14 @@ private static void registerSwitcherKey(Field[] fields) { /** * Load Switcher instances into a map cache + * + * @throws SwitchersValidationException if "switcher.check" is enabled and one or more Switcher Keys are not found */ - private static void loadSwitchers() { + private static void loadSwitchers() throws SwitchersValidationException { + if (contextBol(ContextKey.CHECK_SWITCHERS)) { + checkSwitchers(); + } + if (Objects.isNull(switchers)) { switchers = new HashMap<>(); } @@ -446,7 +454,7 @@ public static void stopWatchingSnapshot() { * * @throws SwitchersValidationException when one or more Switcher Key is not found */ - public static void checkSwitchers() { + public static void checkSwitchers() throws SwitchersValidationException { switcherExecutor.checkSwitchers(switcherKeys); } diff --git a/src/main/java/com/github/switcherapi/client/SwitcherPropertiesImpl.java b/src/main/java/com/github/switcherapi/client/SwitcherPropertiesImpl.java index d8ee7d94..ea0f88e1 100644 --- a/src/main/java/com/github/switcherapi/client/SwitcherPropertiesImpl.java +++ b/src/main/java/com/github/switcherapi/client/SwitcherPropertiesImpl.java @@ -23,6 +23,7 @@ public SwitcherPropertiesImpl() { setValue(ContextKey.SNAPSHOT_AUTO_LOAD, false); setValue(ContextKey.SNAPSHOT_SKIP_VALIDATION, false); setValue(ContextKey.LOCAL_MODE, false); + setValue(ContextKey.CHECK_SWITCHERS, false); setValue(ContextKey.RESTRICT_RELAY, true); } @@ -40,6 +41,7 @@ public void loadFromProperties(Properties prop) { setValue(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL, SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL.getParam(), prop)); setValue(ContextKey.SILENT_MODE, SwitcherUtils.resolveProperties(ContextKey.SILENT_MODE.getParam(), prop)); setValue(ContextKey.LOCAL_MODE, getBoolDefault(SwitcherUtils.resolveProperties(ContextKey.LOCAL_MODE.getParam(), prop), false)); + setValue(ContextKey.CHECK_SWITCHERS, getBoolDefault(SwitcherUtils.resolveProperties(ContextKey.CHECK_SWITCHERS.getParam(), prop), false)); setValue(ContextKey.RESTRICT_RELAY, getBoolDefault(SwitcherUtils.resolveProperties(ContextKey.RESTRICT_RELAY.getParam(), prop), true)); setValue(ContextKey.REGEX_TIMEOUT, getIntDefault(SwitcherUtils.resolveProperties(ContextKey.REGEX_TIMEOUT.getParam(), prop), DEFAULT_REGEX_TIMEOUT)); setValue(ContextKey.TRUSTSTORE_PATH, SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PATH.getParam(), prop)); diff --git a/src/main/java/com/github/switcherapi/client/model/ContextKey.java b/src/main/java/com/github/switcherapi/client/model/ContextKey.java index e0d62279..a87f75fb 100644 --- a/src/main/java/com/github/switcherapi/client/model/ContextKey.java +++ b/src/main/java/com/github/switcherapi/client/model/ContextKey.java @@ -69,6 +69,11 @@ public enum ContextKey { */ LOCAL_MODE("switcher.local"), + /** + * (boolean) Defines if client will check the switchers before using them (default is false). + */ + CHECK_SWITCHERS("switcher.check"), + /** * (boolean) Defines if client will trigger local snapshot relay verification (default is true) */ diff --git a/src/main/java/com/github/switcherapi/client/remote/dto/SwitchersCheck.java b/src/main/java/com/github/switcherapi/client/remote/dto/SwitchersCheck.java index a43bd3bf..b676b3d5 100644 --- a/src/main/java/com/github/switcherapi/client/remote/dto/SwitchersCheck.java +++ b/src/main/java/com/github/switcherapi/client/remote/dto/SwitchersCheck.java @@ -4,6 +4,7 @@ import java.util.Set; import com.github.switcherapi.client.remote.ClientWS; +import com.google.gson.annotations.SerializedName; /** * Request/Response model to use with {@link ClientWS#checkSwitchers(Set, String)} @@ -21,6 +22,7 @@ public class SwitchersCheck { /** * Response field */ + @SerializedName("not_found") private String[] notFound; public SwitchersCheck() {} diff --git a/src/test/java/com/github/switcherapi/client/SwitcherBasicCriteriaResponseTest.java b/src/test/java/com/github/switcherapi/client/SwitcherBasicCriteriaResponseTest.java index 353af422..875ceafe 100644 --- a/src/test/java/com/github/switcherapi/client/SwitcherBasicCriteriaResponseTest.java +++ b/src/test/java/com/github/switcherapi/client/SwitcherBasicCriteriaResponseTest.java @@ -25,7 +25,6 @@ static void setup() throws IOException { MockWebServerHelper.setupMockServer(); Switchers.loadProperties(); // Load default properties from resources - Switchers.initializeClient(); // SwitcherContext requires preload before config override Switchers.configure(ContextBuilder.builder() // Override default properties .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .local(false) diff --git a/src/test/java/com/github/switcherapi/client/SwitcherBasicTest.java b/src/test/java/com/github/switcherapi/client/SwitcherBasicTest.java index 7c3dd131..5a57c12f 100644 --- a/src/test/java/com/github/switcherapi/client/SwitcherBasicTest.java +++ b/src/test/java/com/github/switcherapi/client/SwitcherBasicTest.java @@ -23,7 +23,6 @@ static void setup() throws IOException { MockWebServerHelper.setupMockServer(); Switchers.loadProperties(); // Load default properties from resources - Switchers.initializeClient(); // SwitcherContext requires preload before config override Switchers.configure(ContextBuilder.builder() // Override default properties .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .local(false) diff --git a/src/test/java/com/github/switcherapi/client/SwitcherConfigTest.java b/src/test/java/com/github/switcherapi/client/SwitcherConfigTest.java index c0417d44..8cba12fc 100644 --- a/src/test/java/com/github/switcherapi/client/SwitcherConfigTest.java +++ b/src/test/java/com/github/switcherapi/client/SwitcherConfigTest.java @@ -38,6 +38,9 @@ void shouldInitializeClientFromSwitcherConfig_Minimal() { private T buildSwitcherClientConfig(T classConfig, String component, String domain) { + SwitcherConfig.RelayConfig relay = new SwitcherConfig.RelayConfig(); + relay.setRestrict(false); + SwitcherConfig.SnapshotConfig snapshot = new SwitcherConfig.SnapshotConfig(); snapshot.setLocation(SNAPSHOTS_LOCAL); snapshot.setUpdateInterval(null); @@ -54,9 +57,11 @@ private T buildSwitcherClientConfig(T classConfig, St classConfig.setDomain(domain); classConfig.setEnvironment("fixture1"); classConfig.setLocal(true); + classConfig.setCheck(false); classConfig.setSilent("5m"); classConfig.setTimeout(3000); classConfig.setPoolSize(2); + classConfig.setRelay(relay); classConfig.setSnapshot(snapshot); classConfig.setTruststore(truststore); return classConfig; diff --git a/src/test/java/com/github/switcherapi/client/SwitcherForceResolveTest.java b/src/test/java/com/github/switcherapi/client/SwitcherForceResolveTest.java index 5f2d45c0..7dfb2f13 100644 --- a/src/test/java/com/github/switcherapi/client/SwitcherForceResolveTest.java +++ b/src/test/java/com/github/switcherapi/client/SwitcherForceResolveTest.java @@ -22,7 +22,6 @@ static void setup() throws IOException { MockWebServerHelper.setupMockServer(); Switchers.loadProperties(); // Load default properties from resources - Switchers.initializeClient(); // SwitcherContext requires preload before config override Switchers.configure(ContextBuilder.builder() // Override default properties .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .local(true) diff --git a/src/test/java/com/github/switcherapi/client/remote/ClientRemoteTest.java b/src/test/java/com/github/switcherapi/client/remote/ClientRemoteTest.java index 539717ac..66bad3eb 100644 --- a/src/test/java/com/github/switcherapi/client/remote/ClientRemoteTest.java +++ b/src/test/java/com/github/switcherapi/client/remote/ClientRemoteTest.java @@ -2,7 +2,9 @@ import com.github.switcherapi.Switchers; import com.github.switcherapi.client.ContextBuilder; +import com.github.switcherapi.client.SwitcherContext; import com.github.switcherapi.client.SwitcherProperties; +import com.github.switcherapi.client.exception.SwitchersValidationException; import com.github.switcherapi.client.model.SwitcherRequest; import com.github.switcherapi.client.model.SwitcherResult; import com.github.switcherapi.client.remote.dto.SwitchersCheck; @@ -29,8 +31,7 @@ import java.util.concurrent.Executors; import static com.github.switcherapi.client.remote.Constants.DEFAULT_TIMEOUT; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class ClientRemoteTest extends MockWebServerHelper { @@ -60,7 +61,7 @@ void resetSwitcherContextState() { clientRemote = new ClientRemoteService(ClientWSImpl.build(switcherProperties, executorService, DEFAULT_TIMEOUT), switcherProperties); ((QueueDispatcher) mockBackEnd.getDispatcher()).clear(); - Switchers.configure(ContextBuilder.builder()); + Switchers.configure(ContextBuilder.builder().checkSwitchers(false)); Switchers.initializeClient(); } @@ -84,7 +85,7 @@ void shouldExecuteCriteria() { } @Test - void shouldCheckSwitchers() { + void shouldCheckSwitchersError() { //given final Set switcherKeys = new HashSet<>(); switcherKeys.add("KEY"); @@ -97,4 +98,30 @@ void shouldCheckSwitchers() { assertEquals(1, actual.getNotFound().length); } + @Test + void shouldCheckSwitchersError_throughContextConfiguration() { + //given + final Set switcherKeys = new HashSet<>(); + switcherKeys.add("KEY"); + + givenResponse(generateMockAuth(100)); + givenResponse(generateCheckSwitchersResponse(switcherKeys)); + + //test + Switchers.configure(ContextBuilder.builder().checkSwitchers(true)); + SwitchersValidationException exception = assertThrows(SwitchersValidationException.class, SwitcherContext::initializeClient); + assertEquals("Something went wrong: Unable to load the following Switcher Key(s): [KEY]", exception.getMessage()); + } + + @Test + void shouldCheckSwitchersSuccess_throughContextConfiguration() { + //given + givenResponse(generateMockAuth(100)); + givenResponse(generateCheckSwitchersResponse(new HashSet<>())); + + //test + Switchers.configure(ContextBuilder.builder().checkSwitchers(true)); + assertDoesNotThrow(SwitcherContext::initializeClient); + } + } diff --git a/src/test/java/com/github/switcherapi/fixture/MockWebServerHelper.java b/src/test/java/com/github/switcherapi/fixture/MockWebServerHelper.java index bec19f66..ca5721a2 100644 --- a/src/test/java/com/github/switcherapi/fixture/MockWebServerHelper.java +++ b/src/test/java/com/github/switcherapi/fixture/MockWebServerHelper.java @@ -2,7 +2,6 @@ import com.github.switcherapi.client.model.criteria.Data; import com.github.switcherapi.client.model.criteria.Snapshot; -import com.github.switcherapi.client.remote.dto.SwitchersCheck; import com.github.switcherapi.client.remote.ClientWSImpl; import com.github.switcherapi.client.remote.dto.CriteriaRequest; import com.github.switcherapi.client.utils.SnapshotLoader; @@ -178,13 +177,10 @@ protected MockResponse generateCriteriaResponse(T metadata) { * @return Generated mock /criteria/check_switchers */ protected MockResponse generateCheckSwitchersResponse(Set switchersNotFound) { - SwitchersCheck switchersCheckNotFound = new SwitchersCheck(); - switchersCheckNotFound.setNotFound( - switchersNotFound.toArray(new String[0])); + String jsonResponse = "{ \"not_found\": [%s] }"; - Gson gson = new Gson(); MockResponse.Builder builder = new MockResponse.Builder(); - builder.body(gson.toJson(switchersCheckNotFound)); + builder.body(String.format(jsonResponse, String.join("\",\"", switchersNotFound))); builder.addHeader("Content-Type", "application/json"); return builder.build(); } diff --git a/src/test/resources/switcherapi.properties b/src/test/resources/switcherapi.properties index 3c50e3dd..9215354b 100644 --- a/src/test/resources/switcherapi.properties +++ b/src/test/resources/switcherapi.properties @@ -7,6 +7,8 @@ switcher.domain=switcher-domain #optional switcher.local= +switcher.check= +switcher.relay.restrict= switcher.snapshot.location= switcher.snapshot.auto= switcher.snapshot.updateinterval= From d5d810766bdc0e533f02663916eeb47b36bee31d Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:07:28 -0700 Subject: [PATCH 2/2] chore: removed unused env variable from master CI --- .github/workflows/master-2.yml | 2 -- .github/workflows/master.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/master-2.yml b/.github/workflows/master-2.yml index 5965cb09..3a9ff28d 100644 --- a/.github/workflows/master-2.yml +++ b/.github/workflows/master-2.yml @@ -26,8 +26,6 @@ jobs: - name: Build/Test & SonarCloud Scan run: mvn -B clean verify -Pcoverage,sonar -Dsonar.token=${{ secrets.SONAR_TOKEN }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-test: name: Build & Test - JDK ${{ matrix.java }} on ${{ matrix.os }} diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 43156804..ba59f18d 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -26,8 +26,6 @@ jobs: - name: Build/Test & SonarCloud Scan run: mvn -B clean verify -Pcoverage,sonar -Dsonar.token=${{ secrets.SONAR_TOKEN }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-test: name: Build & Test - JDK ${{ matrix.java }} on ${{ matrix.os }}