diff --git a/.github/workflows/master-2.yml b/.github/workflows/master-2.yml new file mode 100644 index 00000000..3a9ff28d --- /dev/null +++ b/.github/workflows/master-2.yml @@ -0,0 +1,60 @@ +name: Master CI v2 + +on: + push: + branches: [master-2] + pull_request: + branches: [master-2] + +jobs: + build-scan: + name: SonarCloud Scan + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'temurin' + cache: maven + + - name: Build/Test & SonarCloud Scan + run: mvn -B clean verify -Pcoverage,sonar -Dsonar.token=${{ secrets.SONAR_TOKEN }} + + build-test: + name: Build & Test - JDK ${{ matrix.java }} on ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + java: ['11', '17', '21'] + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + + steps: + - uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + + - name: Show Versions + run: mvn -version + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2-${{ matrix.java }} + restore-keys: ${{ runner.os }}-m2- + + - name: Build/Test + run: mvn -B clean package 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 }} diff --git a/README.md b/README.md index 89c82adb..9b33a3fc 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 cb0fad81..15b51392 100644 --- a/src/main/java/com/github/switcherapi/client/ContextBuilder.java +++ b/src/main/java/com/github/switcherapi/client/ContextBuilder.java @@ -182,6 +182,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 c2acdbd2..e7db78d9 100644 --- a/src/main/java/com/github/switcherapi/client/SwitcherConfig.java +++ b/src/main/java/com/github/switcherapi/client/SwitcherConfig.java @@ -11,14 +11,17 @@ abstract class SwitcherConfig { protected String environment; protected boolean local; + protected boolean check; protected String silent; protected Integer timeout; protected Integer regexTimeout; 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(); } @@ -35,10 +38,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)); @@ -93,6 +101,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; } @@ -109,6 +121,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; } @@ -117,6 +132,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 267d5596..f7f7abaa 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) .regexTimeout(regexTimeout) .timeoutMs(timeout) @@ -181,7 +183,7 @@ public static void initializeClient() { validateContext(); registerSwitcherKeys(); switcherExecutor = buildInstance(); - + loadSwitchers(); scheduleSnapshotAutoUpdate(contextStr(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL)); ContextBuilder.preConfigure(switcherProperties); @@ -250,8 +252,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<>(); } @@ -447,7 +455,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..575b00b2 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 @@ -1,10 +1,11 @@ package com.github.switcherapi.client.remote.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.switcherapi.client.remote.ClientWS; + import java.util.Arrays; import java.util.Set; -import com.github.switcherapi.client.remote.ClientWS; - /** * Request/Response model to use with {@link ClientWS#checkSwitchers(Set, String)} * @@ -21,6 +22,7 @@ public class SwitchersCheck { /** * Response field */ + @JsonProperty("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 5c7ec799..76193205 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,10 +57,12 @@ 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.setRegexTimeout(1000); 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..0a45fd14 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,14 +177,14 @@ 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.addHeader("Content-Type", "application/json"); + builder.body(String.format(jsonResponse, switchersNotFound.stream() + .map(s -> "\"" + s + "\"") + .collect(java.util.stream.Collectors.joining(",")))); + 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=