diff --git a/.gitattributes b/.gitattributes
index 00a51aff..022b8414 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -3,4 +3,3 @@
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
-
diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml
index bce13406..1b909c15 100644
--- a/.github/workflows/lint-pr.yml
+++ b/.github/workflows/lint-pr.yml
@@ -18,6 +18,6 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- - uses: amannn/action-semantic-pull-request@40166f00814508ec3201fc8595b393d451c8cd80
+ - uses: amannn/action-semantic-pull-request@04501d43b574e4c1d23c629ffe4dcec27acfdeff
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml
index 6ef83234..b156383e 100644
--- a/.github/workflows/merge.yml
+++ b/.github/workflows/merge.yml
@@ -16,15 +16,14 @@ permissions:
jobs:
build:
-
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- - name: Set up JDK 8
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
+ - name: Set up JDK 17
+ uses: actions/setup-java@3b6c050358614dd082e53cdbc55580431fc4e437
with:
- java-version: '8'
+ java-version: '17'
distribution: 'temurin'
cache: maven
server-id: ossrh
@@ -32,12 +31,12 @@ jobs:
server-password: ${{ secrets.OSSRH_PASSWORD }}
- name: Cache local Maven repository
- uses: actions/cache@9fa7e61ec7e1f44ac75218e7aaea81da8856fd11
+ uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684
with:
path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ key: ${{ runner.os }}-17-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
- ${{ runner.os }}-maven-
+ ${{ runner.os }}-17-maven-
- name: Configure GPG Key
run: |
@@ -49,7 +48,7 @@ jobs:
run: mvn --batch-mode --update-snapshots verify
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v5.3.1
+ uses: codecov/codecov-action@v5.4.0
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
flags: unittests # optional
@@ -60,7 +59,7 @@ jobs:
# Add -SNAPSHOT before deploy
- name: Add SNAPSHOT
run: mvn versions:set -DnewVersion='${project.version}-SNAPSHOT'
-
+
- name: Deploy
run: |
mvn --batch-mode \
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 6e3c40f4..fc1ac720 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -7,36 +7,46 @@ permissions:
jobs:
build:
- runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ build:
+ - java: 17
+ profile: codequality
+ - java: 8
+ profile: java8
+ name: with Java ${{ matrix.build.java }}
+ runs-on: ${{ matrix.os}}
steps:
- name: Check out the code
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- name: Set up JDK 8
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
+ uses: actions/setup-java@3b6c050358614dd082e53cdbc55580431fc4e437
with:
- java-version: '8'
- distribution: 'temurin'
- cache: maven
+ java-version: ${{ matrix.build.java }}
+ distribution: 'temurin'
+ cache: maven
- name: Initialize CodeQL
- uses: github/codeql-action/init@1c15a48f3fb49ce535e9ee4e57e127315f669361
+ uses: github/codeql-action/init@486ab5a2922b634015408a83e10f6867efb5922c
with:
languages: java
- name: Cache local Maven repository
- uses: actions/cache@9fa7e61ec7e1f44ac75218e7aaea81da8856fd11
+ uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684
with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
+ path: ~/.m2/repository
+ key: ${{ runner.os }}${{ matrix.build.java }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}${{ matrix.build.java }}-maven-
- name: Verify with Maven
- run: mvn --batch-mode --update-snapshots --activate-profiles e2e verify
+ run: mvn --batch-mode --update-snapshots --activate-profiles e2e,${{ matrix.build.profile }} verify
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v5.3.1
+ - if: matrix.build.java == '17'
+ name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v5.4.0
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
flags: unittests # optional
@@ -45,4 +55,4 @@ jobs:
verbose: true # optional (default = false)
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@1c15a48f3fb49ce535e9ee4e57e127315f669361
+ uses: github/codeql-action/analyze@486ab5a2922b634015408a83e10f6867efb5922c
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7342889d..28b5798e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,17 +23,17 @@ jobs:
id: release
with:
token: ${{secrets.GITHUB_TOKEN}}
- default-branch: main
+ target-branch: main
# These steps are only run if this was a merged release-please PR
- name: checkout
if: ${{ steps.release.outputs.release_created }}
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- - name: Set up JDK 8
+ - name: Set up JDK 17
if: ${{ steps.release.outputs.release_created }}
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
+ uses: actions/setup-java@3b6c050358614dd082e53cdbc55580431fc4e437
with:
- java-version: '8'
+ java-version: '17'
distribution: 'temurin'
cache: maven
server-id: ossrh
@@ -54,4 +54,4 @@ jobs:
--settings release/m2-settings.xml clean deploy
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
- OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
\ No newline at end of file
+ OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml
index 85313855..a9b7a349 100644
--- a/.github/workflows/static-code-scanning.yaml
+++ b/.github/workflows/static-code-scanning.yaml
@@ -33,12 +33,12 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@1c15a48f3fb49ce535e9ee4e57e127315f669361
+ uses: github/codeql-action/init@486ab5a2922b634015408a83e10f6867efb5922c
with:
languages: java
- name: Autobuild
- uses: github/codeql-action/autobuild@1c15a48f3fb49ce535e9ee4e57e127315f669361
+ uses: github/codeql-action/autobuild@486ab5a2922b634015408a83e10f6867efb5922c
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@1c15a48f3fb49ce535e9ee4e57e127315f669361
+ uses: github/codeql-action/analyze@486ab5a2922b634015408a83e10f6867efb5922c
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f459d7af..762e32db 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1 +1 @@
-{".":"1.14.1"}
\ No newline at end of file
+{".":"1.14.2"}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6301fce0..914cbfef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,68 @@
# Changelog
+## [1.14.2](https://github.com/open-feature/java-sdk/compare/v1.14.1...v1.14.2) (2025-03-27)
+
+
+### ๐ Bug Fixes
+
+* **deps:** update dependency org.slf4j:slf4j-api to v2.0.17 ([#1348](https://github.com/open-feature/java-sdk/issues/1348)) ([2ec7c6c](https://github.com/open-feature/java-sdk/commit/2ec7c6c7ff704380fdfd8116378adf78734e4f2b))
+* **deps:** update junit5 monorepo ([#1344](https://github.com/open-feature/java-sdk/issues/1344)) ([d95e270](https://github.com/open-feature/java-sdk/commit/d95e2706532259bd5739e5b4ea4813ef9f2196a6))
+* **deps:** update junit5 monorepo ([#1373](https://github.com/open-feature/java-sdk/issues/1373)) ([6b65e26](https://github.com/open-feature/java-sdk/commit/6b65e26c7439895652c3f64f2b4a7307a7ca582e))
+* equals and hashcode of several classes ([69b571e](https://github.com/open-feature/java-sdk/commit/69b571eda73b6f43c99864420b8663ae54ebf0ad))
+* equals and hashcode of several classes ([#1364](https://github.com/open-feature/java-sdk/issues/1364)) ([69b571e](https://github.com/open-feature/java-sdk/commit/69b571eda73b6f43c99864420b8663ae54ebf0ad))
+* hooks not run in NOT_READY/FATAL ([#1392](https://github.com/open-feature/java-sdk/issues/1392)) ([24ef9dd](https://github.com/open-feature/java-sdk/commit/24ef9dd2903d01ec029b70cd1e39e71ffe327499))
+
+
+### ๐งน Chore
+
+* **deps:** update actions/cache digest to 5a3ec84 ([#1380](https://github.com/open-feature/java-sdk/issues/1380)) ([8359ef1](https://github.com/open-feature/java-sdk/commit/8359ef13bb935ac1d144787cfd7181814a0b286c))
+* **deps:** update actions/cache digest to 7921ae2 ([#1337](https://github.com/open-feature/java-sdk/issues/1337)) ([3920c63](https://github.com/open-feature/java-sdk/commit/3920c638a49caddfb07041f812cc6bc0bf3101f9))
+* **deps:** update actions/cache digest to d4323d4 ([#1353](https://github.com/open-feature/java-sdk/issues/1353)) ([5901797](https://github.com/open-feature/java-sdk/commit/59017977a487a36c8a39f63b83299bc657134c0d))
+* **deps:** update actions/setup-java digest to 3b6c050 ([#1391](https://github.com/open-feature/java-sdk/issues/1391)) ([7536679](https://github.com/open-feature/java-sdk/commit/753667925a8803b3b227f762936ae397dde95484))
+* **deps:** update actions/setup-java digest to 799ee7c ([#1359](https://github.com/open-feature/java-sdk/issues/1359)) ([31444d6](https://github.com/open-feature/java-sdk/commit/31444d6c8f30f0dd35debacc9dab8da7397e11ed))
+* **deps:** update actions/setup-java digest to b8ebb8b ([#1381](https://github.com/open-feature/java-sdk/issues/1381)) ([2239f05](https://github.com/open-feature/java-sdk/commit/2239f054b90734dde6cdd4a23daec1c1daa96f07))
+* **deps:** update amannn/action-semantic-pull-request digest to 04501d4 ([#1390](https://github.com/open-feature/java-sdk/issues/1390)) ([87c06d9](https://github.com/open-feature/java-sdk/commit/87c06d9edd935287daf7ebc8db1e7da4831531de))
+* **deps:** update codecov/codecov-action action to v5.4.0 ([#1351](https://github.com/open-feature/java-sdk/issues/1351)) ([b133c2f](https://github.com/open-feature/java-sdk/commit/b133c2fa527a0dddb6de7f7781a00fc84feaa813))
+* **deps:** update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.3 ([#1341](https://github.com/open-feature/java-sdk/issues/1341)) ([5de33c0](https://github.com/open-feature/java-sdk/commit/5de33c02a675db6ca5966bfa3f58d99c8e53e36b))
+* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.1.0 ([#1332](https://github.com/open-feature/java-sdk/issues/1332)) ([cdcdc14](https://github.com/open-feature/java-sdk/commit/cdcdc143ea5ad2f003cb3f5450ec78314e619ea3))
+* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.2.0 ([#1360](https://github.com/open-feature/java-sdk/issues/1360)) ([ecea9df](https://github.com/open-feature/java-sdk/commit/ecea9df932ee4874613f219b73640fe964c99593))
+* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.0 ([#1375](https://github.com/open-feature/java-sdk/issues/1375)) ([de3e213](https://github.com/open-feature/java-sdk/commit/de3e213ac8b8931121904a3d12929405512e74dd))
+* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.2 ([#1355](https://github.com/open-feature/java-sdk/issues/1355)) ([2a1adca](https://github.com/open-feature/java-sdk/commit/2a1adca8c2ed8d61d51530969290793a5d3d15f3))
+* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.3 ([#1384](https://github.com/open-feature/java-sdk/issues/1384)) ([b6becac](https://github.com/open-feature/java-sdk/commit/b6becac2c4e0f98a8651cc2f77d4c0b081548991))
+* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.4 ([#1387](https://github.com/open-feature/java-sdk/issues/1387)) ([cb574d9](https://github.com/open-feature/java-sdk/commit/cb574d93b6210c89a188aa104ef4f1db68daf1c0))
+* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.2 ([#1356](https://github.com/open-feature/java-sdk/issues/1356)) ([dd83114](https://github.com/open-feature/java-sdk/commit/dd83114c4d9389753575392fafcd56585d7178ae))
+* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.3 ([#1385](https://github.com/open-feature/java-sdk/issues/1385)) ([4125ae8](https://github.com/open-feature/java-sdk/commit/4125ae83801a9f485059a9edaca090ee47b7632f))
+* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.4 ([#1388](https://github.com/open-feature/java-sdk/issues/1388)) ([d8f6514](https://github.com/open-feature/java-sdk/commit/d8f6514598d53f43cb084ee746742a59d271363b))
+* **deps:** update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.0 ([#1342](https://github.com/open-feature/java-sdk/issues/1342)) ([88a778c](https://github.com/open-feature/java-sdk/commit/88a778cc03e112d45756428d1f0ae1ef0fe02c84))
+* **deps:** update dependency org.awaitility:awaitility to v4.3.0 ([#1343](https://github.com/open-feature/java-sdk/issues/1343)) ([1504d0f](https://github.com/open-feature/java-sdk/commit/1504d0f7982757a2b413eda593ce7057b90519e5))
+* **deps:** update dependency org.mockito:mockito-core to v5.15.2 ([#1339](https://github.com/open-feature/java-sdk/issues/1339)) ([4817864](https://github.com/open-feature/java-sdk/commit/4817864fd7ae70c1e19c3c09e82e1fb03dd88942))
+* **deps:** update dependency org.mockito:mockito-core to v5.16.0 ([#1358](https://github.com/open-feature/java-sdk/issues/1358)) ([30b6d00](https://github.com/open-feature/java-sdk/commit/30b6d004aaf3464547805f7eda6fad0e122de4f9))
+* **deps:** update dependency org.mockito:mockito-core to v5.16.1 ([#1376](https://github.com/open-feature/java-sdk/issues/1376)) ([9750f75](https://github.com/open-feature/java-sdk/commit/9750f75d04beb8339fc2e972f0ee97120eaff354))
+* **deps:** update github/codeql-action digest to 1bb15d0 ([#1336](https://github.com/open-feature/java-sdk/issues/1336)) ([e163ce1](https://github.com/open-feature/java-sdk/commit/e163ce1c060d0dc8812e4a8a3b37f52b0156324d))
+* **deps:** update github/codeql-action digest to 486ab5a ([#1389](https://github.com/open-feature/java-sdk/issues/1389)) ([85fd5e0](https://github.com/open-feature/java-sdk/commit/85fd5e0997ff1a5e5d7226d8bbfe2775769a6ca6))
+* **deps:** update github/codeql-action digest to 56b25d5 ([#1365](https://github.com/open-feature/java-sdk/issues/1365)) ([959e675](https://github.com/open-feature/java-sdk/commit/959e675e4c2363e5fd80d1d2f1edbfab11794fc8))
+* **deps:** update github/codeql-action digest to 608ccd6 ([#1361](https://github.com/open-feature/java-sdk/issues/1361)) ([67b34f8](https://github.com/open-feature/java-sdk/commit/67b34f84a373512013ab2f7649faaddfd2d61048))
+* **deps:** update github/codeql-action digest to 6349095 ([#1378](https://github.com/open-feature/java-sdk/issues/1378)) ([dbf92df](https://github.com/open-feature/java-sdk/commit/dbf92df33bf5657d50dc3b2f129207b0097c1f27))
+* **deps:** update github/codeql-action digest to 6a151cd ([#1377](https://github.com/open-feature/java-sdk/issues/1377)) ([7065655](https://github.com/open-feature/java-sdk/commit/706565581d78856dd73605b1a16b131f974c0731))
+* **deps:** update github/codeql-action digest to 70df9de ([#1372](https://github.com/open-feature/java-sdk/issues/1372)) ([d233480](https://github.com/open-feature/java-sdk/commit/d233480912f1d5e095f5034f36a838535d1ecdff))
+* **deps:** update github/codeql-action digest to 7254660 ([#1368](https://github.com/open-feature/java-sdk/issues/1368)) ([d54c68a](https://github.com/open-feature/java-sdk/commit/d54c68a8e9e4a0f67c99e7d76621a1c5724e4cd1))
+* **deps:** update github/codeql-action digest to 80f9930 ([#1357](https://github.com/open-feature/java-sdk/issues/1357)) ([6c03e5d](https://github.com/open-feature/java-sdk/commit/6c03e5d84aacee11f5b8e608a6114c11fced72b8))
+* **deps:** update github/codeql-action digest to 8392354 ([#1352](https://github.com/open-feature/java-sdk/issues/1352)) ([989f4ae](https://github.com/open-feature/java-sdk/commit/989f4ae54263b46ca2c81561acc70b39918c382d))
+* **deps:** update github/codeql-action digest to 8c1551c ([#1333](https://github.com/open-feature/java-sdk/issues/1333)) ([859a36c](https://github.com/open-feature/java-sdk/commit/859a36cbfafc94d4601b87d304237e6ddf97c08d))
+* **deps:** update github/codeql-action digest to 8c69433 ([#1347](https://github.com/open-feature/java-sdk/issues/1347)) ([6987568](https://github.com/open-feature/java-sdk/commit/698756856ba40e98d91ccf661dab409798861aa5))
+* **deps:** update github/codeql-action digest to 97aac9b ([#1350](https://github.com/open-feature/java-sdk/issues/1350)) ([7df9565](https://github.com/open-feature/java-sdk/commit/7df9565691731d164b534116b8a6b933b171d103))
+* **deps:** update github/codeql-action digest to a8849fb ([#1345](https://github.com/open-feature/java-sdk/issues/1345)) ([de64edd](https://github.com/open-feature/java-sdk/commit/de64eddfb3a6cc117bb108dbcf167830e9f6729d))
+* **deps:** update github/codeql-action digest to acadfed ([#1335](https://github.com/open-feature/java-sdk/issues/1335)) ([5436eb0](https://github.com/open-feature/java-sdk/commit/5436eb0d5db3a0e9bd9289fbef57b9eeada0a667))
+* **deps:** update github/codeql-action digest to b2e6519 ([#1366](https://github.com/open-feature/java-sdk/issues/1366)) ([d00e4b5](https://github.com/open-feature/java-sdk/commit/d00e4b5b24621aa55085827fbe6ea982491376de))
+* **deps:** update github/codeql-action digest to b46b37a ([#1367](https://github.com/open-feature/java-sdk/issues/1367)) ([c550d59](https://github.com/open-feature/java-sdk/commit/c550d597227bfc1e0e17357139f1fd8a87593be0))
+* **deps:** update github/codeql-action digest to bd1d9ab ([#1383](https://github.com/open-feature/java-sdk/issues/1383)) ([922e17e](https://github.com/open-feature/java-sdk/commit/922e17e677e15690e3df2fe93a961f16f21ff283))
+* **deps:** update github/codeql-action digest to c50c157 ([#1379](https://github.com/open-feature/java-sdk/issues/1379)) ([d61c33e](https://github.com/open-feature/java-sdk/commit/d61c33e466336c7120b870ca5e3843eba5f7175c))
+* **deps:** update github/codeql-action digest to d99c7e8 ([#1338](https://github.com/open-feature/java-sdk/issues/1338)) ([4e535fd](https://github.com/open-feature/java-sdk/commit/4e535fd10fac742ca472faa62c941fa51b282ca7))
+* **deps:** update github/codeql-action digest to dc49dca ([#1369](https://github.com/open-feature/java-sdk/issues/1369)) ([f8df5fb](https://github.com/open-feature/java-sdk/commit/f8df5fb84a765af917587dd509f9cec38103f787))
+* **deps:** update github/codeql-action digest to e0ea141 ([#1386](https://github.com/open-feature/java-sdk/issues/1386)) ([387e5f2](https://github.com/open-feature/java-sdk/commit/387e5f2e3bd24ccea6691b0d6dbfe542cfd05b52))
+* **deps:** update github/codeql-action digest to ff79de6 ([#1340](https://github.com/open-feature/java-sdk/issues/1340)) ([50b45b2](https://github.com/open-feature/java-sdk/commit/50b45b2be442bb89a431c9bcc45d825f63bd93a6))
+* update build and tooling to utilize new java version ([#1321](https://github.com/open-feature/java-sdk/issues/1321)) ([90217b2](https://github.com/open-feature/java-sdk/commit/90217b2083a2ba92c623365dc450326d49b46fab))
+
## [1.14.1](https://github.com/open-feature/java-sdk/compare/v1.14.0...v1.14.1) (2025-02-14)
diff --git a/README.md b/README.md
index 49d5562e..22d85bd2 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,8 @@
-
-
+
+
@@ -59,7 +59,7 @@ Note that this library is intended to be used in server-side contexts and has no
dev.openfeature
sdk
- 1.14.1
+ 1.14.2
```
@@ -84,7 +84,7 @@ If you would like snapshot builds, this is the relevant repository information:
```groovy
dependencies {
- implementation 'dev.openfeature:sdk:1.14.1'
+ implementation 'dev.openfeature:sdk:1.14.2'
}
```
diff --git a/pom.xml b/pom.xml
index a6a54e5a..7b8e0d58 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,16 +5,22 @@
dev.openfeature
sdk
- 1.14.1
+ 1.14.2
+ [17,)
UTF-8
1.8
${maven.compiler.source}
- 5.11.4
+ 5.12.1
+ 7.21.1
+ 5.16.1
**/e2e/*.java
${project.groupId}.${project.artifactId}
+ false
+
+ 8
OpenFeature Java SDK
@@ -63,14 +69,21 @@
org.slf4j
slf4j-api
- 2.0.16
+ 2.0.17
+
+ com.tngtech.archunit
+ archunit-junit5
+ 1.4.0
+ test
+
+
org.mockito
mockito-core
- 4.11.0
+ ${org.mockito.version}
test
@@ -112,19 +125,28 @@
org.junit.platform
junit-platform-suite
- 1.11.4
+ 1.12.1
test
io.cucumber
cucumber-java
+ ${io.cucumber.version}
test
io.cucumber
cucumber-junit-platform-engine
+ ${io.cucumber.version}
+ test
+
+
+
+ io.cucumber
+ cucumber-picocontainer
+ ${io.cucumber.version}
test
@@ -145,7 +167,7 @@
org.awaitility
awaitility
- 4.2.2
+ 4.3.0
test
@@ -167,14 +189,14 @@
net.bytebuddy
byte-buddy
- 1.17.1
+ 1.17.4
test
net.bytebuddy
byte-buddy-agent
- 1.17.1
+ 1.17.4
test
@@ -190,7 +212,7 @@
org.junit
junit-bom
- 5.11.4
+ 5.12.1
pom
import
@@ -200,6 +222,18 @@
+
+ org.apache.maven.plugins
+ maven-toolchains-plugin
+ 3.2.0
+
+
+
+ select-jdk-toolchain
+
+
+
+
org.cyclonedx
cyclonedx-maven-plugin
@@ -226,38 +260,9 @@
-
- maven-dependency-plugin
- 3.8.1
-
-
- verify
-
- analyze
-
-
-
-
- true
-
- com.github.spotbugs:*
- org.junit*
- org.simplify4u:slf4j2-mock*
-
-
- com.google.guava*
- io.cucumber*
- org.junit*
- com.google.code.findbugs*
- com.github.spotbugs*
- org.simplify4u:slf4j-mock-common:*
-
-
-
-
maven-compiler-plugin
- 3.13.0
+ 3.14.0
@@ -269,6 +274,8 @@
false
${surefireArgLine}
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.lang=ALL-UNNAMED
@@ -288,65 +295,6 @@
-
- org.jacoco
- jacoco-maven-plugin
- 0.8.12
-
-
-
- prepare-agent
-
- prepare-agent
-
-
-
- ${project.build.directory}/coverage-reports/jacoco-ut.exec
- surefireArgLine
-
-
-
-
- report
- verify
-
- report
-
-
-
- ${project.build.directory}/coverage-reports/jacoco-ut.exec
- ${project.reporting.outputDirectory}/jacoco-ut
-
-
-
-
- jacoco-check
-
- check
-
-
- ${project.build.directory}/coverage-reports/jacoco-ut.exec
-
- dev/openfeature/sdk/exceptions/**
-
-
-
-
- PACKAGE
-
-
- LINE
- COVEREDRATIO
- 0.80
-
-
-
-
-
-
-
-
-
org.apache.maven.plugins
@@ -361,134 +309,217 @@
-
- org.apache.maven.plugins
- maven-pmd-plugin
- 3.26.0
-
-
- run-pmd
- verify
-
- check
-
-
-
-
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
- 4.8.6.6
-
- spotbugs-exclusions.xml
-
-
- com.h3xstream.findsecbugs
- findsecbugs-plugin
- 1.13.0
-
-
-
-
-
-
- com.github.spotbugs
- spotbugs
- 4.8.6
-
-
-
-
- run-spotbugs
- verify
-
- check
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- 3.6.0
-
- checkstyle.xml
- UTF-8
- true
- true
- false
-
-
-
- com.puppycrawl.tools
- checkstyle
- 9.3
-
-
-
-
- validate
- validate
-
- check
-
-
-
-
-
- com.diffplug.spotless
- spotless-maven-plugin
- 2.30.0
-
-
-
-
-
-
-
-
- .gitattributes
- .gitignore
-
-
-
-
-
- true
- 4
-
-
-
-
-
-
-
-
- true
- 4
-
-
-
-
-
-
-
-
-
-
-
- check
-
-
-
-
-
+
+ codequality
+
+ true
+
+
+
+
+ maven-dependency-plugin
+ 3.8.1
+
+
+ verify
+
+ analyze
+
+
+
+
+ true
+
+ com.github.spotbugs:*
+ org.junit*
+ com.tngtech.archunit*
+ org.simplify4u:slf4j2-mock*
+
+
+ com.google.guava*
+ io.cucumber*
+ org.junit*
+ com.tngtech.archunit*
+ com.google.code.findbugs*
+ com.github.spotbugs*
+ org.simplify4u:slf4j-mock-common:*
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ ${project.build.directory}/coverage-reports/jacoco-ut.exec
+ surefireArgLine
+
+
+
+
+ report
+ verify
+
+ report
+
+
+
+ ${project.build.directory}/coverage-reports/jacoco-ut.exec
+ ${project.reporting.outputDirectory}/jacoco-ut
+
+
+
+
+ jacoco-check
+
+ check
+
+
+ ${project.build.directory}/coverage-reports/jacoco-ut.exec
+
+ dev/openfeature/sdk/exceptions/**
+
+
+
+
+ PACKAGE
+
+
+ LINE
+ COVEREDRATIO
+ 0.80
+
+
+
+
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.9.3.0
+
+ spotbugs-exclusions.xml
+
+
+ com.h3xstream.findsecbugs
+ findsecbugs-plugin
+ 1.13.0
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs
+ 4.8.6
+
+
+
+
+ run-spotbugs
+ verify
+
+ check
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+ checkstyle.xml
+ true
+ true
+ false
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 9.3
+
+
+
+
+ validate
+ validate
+
+ check
+
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.44.3
+
+
+
+
+
+
+
+
+ .gitattributes
+ .gitignore
+
+
+
+
+
+ true
+ 4
+
+
+
+
+
+
+
+
+ true
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ check
+
+
+
+
+
+
+
deploy
@@ -610,19 +641,75 @@
+
+
+
+
+
+
+
+
+ java8
+
+
+
+ (1.8,9)
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-toolchains-plugin
+ 3.2.0
+
+
+
+ select-jdk-toolchain
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.2
+
+
+ ${surefireArgLine}
+
+
+
+ ${testExclusions}
+
+
+ ${skip.tests}
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.2
+
+
+ ${surefireArgLine}
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.0
+
- copy-evaluation-gherkin-tests
- validate
+ default-testCompile
+ test-compile
- exec
+ testCompile
-
- cp
-
- spec/specification/assets/gherkin/evaluation.feature
- src/test/resources/features/
-
+ true
diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java
index 6c652114..7962705c 100644
--- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java
+++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java
@@ -3,15 +3,17 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import lombok.EqualsAndHashCode;
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
+@EqualsAndHashCode
abstract class AbstractStructure implements Structure {
protected final Map attributes;
@Override
public boolean isEmpty() {
- return attributes == null || attributes.size() == 0;
+ return attributes == null || attributes.isEmpty();
}
AbstractStructure() {
diff --git a/src/main/java/dev/openfeature/sdk/EventDetails.java b/src/main/java/dev/openfeature/sdk/EventDetails.java
index e32e6101..c75b046e 100644
--- a/src/main/java/dev/openfeature/sdk/EventDetails.java
+++ b/src/main/java/dev/openfeature/sdk/EventDetails.java
@@ -1,11 +1,13 @@
package dev.openfeature.sdk;
import lombok.Data;
+import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;
/**
* The details of a particular event.
*/
+@EqualsAndHashCode(callSuper = true)
@Data
@SuperBuilder(toBuilder = true)
public class EventDetails extends ProviderEventDetails {
diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java
index 23a452e0..8560c369 100644
--- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java
+++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java
@@ -4,6 +4,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
+import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Delegate;
@@ -15,6 +16,7 @@
* not be modified after instantiation.
*/
@ToString
+@EqualsAndHashCode
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
public final class ImmutableContext implements EvaluationContext {
diff --git a/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java b/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java
index c2b6f583..7f57a174 100644
--- a/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java
+++ b/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java
@@ -97,6 +97,14 @@ public T getValue(final String key, final Class type) {
}
}
+ public boolean isEmpty() {
+ return metadata.isEmpty();
+ }
+
+ public boolean isNotEmpty() {
+ return !metadata.isEmpty();
+ }
+
/**
* Obtain a builder for {@link ImmutableMetadata}.
*/
diff --git a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java
index c47a49eb..84935942 100644
--- a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java
+++ b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java
@@ -18,7 +18,7 @@
* not be modified after instantiation. All references are clones.
*/
@ToString
-@EqualsAndHashCode
+@EqualsAndHashCode(callSuper = true)
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
public final class ImmutableStructure extends AbstractStructure {
@@ -38,7 +38,7 @@ public ImmutableStructure(Map attributes) {
super(copyAttributes(attributes, null));
}
- protected ImmutableStructure(String targetingKey, Map attributes) {
+ ImmutableStructure(String targetingKey, Map attributes) {
super(copyAttributes(attributes, targetingKey));
}
@@ -70,12 +70,14 @@ private static Map copyAttributes(Map in) {
private static Map copyAttributes(Map in, String targetingKey) {
Map copy = new HashMap<>();
- for (Entry entry : in.entrySet()) {
- copy.put(
- entry.getKey(),
- Optional.ofNullable(entry.getValue())
- .map((Value val) -> val.clone())
- .orElse(null));
+ if (in != null) {
+ for (Entry entry : in.entrySet()) {
+ copy.put(
+ entry.getKey(),
+ Optional.ofNullable(entry.getValue())
+ .map((Value val) -> val.clone())
+ .orElse(null));
+ }
}
if (targetingKey != null) {
copy.put(EvaluationContext.TARGETING_KEY, new Value(targetingKey));
diff --git a/src/main/java/dev/openfeature/sdk/MutableStructure.java b/src/main/java/dev/openfeature/sdk/MutableStructure.java
index a06e2f2d..f3158456 100644
--- a/src/main/java/dev/openfeature/sdk/MutableStructure.java
+++ b/src/main/java/dev/openfeature/sdk/MutableStructure.java
@@ -15,8 +15,8 @@
* be modified after instantiation.
*/
@ToString
-@EqualsAndHashCode
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
+@EqualsAndHashCode(callSuper = true)
public class MutableStructure extends AbstractStructure {
public MutableStructure() {
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
index 9175a7cd..bd60cc78 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
@@ -29,7 +29,7 @@ public class OpenFeatureAPI implements EventBus {
protected OpenFeatureAPI() {
apiHooks = new ArrayList<>();
- providerRepository = new ProviderRepository();
+ providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
transactionContextPropagator = new NoOpTransactionContextPropagator();
}
@@ -333,7 +333,7 @@ public void shutdown() {
providerRepository.shutdown();
eventSupport.shutdown();
- providerRepository = new ProviderRepository();
+ providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
}
}
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
index 66f25f60..e68d28f7 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
@@ -178,12 +178,6 @@ private FlagEvaluationDetails evaluateFlag(
// provider must be accessed once to maintain a consistent reference
provider = stateManager.getProvider();
ProviderState state = stateManager.getState();
- if (ProviderState.NOT_READY.equals(state)) {
- throw new ProviderNotReadyError("provider not yet initialized");
- }
- if (ProviderState.FATAL.equals(state)) {
- throw new FatalError("provider is in an irrecoverable error state");
- }
mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
@@ -203,6 +197,14 @@ private FlagEvaluationDetails evaluateFlag(
afterHookContext =
HookContext.from(key, type, this.getMetadata(), provider.getMetadata(), mergedCtx, defaultValue);
+ // "short circuit" if the provider is in NOT_READY or FATAL state
+ if (ProviderState.NOT_READY.equals(state)) {
+ throw new ProviderNotReadyError("Provider not yet initialized");
+ }
+ if (ProviderState.FATAL.equals(state)) {
+ throw new FatalError("Provider is in an irrecoverable error state");
+ }
+
ProviderEvaluation providerEval =
(ProviderEvaluation) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx);
@@ -217,7 +219,7 @@ private FlagEvaluationDetails evaluateFlag(
}
} catch (Exception e) {
if (details == null) {
- details = FlagEvaluationDetails.builder().build();
+ details = FlagEvaluationDetails.builder().flagKey(key).build();
}
if (e instanceof OpenFeatureError) {
details.setErrorCode(((OpenFeatureError) e).getErrorCode());
@@ -507,7 +509,7 @@ public Client onProviderStale(Consumer handler) {
*/
@Override
public Client on(ProviderEvent event, Consumer handler) {
- OpenFeatureAPI.getInstance().addHandler(domain, event, handler);
+ openfeatureApi.addHandler(domain, event, handler);
return this;
}
@@ -516,7 +518,7 @@ public Client on(ProviderEvent event, Consumer handler) {
*/
@Override
public Client removeHandler(ProviderEvent event, Consumer handler) {
- OpenFeatureAPI.getInstance().removeHandler(domain, event, handler);
+ openfeatureApi.removeHandler(domain, event, handler);
return this;
}
}
diff --git a/src/main/java/dev/openfeature/sdk/ProviderRepository.java b/src/main/java/dev/openfeature/sdk/ProviderRepository.java
index bec86682..ab024a75 100644
--- a/src/main/java/dev/openfeature/sdk/ProviderRepository.java
+++ b/src/main/java/dev/openfeature/sdk/ProviderRepository.java
@@ -28,6 +28,11 @@ class ProviderRepository {
return thread;
});
private final Object registerStateManagerLock = new Object();
+ private final OpenFeatureAPI openFeatureAPI;
+
+ public ProviderRepository(OpenFeatureAPI openFeatureAPI) {
+ this.openFeatureAPI = openFeatureAPI;
+ }
FeatureProviderStateManager getFeatureProviderStateManager() {
return defaultStateManger.get();
@@ -205,7 +210,7 @@ private void initializeProvider(
FeatureProviderStateManager oldManager) {
try {
if (ProviderState.NOT_READY.equals(newManager.getState())) {
- newManager.initialize(OpenFeatureAPI.getInstance().getEvaluationContext());
+ newManager.initialize(openFeatureAPI.getEvaluationContext());
afterInit.accept(newManager.getProvider());
}
shutDownOld(oldManager, afterShutdown);
diff --git a/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java b/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java
index 61778d85..f2dc6b49 100644
--- a/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java
+++ b/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java
@@ -1,5 +1,6 @@
package dev.openfeature.sdk.providers.memory;
+import dev.openfeature.sdk.ImmutableMetadata;
import java.util.Map;
import lombok.Builder;
import lombok.Getter;
@@ -18,4 +19,5 @@ public class Flag {
private String defaultVariant;
private ContextEvaluator contextEvaluator;
+ private ImmutableMetadata flagMetadata;
}
diff --git a/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java b/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
index d3fdb985..3be1b631 100644
--- a/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
+++ b/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
@@ -152,6 +152,7 @@ private ProviderEvaluation getEvaluation(
.value(value)
.variant(flag.getDefaultVariant())
.reason(Reason.STATIC.toString())
+ .flagMetadata(flag.getFlagMetadata())
.build();
}
}
diff --git a/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java b/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java
index 8f304eaa..bd0ac2c2 100644
--- a/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java
+++ b/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java
@@ -1,14 +1,12 @@
package dev.openfeature.sdk;
-import dev.openfeature.sdk.exceptions.FlagNotFoundError;
-
public class AlwaysBrokenWithDetailsProvider implements FeatureProvider {
+ private final String name = "always broken with details";
+
@Override
public Metadata getMetadata() {
- return () -> {
- throw new FlagNotFoundError(TestConstants.BROKEN_MESSAGE);
- };
+ return () -> name;
}
@Override
diff --git a/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java b/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithExceptionProvider.java
similarity index 94%
rename from src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java
rename to src/test/java/dev/openfeature/sdk/AlwaysBrokenWithExceptionProvider.java
index 2f214d8a..0ad09db2 100644
--- a/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java
+++ b/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithExceptionProvider.java
@@ -2,7 +2,7 @@
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
-public class AlwaysBrokenProvider implements FeatureProvider {
+public class AlwaysBrokenWithExceptionProvider implements FeatureProvider {
private final String name = "always broken";
diff --git a/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java b/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java
index cd7e8b29..beadf7aa 100644
--- a/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java
+++ b/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java
@@ -2,17 +2,16 @@
import static org.junit.jupiter.api.Assertions.*;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import org.junit.jupiter.api.Test;
class ClientProviderMappingTest {
@Test
void clientProviderTest() {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+ OpenFeatureAPI api = new OpenFeatureAPI();
- FeatureProviderTestUtils.setFeatureProvider("client1", new DoSomethingProvider());
- FeatureProviderTestUtils.setFeatureProvider("client2", new NoOpProvider());
+ api.setProviderAndWait("client1", new DoSomethingProvider());
+ api.setProviderAndWait("client2", new NoOpProvider());
Client c1 = api.getClient("client1");
Client c2 = api.getClient("client2");
diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
index aacf0916..32fa605c 100644
--- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
+++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java
@@ -8,7 +8,6 @@
import static org.mockito.Mockito.verify;
import dev.openfeature.sdk.fixtures.HookFixtures;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.Arrays;
import java.util.HashMap;
@@ -16,14 +15,20 @@
import java.util.Map;
import java.util.Optional;
import lombok.SneakyThrows;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class DeveloperExperienceTest implements HookFixtures {
transient String flagKey = "mykey";
+ private OpenFeatureAPI api;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ api = new OpenFeatureAPI();
+ }
@Test
void simpleBooleanFlag() {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
Boolean retval = client.getBooleanValue(flagKey, false);
@@ -34,7 +39,6 @@ void simpleBooleanFlag() {
void clientHooks() {
Hook exampleHook = mockBooleanHook();
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
client.addHooks(exampleHook);
@@ -48,7 +52,6 @@ void evalHooks() {
Hook clientHook = mockBooleanHook();
Hook evalHook = mockBooleanHook();
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
client.addHooks(clientHook);
@@ -69,7 +72,6 @@ void evalHooks() {
@Test
void providingContext() {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(new TestEventsProvider());
Client client = api.getClient();
Map attributes = new HashMap<>();
@@ -86,8 +88,7 @@ void providingContext() {
@Test
void brokenProvider() {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
- FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client client = api.getClient();
FlagEvaluationDetails retval = client.getBooleanDetails(flagKey, false);
assertEquals(ErrorCode.FLAG_NOT_FOUND, retval.getErrorCode());
@@ -99,6 +100,9 @@ void brokenProvider() {
@Test
void providerLockedPerTransaction() {
+ final String defaultValue = "string-value";
+ final OpenFeatureAPI api = new OpenFeatureAPI();
+
class MutatingHook implements Hook {
@Override
@@ -106,16 +110,14 @@ class MutatingHook implements Hook {
// change the provider during a before hook - this should not impact the evaluation in progress
public Optional before(HookContext ctx, Map hints) {
- FeatureProviderTestUtils.setFeatureProvider(TestEventsProvider.newInitializedTestEventsProvider());
+ api.setProviderAndWait(TestEventsProvider.newInitializedTestEventsProvider());
return Optional.empty();
}
}
- final String defaultValue = "string-value";
- final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
final Client client = api.getClient();
- FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
+ api.setProviderAndWait(new DoSomethingProvider());
api.addHooks(new MutatingHook());
// if provider is changed during an evaluation transaction it should proceed with the original provider
@@ -132,7 +134,6 @@ public Optional before(HookContext ctx, Map hints) {
@Test
void setProviderAndWaitShouldPutTheProviderInReadyState() {
String domain = "domain";
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProviderAndWait(domain, new TestEventsProvider());
Client client = api.getClient(domain);
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
@@ -145,7 +146,6 @@ void setProviderAndWaitShouldPutTheProviderInReadyState() {
@Test
void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() {
String domain = "domain";
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
TestEventsProvider provider = new TestEventsProvider();
api.setProviderAndWait(domain, provider);
Client client = api.getClient(domain);
@@ -161,7 +161,6 @@ void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() {
@Test
void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() {
String domain = "domain";
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
TestEventsProvider provider = new TestEventsProvider();
api.setProviderAndWait(domain, provider);
Client client = api.getClient(domain);
@@ -177,7 +176,6 @@ void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() {
@Test
void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() {
String domain = "domain";
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
TestEventsProvider provider = new TestEventsProvider();
api.setProviderAndWait(domain, provider);
Client client = api.getClient(domain);
diff --git a/src/test/java/dev/openfeature/sdk/EventProviderTest.java b/src/test/java/dev/openfeature/sdk/EventProviderTest.java
index a159877f..ebf8901c 100644
--- a/src/test/java/dev/openfeature/sdk/EventProviderTest.java
+++ b/src/test/java/dev/openfeature/sdk/EventProviderTest.java
@@ -28,7 +28,7 @@ void setup() {
@AfterAll
public static void resetDefaultProvider() {
- OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
+ new OpenFeatureAPI().setProviderAndWait(new NoOpProvider());
}
@Test
@@ -91,7 +91,7 @@ void doesNotThrowWhenOnEmitSame() {
@DisplayName("should not deadlock on emit called during emit")
void doesNotDeadlockOnEmitStackedCalls() {
TestStackedEmitCallsProvider provider = new TestStackedEmitCallsProvider();
- OpenFeatureAPI.getInstance().setProviderAndWait(provider);
+ new OpenFeatureAPI().setProviderAndWait(provider);
}
static class TestEventProvider extends EventProvider {
diff --git a/src/test/java/dev/openfeature/sdk/EventsTest.java b/src/test/java/dev/openfeature/sdk/EventsTest.java
index e5902465..157c0baf 100644
--- a/src/test/java/dev/openfeature/sdk/EventsTest.java
+++ b/src/test/java/dev/openfeature/sdk/EventsTest.java
@@ -7,11 +7,11 @@
import static org.mockito.Mockito.*;
import dev.openfeature.sdk.testutils.TestEventsProvider;
-import io.cucumber.java.AfterAll;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import lombok.SneakyThrows;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -21,10 +21,11 @@ class EventsTest {
private static final int TIMEOUT = 500;
private static final int INIT_DELAY = TIMEOUT / 2;
+ private OpenFeatureAPI api;
- @AfterAll
- public static void resetDefaultProvider() {
- OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
+ @BeforeEach
+ public void setUp() throws Exception {
+ api = new OpenFeatureAPI();
}
@Nested
@@ -49,8 +50,8 @@ void apiInitReady() {
final String name = "apiInitReady";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().onProviderReady(handler);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
+ api.onProviderReady(handler);
+ api.setProviderAndWait(name, provider);
verify(handler, timeout(TIMEOUT).atLeastOnce()).accept(any());
}
@@ -66,8 +67,8 @@ void apiInitError() {
final String errMessage = "oh no!";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY, true, errMessage);
- OpenFeatureAPI.getInstance().onProviderError(handler);
- OpenFeatureAPI.getInstance().setProvider(name, provider);
+ api.onProviderError(handler);
+ api.setProvider(name, provider);
verify(handler, timeout(TIMEOUT)).accept(argThat(details -> {
return errMessage.equals(details.getMessage());
}));
@@ -89,8 +90,8 @@ void apiShouldPropagateEvents() {
final String name = "apiShouldPropagateEvents";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
- OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler);
+ api.setProviderAndWait(name, provider);
+ api.onProviderConfigurationChanged(handler);
provider.mockEvent(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED,
@@ -118,12 +119,12 @@ void apiShouldSupportAllEventTypes() {
final Consumer handler4 = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
+ api.setProviderAndWait(name, provider);
- OpenFeatureAPI.getInstance().onProviderReady(handler1);
- OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler2);
- OpenFeatureAPI.getInstance().onProviderStale(handler3);
- OpenFeatureAPI.getInstance().onProviderError(handler4);
+ api.onProviderReady(handler1);
+ api.onProviderConfigurationChanged(handler2);
+ api.onProviderStale(handler3);
+ api.onProviderError(handler4);
Arrays.asList(ProviderEvent.values()).stream().forEach(eventType -> {
provider.mockEvent(
@@ -162,8 +163,8 @@ void shouldPropagateDefaultAndAnon() {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
- OpenFeatureAPI.getInstance().setProviderAndWait(provider);
- Client client = OpenFeatureAPI.getInstance().getClient();
+ api.setProviderAndWait(provider);
+ Client client = api.getClient();
client.onProviderStale(handler);
provider.mockEvent(
@@ -183,8 +184,8 @@ void shouldPropagateDefaultAndNamed() {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
- OpenFeatureAPI.getInstance().setProviderAndWait(provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(provider);
+ Client client = api.getClient(name);
client.onProviderStale(handler);
provider.mockEvent(
@@ -213,10 +214,10 @@ void initReadyProviderBefore() {
final String name = "initReadyProviderBefore";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ Client client = api.getClient(name);
client.onProviderReady(handler);
// set provider after getting a client
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
+ api.setProviderAndWait(name, provider);
verify(handler, timeout(TIMEOUT).atLeastOnce())
.accept(argThat(details -> details.getDomain().equals(name)));
}
@@ -233,8 +234,8 @@ void initReadyProviderAfter() {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider);
+ Client client = api.getClient(name);
client.onProviderReady(handler);
verify(handler, timeout(TIMEOUT).atLeastOnce())
.accept(argThat(details -> details.getDomain().equals(name)));
@@ -252,10 +253,10 @@ void initErrorProviderAfter() {
final String errMessage = "oh no!";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY, true, errMessage);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ Client client = api.getClient(name);
client.onProviderError(handler);
// set provider after getting a client
- OpenFeatureAPI.getInstance().setProvider(name, provider);
+ api.setProvider(name, provider);
verify(handler, timeout(TIMEOUT)).accept(argThat(details -> {
return name.equals(details.getDomain()) && errMessage.equals(details.getMessage());
}));
@@ -274,8 +275,8 @@ void initErrorProviderBefore() {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY, true, errMessage);
// set provider after getting a client
- OpenFeatureAPI.getInstance().setProvider(name, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProvider(name, provider);
+ Client client = api.getClient(name);
client.onProviderError(handler);
verify(handler, timeout(TIMEOUT)).accept(argThat(details -> {
return name.equals(details.getDomain()) && errMessage.equals(details.getMessage());
@@ -299,8 +300,8 @@ void shouldPropagateBefore() {
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
// set provider before getting a client
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider);
+ Client client = api.getClient(name);
client.onProviderConfigurationChanged(handler);
provider.mockEvent(
@@ -322,10 +323,10 @@ void shouldPropagateAfter() {
final String name = "shouldPropagateAfter";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ Client client = api.getClient(name);
client.onProviderConfigurationChanged(handler);
// set provider after getting a client
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
+ api.setProviderAndWait(name, provider);
provider.mockEvent(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED,
@@ -354,8 +355,8 @@ void shouldSupportAllEventTypes() {
final Consumer handler4 = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider);
+ Client client = api.getClient(name);
client.onProviderReady(handler1);
client.onProviderConfigurationChanged(handler2);
@@ -384,14 +385,14 @@ void shouldNotRunHandlers() {
TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider1);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider1);
+ Client client = api.getClient(name);
// attached handlers
- OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler1);
+ api.onProviderConfigurationChanged(handler1);
client.onProviderConfigurationChanged(handler2);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider2);
+ api.setProviderAndWait(name, provider2);
// wait for the new provider to be ready and make sure things are cleaned up.
await().until(() -> provider1.isShutDown());
@@ -421,11 +422,11 @@ void otherClientHandlersShouldNotRun() {
TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name1, provider1);
- OpenFeatureAPI.getInstance().setProviderAndWait(name2, provider2);
+ api.setProviderAndWait(name1, provider1);
+ api.setProviderAndWait(name2, provider2);
- Client client1 = OpenFeatureAPI.getInstance().getClient(name1);
- Client client2 = OpenFeatureAPI.getInstance().getClient(name2);
+ Client client1 = api.getClient(name1);
+ Client client2 = api.getClient(name2);
client1.onProviderConfigurationChanged(handlerToRun);
client2.onProviderConfigurationChanged(handlerNotToRun);
@@ -450,11 +451,11 @@ void boundShouldNotRunWithDefault() {
TestEventsProvider namedProvider = new TestEventsProvider(INIT_DELAY);
TestEventsProvider defaultProvider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(defaultProvider);
+ api.setProviderAndWait(defaultProvider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ Client client = api.getClient(name);
client.onProviderConfigurationChanged(handlerNotToRun);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, namedProvider);
+ api.setProviderAndWait(name, namedProvider);
// await the new provider to make sure the old one is shut down
await().until(() -> namedProvider.getState().equals(ProviderState.READY));
@@ -465,7 +466,7 @@ void boundShouldNotRunWithDefault() {
ProviderEventDetails.builder().build());
verify(handlerNotToRun, after(TIMEOUT).never()).accept(any());
- OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
+ api.setProviderAndWait(new NoOpProvider());
}
@Test
@@ -479,9 +480,9 @@ void unboundShouldRunWithDefault() {
final Consumer handlerToRun = mockHandler();
TestEventsProvider defaultProvider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(defaultProvider);
+ api.setProviderAndWait(defaultProvider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ Client client = api.getClient(name);
client.onProviderConfigurationChanged(handlerToRun);
// await the new provider to make sure the old one is shut down
@@ -493,7 +494,7 @@ void unboundShouldRunWithDefault() {
ProviderEventDetails.builder().build());
verify(handlerToRun, timeout(TIMEOUT)).accept(any());
- OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
+ api.setProviderAndWait(new NoOpProvider());
}
@Test
@@ -509,9 +510,9 @@ void handlersRunIfOneThrows() {
final Consumer lastHandler = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
+ api.setProviderAndWait(name, provider);
- Client client1 = OpenFeatureAPI.getInstance().getClient(name);
+ Client client1 = api.getClient(name);
client1.onProviderConfigurationChanged(errorHandler);
client1.onProviderConfigurationChanged(nextHandler);
@@ -537,11 +538,11 @@ void shouldHaveAllProperties() {
final String name = "shouldHaveAllProperties";
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider);
+ Client client = api.getClient(name);
// attached handlers
- OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler1);
+ api.onProviderConfigurationChanged(handler1);
client.onProviderConfigurationChanged(handler2);
List flagsChanged = Arrays.asList("flag");
@@ -582,10 +583,10 @@ void matchingReadyEventsMustRunImmediately() {
// provider which is already ready
TestEventsProvider provider = new TestEventsProvider();
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
+ api.setProviderAndWait(name, provider);
// should run even thought handler was added after ready
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ Client client = api.getClient(name);
client.onProviderReady(handler);
verify(handler, timeout(TIMEOUT)).accept(any());
}
@@ -598,7 +599,6 @@ void matchingReadyEventsMustRunImmediately() {
void matchingStaleEventsMustRunImmediately() {
final String name = "matchingEventsMustRunImmediately";
final Consumer handler = mockHandler();
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
// provider which is already stale
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
@@ -620,7 +620,6 @@ void matchingStaleEventsMustRunImmediately() {
void matchingErrorEventsMustRunImmediately() {
final String name = "matchingEventsMustRunImmediately";
final Consumer handler = mockHandler();
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
// provider which is already in error
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
@@ -629,6 +628,7 @@ void matchingErrorEventsMustRunImmediately() {
provider.emitProviderError(ProviderEventDetails.builder().build());
assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR);
+ verify(handler, never()).accept(any());
// should run even though handler was added after error
client.onProviderError(handler);
verify(handler, timeout(TIMEOUT)).accept(any());
@@ -644,8 +644,8 @@ void mustPersistAcrossChanges() {
TestEventsProvider provider1 = new TestEventsProvider(INIT_DELAY);
TestEventsProvider provider2 = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider1);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider1);
+ Client client = api.getClient(name);
client.onProviderConfigurationChanged(handler);
provider1.mockEvent(
@@ -657,7 +657,7 @@ void mustPersistAcrossChanges() {
verify(handler, timeout(TIMEOUT).times(1)).accept(argThat(nameMatches));
// wait for the new provider to be ready.
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider2);
+ api.setProviderAndWait(name, provider2);
// verify that with the new provider under the same name, the handler is called
// again.
@@ -681,14 +681,14 @@ void removedEventsShouldNotRun() {
final Consumer handler2 = mockHandler();
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
- OpenFeatureAPI.getInstance().setProviderAndWait(name, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(name);
+ api.setProviderAndWait(name, provider);
+ Client client = api.getClient(name);
// attached handlers
- OpenFeatureAPI.getInstance().onProviderStale(handler1);
+ api.onProviderStale(handler1);
client.onProviderConfigurationChanged(handler2);
- OpenFeatureAPI.getInstance().removeHandler(ProviderEvent.PROVIDER_STALE, handler1);
+ api.removeHandler(ProviderEvent.PROVIDER_STALE, handler1);
client.removeHandler(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, handler2);
// emit event
diff --git a/src/test/java/dev/openfeature/sdk/FatalErrorProvider.java b/src/test/java/dev/openfeature/sdk/FatalErrorProvider.java
new file mode 100644
index 00000000..9ebd2475
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/FatalErrorProvider.java
@@ -0,0 +1,45 @@
+package dev.openfeature.sdk;
+
+import dev.openfeature.sdk.exceptions.FatalError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+
+public class FatalErrorProvider implements FeatureProvider {
+
+ private final String name = "fatal";
+
+ @Override
+ public Metadata getMetadata() {
+ return () -> name;
+ }
+
+ @Override
+ public void initialize(EvaluationContext evaluationContext) throws Exception {
+ throw new FatalError(); // throw a fatal error on startup (this will cause the SDK to short circuit evaluations)
+ }
+
+ @Override
+ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+ throw new GeneralError(TestConstants.BROKEN_MESSAGE);
+ }
+
+ @Override
+ public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+ throw new GeneralError(TestConstants.BROKEN_MESSAGE);
+ }
+
+ @Override
+ public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+ throw new GeneralError(TestConstants.BROKEN_MESSAGE);
+ }
+
+ @Override
+ public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+ throw new GeneralError(TestConstants.BROKEN_MESSAGE);
+ }
+
+ @Override
+ public ProviderEvaluation getObjectEvaluation(
+ String key, Value defaultValue, EvaluationContext invocationContext) {
+ throw new GeneralError(TestConstants.BROKEN_MESSAGE);
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
index 2ad88d32..3b02b172 100644
--- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
@@ -9,7 +9,6 @@
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.fixtures.HookFixtures;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.HashMap;
import java.util.List;
@@ -29,7 +28,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
private OpenFeatureAPI api;
private Client _client() {
- FeatureProviderTestUtils.setFeatureProvider(new NoOpProvider());
+ api.setProviderAndWait(new NoOpProvider());
return api.getClient();
}
@@ -37,18 +36,13 @@ private Client _client() {
private Client _initializedClient() {
TestEventsProvider provider = new TestEventsProvider();
provider.initialize(null);
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
return api.getClient();
}
@BeforeEach
void getApiInstance() {
- api = OpenFeatureAPI.getInstance();
- }
-
- @AfterEach
- void reset_ctx() {
- api.setEvaluationContext(null);
+ api = new OpenFeatureAPI();
}
@BeforeEach
@@ -62,15 +56,6 @@ void reset_logs() {
LoggerMock.setMock(OpenFeatureClient.class, logger);
}
- @Specification(
- number = "1.1.1",
- text =
- "The API, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the API are present at runtime.")
- @Test
- void global_singleton() {
- assertSame(OpenFeatureAPI.getInstance(), OpenFeatureAPI.getInstance());
- }
-
@Specification(
number = "1.1.2.1",
text =
@@ -78,7 +63,7 @@ void global_singleton() {
@Test
void provider() {
FeatureProvider mockProvider = mock(FeatureProvider.class);
- FeatureProviderTestUtils.setFeatureProvider(mockProvider);
+ api.setProviderAndWait(mockProvider);
assertThat(api.getProvider()).isEqualTo(mockProvider);
}
@@ -90,13 +75,13 @@ void provider() {
@Test
void providerAndWait() {
FeatureProvider provider = new TestEventsProvider(500);
- OpenFeatureAPI.getInstance().setProviderAndWait(provider);
+ api.setProviderAndWait(provider);
Client client = api.getClient();
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
provider = new TestEventsProvider(500);
String providerName = "providerAndWait";
- OpenFeatureAPI.getInstance().setProviderAndWait(providerName, provider);
+ api.setProviderAndWait(providerName, provider);
Client client2 = api.getClient(providerName);
assertThat(client2.getProviderState()).isEqualTo(ProviderState.READY);
}
@@ -124,8 +109,8 @@ void providerAndWaitError() {
void shouldReturnNotReadyIfNotInitialized() {
FeatureProvider provider = new TestEventsProvider(100);
String providerName = "shouldReturnNotReadyIfNotInitialized";
- OpenFeatureAPI.getInstance().setProvider(providerName, provider);
- Client client = OpenFeatureAPI.getInstance().getClient(providerName);
+ api.setProvider(providerName, provider);
+ Client client = api.getClient(providerName);
FlagEvaluationDetails details = client.getBooleanDetails("return_error_when_not_initialized", false);
assertEquals(ErrorCode.PROVIDER_NOT_READY, details.getErrorCode());
assertEquals(Reason.ERROR.toString(), details.getReason());
@@ -136,7 +121,7 @@ void shouldReturnNotReadyIfNotInitialized() {
text = "The API MUST provide a function for retrieving the metadata field of the configured provider.")
@Test
void provider_metadata() {
- FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
+ api.setProviderAndWait(new DoSomethingProvider());
assertThat(api.getProviderMetadata().getName()).isEqualTo(DoSomethingProvider.name);
}
@@ -198,7 +183,7 @@ void hookRegistration() {
"The client SHOULD provide functions for floating-point numbers and integers, consistent with language idioms.")
@Test
void value_flags() {
- FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
+ api.setProviderAndWait(new DoSomethingProvider());
Client c = api.getClient();
String key = "key";
@@ -279,7 +264,7 @@ void value_flags() {
"In cases of normal execution, the `evaluation details` structure's `reason` field MUST contain the value of the `reason` field in the `flag resolution` structure returned by the configured `provider`, if the field is set.")
@Test
void detail_flags() {
- FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider());
+ api.setProviderAndWait(new DoSomethingProvider());
Client c = api.getClient();
String key = "key";
@@ -386,7 +371,7 @@ void hooks() {
"In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional details about the nature of the error.")
@Test
void broken_provider() {
- FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
boolean defaultValue = false;
assertFalse(c.getBooleanValue("key", defaultValue));
@@ -414,8 +399,8 @@ void broken_provider() {
text =
"In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional details about the nature of the error.")
@Test
- void broken_provider_withDetails() {
- FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenWithDetailsProvider());
+ void broken_provider_withDetails() throws InterruptedException {
+ api.setProviderAndWait(new AlwaysBrokenWithDetailsProvider());
Client c = api.getClient();
boolean defaultValue = false;
assertFalse(c.getBooleanValue("key", defaultValue));
@@ -431,7 +416,7 @@ void broken_provider_withDetails() {
text = "Methods, functions, or operations on the client SHOULD NOT write log messages.")
@Test
void log_on_error() throws NotImplementedException {
- FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
FlagEvaluationDetails result = c.getBooleanDetails("test", false);
@@ -450,7 +435,7 @@ void clientMetadata() {
assertNull(c.getMetadata().getDomain());
String domainName = "test domain";
- FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c2 = api.getClient(domainName);
assertEquals(domainName, c2.getMetadata().getName());
@@ -463,7 +448,7 @@ void clientMetadata() {
"In cases of abnormal execution (network failure, unhandled error, etc) the reason field in the evaluation details SHOULD indicate an error.")
@Test
void reason_is_error_when_there_are_errors() {
- FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
FlagEvaluationDetails result = c.getBooleanDetails("test", false);
assertEquals(Reason.ERROR.toString(), result.getReason());
@@ -475,7 +460,7 @@ void reason_is_error_when_there_are_errors() {
"If the flag metadata field in the flag resolution structure returned by the configured provider is set, the evaluation details structure's flag metadata field MUST contain that value. Otherwise, it MUST contain an empty record.")
@Test
void flag_metadata_passed() {
- FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider(null));
+ api.setProviderAndWait(new DoSomethingProvider(null));
Client c = api.getClient();
FlagEvaluationDetails result = c.getBooleanDetails("test", false);
assertNotNull(result.getFlagMetadata());
@@ -487,7 +472,7 @@ void api_context() {
String contextKey = "some-key";
String contextValue = "some-value";
DoSomethingProvider provider = spy(new DoSomethingProvider());
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
Map attributes = new HashMap<>();
attributes.put(contextKey, new Value(contextValue));
@@ -514,7 +499,7 @@ void api_context() {
@Test
void multi_layer_context_merges_correctly() {
DoSomethingProvider provider = spy(new DoSomethingProvider());
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);
Hook hook = spy(new Hook() {
@@ -702,7 +687,7 @@ public void after(
@Test
void setting_transaction_context_propagator() {
DoSomethingProvider provider = new DoSomethingProvider();
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);
@@ -716,7 +701,7 @@ void setting_transaction_context_propagator() {
@Test
void setting_transaction_context() {
DoSomethingProvider provider = new DoSomethingProvider();
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);
diff --git a/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java b/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java
index f8b9ba58..22912661 100644
--- a/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java
+++ b/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java
@@ -1,6 +1,8 @@
package dev.openfeature.sdk;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -9,7 +11,7 @@ class FlagMetadataTest {
@Test
@DisplayName("Test metadata payload construction and retrieval")
- public void builder_validation() {
+ void builder_validation() {
// given
ImmutableMetadata flagMetadata = ImmutableMetadata.builder()
.addString("string", "string")
@@ -42,7 +44,7 @@ public void builder_validation() {
@Test
@DisplayName("Value type mismatch returns a null")
- public void value_type_validation() {
+ void value_type_validation() {
// given
ImmutableMetadata flagMetadata =
ImmutableMetadata.builder().addString("string", "string").build();
@@ -53,11 +55,34 @@ public void value_type_validation() {
@Test
@DisplayName("A null is returned if key does not exist")
- public void notfound_error_validation() {
+ void notfound_error_validation() {
// given
ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build();
// then
assertThat(flagMetadata.getBoolean("string")).isNull();
}
+
+ @Test
+ @DisplayName("isEmpty and isNotEmpty return correctly when the metadata is empty")
+ void isEmpty_isNotEmpty_return_correctly_when_metadata_is_empty() {
+ // given
+ ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build();
+
+ // then
+ assertTrue(flagMetadata.isEmpty());
+ assertFalse(flagMetadata.isNotEmpty());
+ }
+
+ @Test
+ @DisplayName("isEmpty and isNotEmpty return correctly when the metadata is not empty")
+ void isEmpty_isNotEmpty_return_correctly_when_metadata_is_not_empty() {
+ // given
+ ImmutableMetadata flagMetadata =
+ ImmutableMetadata.builder().addString("a", "b").build();
+
+ // then
+ assertFalse(flagMetadata.isEmpty());
+ assertTrue(flagMetadata.isNotEmpty());
+ }
}
diff --git a/src/test/java/dev/openfeature/sdk/HookSpecTest.java b/src/test/java/dev/openfeature/sdk/HookSpecTest.java
index d6247c64..3a953d18 100644
--- a/src/test/java/dev/openfeature/sdk/HookSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/HookSpecTest.java
@@ -18,7 +18,6 @@
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
import dev.openfeature.sdk.fixtures.HookFixtures;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.ArrayList;
import java.util.Arrays;
@@ -28,16 +27,18 @@
import java.util.Map;
import java.util.Optional;
import lombok.SneakyThrows;
-import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
class HookSpecTest implements HookFixtures {
- @AfterEach
- void emptyApiHooks() {
- // it's a singleton. Don't pollute each test.
- OpenFeatureAPI.getInstance().clearHooks();
+
+ private OpenFeatureAPI api;
+
+ @BeforeEach
+ void setUp() {
+ this.api = new OpenFeatureAPI();
}
@Specification(
@@ -163,7 +164,7 @@ void optional_properties() {
.type(FlagValueType.INTEGER)
.ctx(new ImmutableContext())
.defaultValue(1)
- .clientMetadata(OpenFeatureAPI.getInstance().getClient().getMetadata())
+ .clientMetadata(api.getClient().getMetadata())
.build();
}
@@ -173,8 +174,8 @@ void optional_properties() {
"The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing.")
@Test
void before_runs_ahead_of_evaluation() {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
- api.setProviderAndWait(new AlwaysBrokenProvider());
+
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client client = api.getClient();
Hook evalHook = mockBooleanHook();
@@ -216,8 +217,7 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() {
.errorMessage(errorMessage)
.build());
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
- FeatureProviderTestUtils.setFeatureProvider("errorHookMustRun", provider);
+ api.setProviderAndWait("errorHookMustRun", provider);
Client client = api.getClient("errorHookMustRun");
client.getBooleanValue(
"key",
@@ -259,7 +259,7 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() {
@Test
void hook_eval_order() {
List evalOrder = new ArrayList<>();
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+
api.setProviderAndWait("evalOrder", new TestEventsProvider() {
public List getProviderHooks() {
return Collections.singletonList(new BooleanHook() {
@@ -411,8 +411,7 @@ void error_stops_before() {
doThrow(RuntimeException.class).when(h).before(any(), any());
Hook h2 = mockBooleanHook();
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
- api.setProviderAndWait(new AlwaysBrokenProvider());
+ api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
Client c = api.getClient();
c.getBooleanDetails(
@@ -516,8 +515,7 @@ void flag_eval_hook_order() {
.thenReturn(ProviderEvaluation.builder().value(true).build());
InOrder order = inOrder(hook, provider);
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
Client client = api.getClient();
client.getBooleanValue(
"key",
@@ -596,6 +594,25 @@ void erroneous_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails() {
assertThat(evaluationDetails.getValue()).isTrue();
}
+ @Test
+ void shortCircuit_flagResolution_runsHooksWithAllFields() {
+ String domain = "shortCircuit_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails";
+ api.setProvider(domain, new FatalErrorProvider());
+
+ Hook hook = mockBooleanHook();
+ String flagKey = "test-flag-key";
+ Client client = api.getClient(domain);
+ client.getBooleanValue(
+ flagKey,
+ true,
+ new ImmutableContext(),
+ FlagEvaluationOptions.builder().hook(hook).build());
+
+ verify(hook).before(any(), any());
+ verify(hook).error(any(HookContext.class), any(Exception.class), any(Map.class));
+ verify(hook).finallyAfter(any(HookContext.class), any(FlagEvaluationDetails.class), any(Map.class));
+ }
+
@Test
void successful_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails() {
Hook hook = mockBooleanHook();
@@ -695,8 +712,7 @@ void mergeHappensCorrectly() {
when(provider.getBooleanEvaluation(any(), any(), any()))
.thenReturn(ProviderEvaluation.builder().value(true).build());
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
Client client = api.getClient();
client.getBooleanValue(
"key",
@@ -761,11 +777,10 @@ void first_error_broken() {
}
private Client getClient(FeatureProvider provider) {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
if (provider == null) {
- FeatureProviderTestUtils.setFeatureProvider(TestEventsProvider.newInitializedTestEventsProvider());
+ api.setProviderAndWait(TestEventsProvider.newInitializedTestEventsProvider());
} else {
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
}
return api.getClient();
}
diff --git a/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java b/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java
index e69a974b..2b39be74 100644
--- a/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java
+++ b/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java
@@ -3,6 +3,7 @@
import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
@@ -133,4 +134,31 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() {
Structure value = key1.asStructure();
assertArrayEquals(new Object[] {"key1_1"}, value.keySet().toArray());
}
+
+ @DisplayName("Two different MutableContext objects with the different contents are not considered equal")
+ @Test
+ void unequalImmutableContextsAreNotEqual() {
+ final Map attributes = new HashMap<>();
+ attributes.put("key1", new Value("val1"));
+ final ImmutableContext ctx = new ImmutableContext(attributes);
+
+ final Map attributes2 = new HashMap<>();
+ final ImmutableContext ctx2 = new ImmutableContext(attributes2);
+
+ assertNotEquals(ctx, ctx2);
+ }
+
+ @DisplayName("Two different MutableContext objects with the same content are considered equal")
+ @Test
+ void equalImmutableContextsAreEqual() {
+ final Map attributes = new HashMap<>();
+ attributes.put("key1", new Value("val1"));
+ final ImmutableContext ctx = new ImmutableContext(attributes);
+
+ final Map attributes2 = new HashMap<>();
+ attributes2.put("key1", new Value("val1"));
+ final ImmutableContext ctx2 = new ImmutableContext(attributes2);
+
+ assertEquals(ctx, ctx2);
+ }
}
diff --git a/src/test/java/dev/openfeature/sdk/ImmutableMetadataTest.java b/src/test/java/dev/openfeature/sdk/ImmutableMetadataTest.java
new file mode 100644
index 00000000..e3bd0316
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/ImmutableMetadataTest.java
@@ -0,0 +1,28 @@
+package dev.openfeature.sdk;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ImmutableMetadataTest {
+ @Test
+ void unequalImmutableMetadataAreUnequal() {
+ ImmutableMetadata i1 =
+ ImmutableMetadata.builder().addString("key1", "value1").build();
+ ImmutableMetadata i2 =
+ ImmutableMetadata.builder().addString("key1", "value2").build();
+
+ assertNotEquals(i1, i2);
+ }
+
+ @Test
+ void equalImmutableMetadataAreEqual() {
+ ImmutableMetadata i1 =
+ ImmutableMetadata.builder().addString("key1", "value1").build();
+ ImmutableMetadata i2 =
+ ImmutableMetadata.builder().addString("key1", "value1").build();
+
+ assertEquals(i1, i2);
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java b/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java
index dff95adc..6a0eed59 100644
--- a/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java
+++ b/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java
@@ -1,6 +1,11 @@
package dev.openfeature.sdk;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -154,4 +159,42 @@ void constructorHandlesNullValue() {
attrs.put("null", null);
new ImmutableStructure(attrs);
}
+
+ @Test
+ void unequalImmutableStructuresAreNotEqual() {
+ Map attrs1 = new HashMap<>();
+ attrs1.put("test", new Value(45));
+ ImmutableStructure structure1 = new ImmutableStructure(attrs1);
+
+ Map attrs2 = new HashMap<>();
+ attrs2.put("test", new Value(2));
+ ImmutableStructure structure2 = new ImmutableStructure(attrs2);
+
+ assertNotEquals(structure1, structure2);
+ }
+
+ @Test
+ void equalImmutableStructuresAreEqual() {
+ Map attrs1 = new HashMap<>();
+ attrs1.put("test", new Value(45));
+ ImmutableStructure structure1 = new ImmutableStructure(attrs1);
+
+ Map attrs2 = new HashMap<>();
+ attrs2.put("test", new Value(45));
+ ImmutableStructure structure2 = new ImmutableStructure(attrs2);
+
+ assertEquals(structure1, structure2);
+ }
+
+ @Test
+ void emptyImmutableStructureIsEmpty() {
+ ImmutableStructure m1 = new ImmutableStructure();
+ assertTrue(m1.isEmpty());
+ }
+
+ @Test
+ void immutableStructureWithNullAttributesIsEmpty() {
+ ImmutableStructure m1 = new ImmutableStructure(null);
+ assertTrue(m1.isEmpty());
+ }
}
diff --git a/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java b/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java
index 3353f564..4bcd7312 100644
--- a/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java
@@ -17,10 +17,12 @@
class InitializeBehaviorSpecTest {
private static final String DOMAIN_NAME = "mydomain";
+ private OpenFeatureAPI api;
@BeforeEach
void setupTest() {
- OpenFeatureAPI.getInstance().setProvider(new NoOpProvider());
+ this.api = new OpenFeatureAPI();
+ api.setProvider(new NoOpProvider());
}
@Nested
@@ -37,7 +39,7 @@ void mustCallInitializeFunctionOfTheNewlyRegisteredProviderBeforeUsingItForFlagE
FeatureProvider featureProvider = mock(FeatureProvider.class);
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
- OpenFeatureAPI.getInstance().setProvider(featureProvider);
+ api.setProvider(featureProvider);
verify(featureProvider, timeout(1000)).initialize(any());
}
@@ -55,8 +57,7 @@ void shouldCatchExceptionThrownByTheProviderOnInitialization() throws Exception
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
doThrow(TestException.class).when(featureProvider).initialize(any());
- assertThatCode(() -> OpenFeatureAPI.getInstance().setProvider(featureProvider))
- .doesNotThrowAnyException();
+ assertThatCode(() -> api.setProvider(featureProvider)).doesNotThrowAnyException();
verify(featureProvider, timeout(1000)).initialize(any());
}
@@ -77,7 +78,7 @@ void mustCallInitializeFunctionOfTheNewlyRegisteredNamedProviderBeforeUsingItFor
FeatureProvider featureProvider = mock(FeatureProvider.class);
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
- OpenFeatureAPI.getInstance().setProvider(DOMAIN_NAME, featureProvider);
+ api.setProvider(DOMAIN_NAME, featureProvider);
verify(featureProvider, timeout(1000)).initialize(any());
}
@@ -95,8 +96,7 @@ void shouldCatchExceptionThrownByTheNamedClientProviderOnInitialization() throws
doReturn(ProviderState.NOT_READY).when(featureProvider).getState();
doThrow(TestException.class).when(featureProvider).initialize(any());
- assertThatCode(() -> OpenFeatureAPI.getInstance().setProvider(DOMAIN_NAME, featureProvider))
- .doesNotThrowAnyException();
+ assertThatCode(() -> api.setProvider(DOMAIN_NAME, featureProvider)).doesNotThrowAnyException();
verify(featureProvider, timeout(1000)).initialize(any());
}
diff --git a/src/test/java/dev/openfeature/sdk/LockingTest.java b/src/test/java/dev/openfeature/sdk/LockingSingeltonTest.java
similarity index 99%
rename from src/test/java/dev/openfeature/sdk/LockingTest.java
rename to src/test/java/dev/openfeature/sdk/LockingSingeltonTest.java
index 4b7af553..ad86f4bc 100644
--- a/src/test/java/dev/openfeature/sdk/LockingTest.java
+++ b/src/test/java/dev/openfeature/sdk/LockingSingeltonTest.java
@@ -15,7 +15,7 @@
import org.junit.jupiter.api.parallel.Isolated;
@Isolated()
-class LockingTest {
+class LockingSingeltonTest {
private static OpenFeatureAPI api;
private OpenFeatureClient client;
diff --git a/src/test/java/dev/openfeature/sdk/MutableContextTest.java b/src/test/java/dev/openfeature/sdk/MutableContextTest.java
index 953e3f63..6c471d09 100644
--- a/src/test/java/dev/openfeature/sdk/MutableContextTest.java
+++ b/src/test/java/dev/openfeature/sdk/MutableContextTest.java
@@ -3,6 +3,7 @@
import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
@@ -137,4 +138,31 @@ void shouldAllowChainingOfMutations() {
assertEquals(2, context.getValue("key2").asInteger());
assertEquals(3.0, context.getValue("key3").asDouble());
}
+
+ @DisplayName("Two different MutableContext objects with the different contents are not considered equal")
+ @Test
+ void unequalMutableContextsAreNotEqual() {
+ final Map attributes = new HashMap<>();
+ attributes.put("key1", new Value("val1"));
+ final MutableContext ctx = new MutableContext(attributes);
+
+ final Map attributes2 = new HashMap<>();
+ final MutableContext ctx2 = new MutableContext(attributes2);
+
+ assertNotEquals(ctx, ctx2);
+ }
+
+ @DisplayName("Two different MutableContext objects with the same content are considered equal")
+ @Test
+ void equalMutableContextsAreEqual() {
+ final Map attributes = new HashMap<>();
+ attributes.put("key1", new Value("val1"));
+ final MutableContext ctx = new MutableContext(attributes);
+
+ final Map attributes2 = new HashMap<>();
+ attributes2.put("key1", new Value("val1"));
+ final MutableContext ctx2 = new MutableContext(attributes2);
+
+ assertEquals(ctx, ctx2);
+ }
}
diff --git a/src/test/java/dev/openfeature/sdk/MutableStructureTest.java b/src/test/java/dev/openfeature/sdk/MutableStructureTest.java
new file mode 100644
index 00000000..ebd11af0
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/MutableStructureTest.java
@@ -0,0 +1,67 @@
+package dev.openfeature.sdk;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+
+class MutableStructureTest {
+
+ @Test
+ void emptyMutableStructureIsEmpty() {
+ MutableStructure m1 = new MutableStructure();
+ assertTrue(m1.isEmpty());
+ }
+
+ @Test
+ void mutableStructureWithNullBackingStructureIsEmpty() {
+ MutableStructure m1 = new MutableStructure(null);
+ assertTrue(m1.isEmpty());
+ }
+
+ @Test
+ void unequalMutableStructuresAreNotEqual() {
+ MutableStructure m1 = new MutableStructure();
+ m1.add("key1", "val1");
+ MutableStructure m2 = new MutableStructure();
+ m2.add("key2", "val2");
+ assertNotEquals(m1, m2);
+ }
+
+ @Test
+ void equalMutableStructuresAreEqual() {
+ MutableStructure m1 = new MutableStructure();
+ m1.add("key1", "val1");
+ MutableStructure m2 = new MutableStructure();
+ m2.add("key1", "val1");
+ assertEquals(m1, m2);
+ }
+
+ @Test
+ void equalAbstractStructuresOfDifferentTypesAreNotEqual() {
+ MutableStructure m1 = new MutableStructure();
+ m1.add("key1", "val1");
+ HashMap map = new HashMap<>();
+ map.put("key1", new Value("val1"));
+ AbstractStructure m2 = new AbstractStructure(map) {
+ @Override
+ public Set keySet() {
+ return attributes.keySet();
+ }
+
+ @Override
+ public Value getValue(String key) {
+ return attributes.get(key);
+ }
+
+ @Override
+ public Map asMap() {
+ return attributes;
+ }
+ };
+
+ assertNotEquals(m1, m2);
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPISingeltonTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPISingeltonTest.java
new file mode 100644
index 00000000..dd9916ee
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPISingeltonTest.java
@@ -0,0 +1,17 @@
+package dev.openfeature.sdk;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.junit.jupiter.api.Test;
+
+class OpenFeatureAPISingeltonTest {
+
+ @Specification(
+ number = "1.1.1",
+ text =
+ "The API, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the API are present at runtime.")
+ @Test
+ void global_singleton() {
+ assertSame(OpenFeatureAPI.getInstance(), OpenFeatureAPI.getInstance());
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java
index 63145ecb..e8e8b27b 100644
--- a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java
@@ -8,7 +8,6 @@
import static org.mockito.Mockito.verify;
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import dev.openfeature.sdk.testutils.TestEventsProvider;
import java.util.Collections;
import java.util.HashMap;
@@ -23,13 +22,13 @@ class OpenFeatureAPITest {
@BeforeEach
void setupTest() {
- api = OpenFeatureAPI.getInstance();
+ api = new OpenFeatureAPI();
}
@Test
void namedProviderTest() {
FeatureProvider provider = new NoOpProvider();
- FeatureProviderTestUtils.setFeatureProvider("namedProviderTest", provider);
+ api.setProviderAndWait("namedProviderTest", provider);
assertThat(provider.getMetadata().getName())
.isEqualTo(api.getProviderMetadata("namedProviderTest").getName());
@@ -44,14 +43,10 @@ void namedProviderOverwrittenTest() {
String domain = "namedProviderOverwrittenTest";
FeatureProvider provider1 = new NoOpProvider();
FeatureProvider provider2 = new DoSomethingProvider();
- FeatureProviderTestUtils.setFeatureProvider(domain, provider1);
- FeatureProviderTestUtils.setFeatureProvider(domain, provider2);
-
- assertThat(OpenFeatureAPI.getInstance()
- .getProvider(domain)
- .getMetadata()
- .getName())
- .isEqualTo(DoSomethingProvider.name);
+ api.setProviderAndWait(domain, provider1);
+ api.setProviderAndWait(domain, provider2);
+
+ assertThat(api.getProvider(domain).getMetadata().getName()).isEqualTo(DoSomethingProvider.name);
}
@Test
@@ -60,17 +55,17 @@ void providerToMultipleNames() throws Exception {
FeatureProvider noOpAsNonEventingProvider = new NoOpProvider();
// register same provider for multiple names & as default provider
- OpenFeatureAPI.getInstance().setProviderAndWait(inMemAsEventingProvider);
- OpenFeatureAPI.getInstance().setProviderAndWait("clientA", inMemAsEventingProvider);
- OpenFeatureAPI.getInstance().setProviderAndWait("clientB", inMemAsEventingProvider);
- OpenFeatureAPI.getInstance().setProviderAndWait("clientC", noOpAsNonEventingProvider);
- OpenFeatureAPI.getInstance().setProviderAndWait("clientD", noOpAsNonEventingProvider);
-
- assertEquals(inMemAsEventingProvider, OpenFeatureAPI.getInstance().getProvider());
- assertEquals(inMemAsEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientA"));
- assertEquals(inMemAsEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientB"));
- assertEquals(noOpAsNonEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientC"));
- assertEquals(noOpAsNonEventingProvider, OpenFeatureAPI.getInstance().getProvider("clientD"));
+ api.setProviderAndWait(inMemAsEventingProvider);
+ api.setProviderAndWait("clientA", inMemAsEventingProvider);
+ api.setProviderAndWait("clientB", inMemAsEventingProvider);
+ api.setProviderAndWait("clientC", noOpAsNonEventingProvider);
+ api.setProviderAndWait("clientD", noOpAsNonEventingProvider);
+
+ assertEquals(inMemAsEventingProvider, api.getProvider());
+ assertEquals(inMemAsEventingProvider, api.getProvider("clientA"));
+ assertEquals(inMemAsEventingProvider, api.getProvider("clientB"));
+ assertEquals(noOpAsNonEventingProvider, api.getProvider("clientC"));
+ assertEquals(noOpAsNonEventingProvider, api.getProvider("clientD"));
}
@Test
@@ -101,23 +96,20 @@ void getStateReturnsTheStateOfTheAppropriateProvider() throws Exception {
String domain = "namedProviderOverwrittenTest";
FeatureProvider provider1 = new NoOpProvider();
FeatureProvider provider2 = new TestEventsProvider();
- FeatureProviderTestUtils.setFeatureProvider(domain, provider1);
- FeatureProviderTestUtils.setFeatureProvider(domain, provider2);
+ api.setProviderAndWait(domain, provider1);
+ api.setProviderAndWait(domain, provider2);
provider2.initialize(null);
- assertThat(OpenFeatureAPI.getInstance().getClient(domain).getProviderState())
- .isEqualTo(ProviderState.READY);
+ assertThat(api.getClient(domain).getProviderState()).isEqualTo(ProviderState.READY);
}
@Test
void featureProviderTrackIsCalled() throws Exception {
FeatureProvider featureProvider = mock(FeatureProvider.class);
- FeatureProviderTestUtils.setFeatureProvider(featureProvider);
+ api.setProviderAndWait(featureProvider);
- OpenFeatureAPI.getInstance()
- .getClient()
- .track("track-event", new ImmutableContext(), new MutableTrackingEventDetails(22.2f));
+ api.getClient().track("track-event", new ImmutableContext(), new MutableTrackingEventDetails(22.2f));
verify(featureProvider).initialize(any());
verify(featureProvider).getMetadata();
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITestUtil.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITestUtil.java
new file mode 100644
index 00000000..f33c5b4d
--- /dev/null
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITestUtil.java
@@ -0,0 +1,10 @@
+package dev.openfeature.sdk;
+
+public class OpenFeatureAPITestUtil {
+
+ private OpenFeatureAPITestUtil() {}
+
+ public static OpenFeatureAPI createAPI() {
+ return new OpenFeatureAPI();
+ }
+}
diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
index 4f4d3200..97a1417a 100644
--- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
+++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java
@@ -38,7 +38,7 @@ void reset_logs() {
@Test
@DisplayName("should not throw exception if hook has different type argument than hookContext")
void shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext() {
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+ OpenFeatureAPI api = new OpenFeatureAPI();
api.setProviderAndWait(
"shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext", new DoSomethingProvider());
Client client = api.getClient("shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext");
@@ -82,7 +82,7 @@ void setEvaluationContextShouldAllowChaining() {
@DisplayName("Should not call evaluation methods when the provider has state FATAL")
void shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState() {
FeatureProvider provider = new TestEventsProvider(100, true, "fake fatal", true);
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+ OpenFeatureAPI api = new OpenFeatureAPI();
Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState");
assertThrows(
@@ -97,7 +97,7 @@ void shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState() {
@DisplayName("Should not call evaluation methods when the provider has state NOT_READY")
void shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState() {
FeatureProvider provider = new TestEventsProvider(5000);
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
+ OpenFeatureAPI api = new OpenFeatureAPI();
api.setProvider("shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState", provider);
Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState");
FlagEvaluationDetails details = client.getBooleanDetails("key", true);
diff --git a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java
index 98652635..7041df5c 100644
--- a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java
+++ b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java
@@ -35,7 +35,7 @@ class ProviderRepositoryTest {
@BeforeEach
void setupTest() {
- providerRepository = new ProviderRepository();
+ providerRepository = new ProviderRepository(new OpenFeatureAPI());
}
@Nested
diff --git a/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java b/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java
index e7caf927..1bb7d4b6 100644
--- a/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java
@@ -1,6 +1,5 @@
package dev.openfeature.sdk;
-import static dev.openfeature.sdk.testutils.FeatureProviderTestUtils.setFeatureProvider;
import static org.mockito.Mockito.*;
import dev.openfeature.sdk.fixtures.ProviderFixture;
@@ -15,9 +14,19 @@
class ShutdownBehaviorSpecTest {
private String DOMAIN = "myDomain";
+ private OpenFeatureAPI api;
+
+ void setFeatureProvider(FeatureProvider featureProvider) {
+ api.setProviderAndWait(featureProvider);
+ }
+
+ void setFeatureProvider(String domain, FeatureProvider featureProvider) {
+ api.setProviderAndWait(domain, featureProvider);
+ }
@BeforeEach
void resetFeatureProvider() {
+ api = new OpenFeatureAPI();
setFeatureProvider(new NoOpProvider());
}
@@ -110,7 +119,6 @@ void mustShutdownAllProvidersOnShuttingDownApi() {
FeatureProvider namedProvider = ProviderFixture.createMockedProvider();
setFeatureProvider(defaultProvider);
setFeatureProvider(DOMAIN, namedProvider);
- OpenFeatureAPI api = OpenFeatureAPI.getInstance();
synchronized (OpenFeatureAPI.class) {
api.shutdown();
@@ -125,15 +133,14 @@ void mustShutdownAllProvidersOnShuttingDownApi() {
@Test
@DisplayName("once shutdown is complete, api must be ready to use again")
void apiIsReadyToUseAfterShutdown() {
- final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
NoOpProvider p1 = new NoOpProvider();
- openFeatureAPI.setProvider(p1);
+ api.setProvider(p1);
- openFeatureAPI.shutdown();
+ api.shutdown();
NoOpProvider p2 = new NoOpProvider();
- openFeatureAPI.setProvider(p2);
+ api.setProvider(p2);
}
}
}
diff --git a/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java b/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java
index a8f6e30f..ba354374 100644
--- a/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java
@@ -15,7 +15,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import dev.openfeature.sdk.fixtures.ProviderFixture;
-import dev.openfeature.sdk.testutils.FeatureProviderTestUtils;
import java.util.HashMap;
import java.util.Map;
import lombok.SneakyThrows;
@@ -29,7 +28,7 @@ class TrackingSpecTest {
@BeforeEach
void getApiInstance() {
- api = OpenFeatureAPI.getInstance();
+ api = new OpenFeatureAPI();
client = api.getClient();
}
@@ -116,7 +115,7 @@ void contextsGetMerged() {
client.setEvaluationContext(clCtx);
FeatureProvider provider = ProviderFixture.createMockedProvider();
- FeatureProviderTestUtils.setFeatureProvider(provider);
+ api.setProviderAndWait(provider);
client.track("event", new MutableContext().add("my-key", "final"), new MutableTrackingEventDetails(0.0f));
@@ -170,8 +169,7 @@ void eventDetails() {
.add("my-struct", new Value(new MutableTrackingEventDetails()));
assertEquals(expectedMap, details.asMap());
- assertThatCode(() -> OpenFeatureAPI.getInstance()
- .getClient()
+ assertThatCode(() -> api.getClient()
.track("tracking-event-name", new ImmutableContext(), new MutableTrackingEventDetails()))
.doesNotThrowAnyException();
@@ -188,8 +186,7 @@ void eventDetails() {
ImmutableTrackingEventDetails immutableDetails = new ImmutableTrackingEventDetails(2, expectedMap);
assertEquals(expectedImmutable, immutableDetails.asMap());
- assertThatCode(() -> OpenFeatureAPI.getInstance()
- .getClient()
+ assertThatCode(() -> api.getClient()
.track("tracking-event-name", new ImmutableContext(), new ImmutableTrackingEventDetails()))
.doesNotThrowAnyException();
}
diff --git a/src/test/java/dev/openfeature/sdk/ValueTest.java b/src/test/java/dev/openfeature/sdk/ValueTest.java
index c2553850..697edb7b 100644
--- a/src/test/java/dev/openfeature/sdk/ValueTest.java
+++ b/src/test/java/dev/openfeature/sdk/ValueTest.java
@@ -2,6 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -11,15 +12,15 @@
import java.util.List;
import org.junit.jupiter.api.Test;
-public class ValueTest {
+class ValueTest {
@Test
- public void noArgShouldContainNull() {
+ void noArgShouldContainNull() {
Value value = new Value();
assertTrue(value.isNull());
}
@Test
- public void objectArgShouldContainObject() {
+ void objectArgShouldContainObject() {
try {
// int is a special case, see intObjectArgShouldConvertToInt()
List