Compare commits
180 Commits
Author | SHA1 | Date |
---|---|---|
|
ac3344c7f6 | |
|
ecc8f7e3ad | |
|
cbf7a58622 | |
|
1382b367d9 | |
|
5b3e3656f6 | |
|
1d3fab6184 | |
|
006ae75e2b | |
|
aa0569379b | |
|
bf68cbdedf | |
|
62738f7f16 | |
|
1e8f5c880c | |
|
488196656a | |
|
26716a51cf | |
|
908755c2c2 | |
|
d2b1dc3a41 | |
|
85d89ee79a | |
|
6194186b3e | |
|
5425a34a12 | |
|
957c0d1ba3 | |
|
ebea0fdf1c | |
|
08f549afd1 | |
|
a5d1cbced4 | |
|
0fd9d3dcfb | |
|
0515ad54c4 | |
|
69519b1ef7 | |
|
2e3b479cb1 | |
|
49214b7282 | |
|
fc430c3e1d | |
|
86a5916f0d | |
|
34b22e8d93 | |
|
300a705e0a | |
|
b64efe82d9 | |
|
6f67b06f71 | |
|
e67f598357 | |
|
312b6df5d2 | |
|
8fad544b17 | |
|
c3eaecdb8b | |
|
7c2af57a36 | |
|
8bf777a7e9 | |
|
936ff60fac | |
|
99a3006de8 | |
|
8e51e6fe10 | |
|
844d5e244b | |
|
0b57bcafc1 | |
|
3dd7d5d426 | |
|
6cca721be5 | |
|
4481537ceb | |
|
b5d873e44d | |
|
545d6aac09 | |
|
2dcd6a1dd0 | |
|
3ed65cfb0c | |
|
6597de7a98 | |
|
376f81f5c3 | |
|
1558a86249 | |
|
f8260a1c3a | |
|
50a6b168a7 | |
|
b6ceff2ecb | |
|
2de7616676 | |
|
f10aaaa357 | |
|
40b319c5de | |
|
5e922cf3ef | |
|
6a95c008e9 | |
|
dcbfd265a3 | |
|
e17b0b2975 | |
|
b45a937017 | |
|
36eed065e7 | |
|
e3379395e6 | |
|
b667aa3251 | |
|
1714efe81a | |
|
d9a72d2aaf | |
|
cfd9512728 | |
|
f6bd30db93 | |
|
e2813b2e5d | |
|
bc10bacb5a | |
|
7182a7fc41 | |
|
d0ae548277 | |
|
e568f3a4f5 | |
|
58454b4eaa | |
|
78657ee79e | |
|
3403510515 | |
|
4dc988b637 | |
|
f2348ea370 | |
|
85b200a08b | |
|
f965cbcb37 | |
|
b09e88798f | |
|
7e74f2aa3a | |
|
62ba6db457 | |
|
96cf9c7f54 | |
|
99faaf88aa | |
|
1cc851b293 | |
|
32137bfa82 | |
|
45ec4b1b77 | |
|
014f8a59da | |
|
1c4d2efafd | |
|
844374a42b | |
|
a7828e73a8 | |
|
6b6849f3a3 | |
|
495da271be | |
|
e19ccaa35d | |
|
498fd38265 | |
|
a3e2a59aeb | |
|
665dd51eb2 | |
|
a6389e89f6 | |
|
97b442ed6e | |
|
0c77c84460 | |
|
4607c62f15 | |
|
a5789038ac | |
|
e066d3f749 | |
|
5b327eeb77 | |
|
e25181982a | |
|
3c69f2f36c | |
|
345cdcfa10 | |
|
ca160cab7c | |
|
e211397d51 | |
|
5b2f1513ab | |
|
f834e11acc | |
|
ef32f11571 | |
|
07301bda3f | |
|
384953d30e | |
|
1f2d071508 | |
|
d6ebc161a9 | |
|
1fcf0e77d9 | |
|
37d76be697 | |
|
d7b591c9f9 | |
|
7f54c334da | |
|
24ef9dd290 | |
|
753667925a | |
|
87c06d9edd | |
|
85fd5e0997 | |
|
d8f6514598 | |
|
cb574d93b6 | |
|
4125ae8380 | |
|
387e5f2e3b | |
|
b6becac2c4 | |
|
922e17e677 | |
|
d61c33e466 | |
|
2239f054b9 | |
|
8359ef13bb | |
|
dbf92df33b | |
|
706565581d | |
|
9750f75d04 | |
|
de3e213ac8 | |
|
6b65e26c74 | |
|
d233480912 | |
|
69b571eda7 | |
|
f8df5fb84a | |
|
d54c68a8e9 | |
|
c550d59722 | |
|
d00e4b5b24 | |
|
c37d249776 | |
|
959e675e4c | |
|
67b34f84a3 | |
|
30b6d004aa | |
|
ecea9df932 | |
|
6c03e5d84a | |
|
31444d6c8f | |
|
dd83114c4d | |
|
2a1adca8c2 | |
|
59017977a4 | |
|
989f4ae542 | |
|
b133c2fa52 | |
|
7df9565691 | |
|
2ec7c6c7ff | |
|
698756856b | |
|
5de33c02a6 | |
|
de64eddfb3 | |
|
d95e270653 | |
|
1504d0f798 | |
|
88a778cc03 | |
|
50b45b2be4 | |
|
dd9227a924 | |
|
cdcdc143ea | |
|
3920c638a4 | |
|
4e535fd10f | |
|
4817864fd7 | |
|
90217b2083 | |
|
88baa65dd0 | |
|
e163ce1c06 | |
|
5436eb0d5d | |
|
859a36cbfa |
|
@ -3,4 +3,3 @@
|
|||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
|
|
|
@ -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@335288255954904a41ddda8947c8f2c844b8bfeb
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -9,35 +9,36 @@ name: on-merge
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@ae2b61dbc685e60e4427b2e8ed4f0135c6ea8597
|
||||
with:
|
||||
java-version: '8'
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
server-id: ossrh
|
||||
server-username: ${{ secrets.OSSRH_USERNAME }}
|
||||
server-password: ${{ secrets.OSSRH_PASSWORD }}
|
||||
server-id: central
|
||||
server-username: ${{ secrets.CENTRAL_USERNAME }}
|
||||
server-password: ${{ secrets.CENTRAL_PASSWORD }}
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@9fa7e61ec7e1f44ac75218e7aaea81da8856fd11
|
||||
uses: actions/cache@640a1c2554105b57832a23eea0b4672fc7a790d5
|
||||
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 +50,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.3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||
flags: unittests # optional
|
||||
|
@ -60,11 +61,11 @@ jobs:
|
|||
# Add -SNAPSHOT before deploy
|
||||
- name: Add SNAPSHOT
|
||||
run: mvn versions:set -DnewVersion='${project.version}-SNAPSHOT'
|
||||
|
||||
|
||||
- name: Deploy
|
||||
run: |
|
||||
mvn --batch-mode \
|
||||
--settings release/m2-settings.xml clean deploy
|
||||
--settings release/m2-settings.xml -DskipTests clean deploy
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
|
||||
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
|
||||
|
|
|
@ -7,36 +7,46 @@ permissions:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
build:
|
||||
- java: 17
|
||||
profile: codequality
|
||||
- java: 11
|
||||
profile: java11
|
||||
name: with Java ${{ matrix.build.java }}
|
||||
runs-on: ${{ matrix.os}}
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@ae2b61dbc685e60e4427b2e8ed4f0135c6ea8597
|
||||
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@7710ed11e398ea99c7f7004c2b2e0f580458db42
|
||||
with:
|
||||
languages: java
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@9fa7e61ec7e1f44ac75218e7aaea81da8856fd11
|
||||
uses: actions/cache@640a1c2554105b57832a23eea0b4672fc7a790d5
|
||||
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.3
|
||||
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@7710ed11e398ea99c7f7004c2b2e0f580458db42
|
||||
|
|
|
@ -12,46 +12,53 @@ permissions: # added using https://github.com/step-security/secure-workflows
|
|||
|
||||
jobs:
|
||||
release-please:
|
||||
permissions:
|
||||
contents: write # for google-github-actions/release-please-action to create release commit
|
||||
pull-requests: write # for google-github-actions/release-please-action to create release PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # for googleapis/release-please-action to create release commit
|
||||
pull-requests: write # for googleapis/release-please-action to create release PR
|
||||
issues: write # for googleapis/release-please-action to create labels
|
||||
|
||||
# Release-please creates a PR that tracks all changes
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee
|
||||
- uses: googleapis/release-please-action@v4
|
||||
id: release
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
default-branch: main
|
||||
token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}}
|
||||
outputs:
|
||||
release_created: ${{ fromJSON(steps.release.outputs.paths_released)[0] != null }} # if we have a single release path, do the release
|
||||
|
||||
# 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
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
publish:
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
needs: release-please
|
||||
if: ${{ fromJSON(needs.release-please.outputs.release_created || false) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@ae2b61dbc685e60e4427b2e8ed4f0135c6ea8597
|
||||
with:
|
||||
java-version: '8'
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
server-id: ossrh
|
||||
server-username: ${{ secrets.OSSRH_USERNAME }}
|
||||
server-password: ${{ secrets.OSSRH_PASSWORD }}
|
||||
server-id: central
|
||||
server-username: ${{ secrets.CENTRAL_USERNAME }}
|
||||
server-password: ${{ secrets.CENTRAL_PASSWORD }}
|
||||
|
||||
- name: Configure GPG Key
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
run: |
|
||||
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
|
||||
env:
|
||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
|
||||
|
||||
- name: Deploy
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
run: |
|
||||
mvn --batch-mode \
|
||||
--settings release/m2-settings.xml clean deploy
|
||||
--settings release/m2-settings.xml -DskipTests clean deploy
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
|
||||
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
|
||||
|
|
|
@ -29,16 +29,16 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@1c15a48f3fb49ce535e9ee4e57e127315f669361
|
||||
uses: github/codeql-action/init@7710ed11e398ea99c7f7004c2b2e0f580458db42
|
||||
with:
|
||||
languages: java
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@1c15a48f3fb49ce535e9ee4e57e127315f669361
|
||||
uses: github/codeql-action/autobuild@7710ed11e398ea99c7f7004c2b2e0f580458db42
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@1c15a48f3fb49ce535e9ee4e57e127315f669361
|
||||
uses: github/codeql-action/analyze@7710ed11e398ea99c7f7004c2b2e0f580458db42
|
||||
|
|
|
@ -16,4 +16,4 @@
|
|||
# under the License.
|
||||
wrapperVersion=3.3.2
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||
|
|
|
@ -1 +1 @@
|
|||
{".":"1.14.1"}
|
||||
{".":"1.16.0"}
|
||||
|
|
206
CHANGELOG.md
206
CHANGELOG.md
|
@ -1,5 +1,211 @@
|
|||
# Changelog
|
||||
|
||||
## [1.16.0](https://github.com/open-feature/java-sdk/compare/v1.15.1...v1.16.0) (2025-07-07)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **deps:** update dependency io.cucumber:cucumber-bom to v7.23.0 ([#1466](https://github.com/open-feature/java-sdk/issues/1466)) ([50a6b16](https://github.com/open-feature/java-sdk/commit/50a6b168a7de40337aa51ef3d79d122030956cb9))
|
||||
* **deps:** update dependency org.junit:junit-bom to v5.13.1 ([#1475](https://github.com/open-feature/java-sdk/issues/1475)) ([545d6aa](https://github.com/open-feature/java-sdk/commit/545d6aac09dbc74c00a0a4e5c26f4ef80be22379))
|
||||
* **deps:** update dependency org.junit:junit-bom to v5.13.2 ([#1492](https://github.com/open-feature/java-sdk/issues/1492)) ([34b22e8](https://github.com/open-feature/java-sdk/commit/34b22e8d93a986fdb81500ab539b4d2fe038b618))
|
||||
* **deps:** update dependency org.junit:junit-bom to v5.13.3 ([#1505](https://github.com/open-feature/java-sdk/issues/1505)) ([957c0d1](https://github.com/open-feature/java-sdk/commit/957c0d1ba38ecc758c1ec164e40070ac93a01d68))
|
||||
* **deps:** update junit5 monorepo ([#1467](https://github.com/open-feature/java-sdk/issues/1467)) ([f8260a1](https://github.com/open-feature/java-sdk/commit/f8260a1c3a345c877eba95bfe41184ad11f6555e))
|
||||
* Reduce locking and concurrency issues ([#1478](https://github.com/open-feature/java-sdk/issues/1478)) ([ebea0fd](https://github.com/open-feature/java-sdk/commit/ebea0fdf1cf3e6f4d2e8aebf2dcb7c7e1f31acc2))
|
||||
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
* add means of awaiting event emission, fix flaky build ([#1463](https://github.com/open-feature/java-sdk/issues/1463)) ([3dd7d5d](https://github.com/open-feature/java-sdk/commit/3dd7d5d4262f1f4461e13c13a7d64d2fa8bfd764)), closes [#1449](https://github.com/open-feature/java-sdk/issues/1449)
|
||||
|
||||
|
||||
### 🧹 Chore
|
||||
|
||||
* **deps:** update actions/cache digest to 640a1c2 ([#1485](https://github.com/open-feature/java-sdk/issues/1485)) ([7c2af57](https://github.com/open-feature/java-sdk/commit/7c2af57a362ee11f757a431ee17eff3ee448bf6c))
|
||||
* **deps:** update actions/checkout digest to 09d2aca ([#1473](https://github.com/open-feature/java-sdk/issues/1473)) ([b5d873e](https://github.com/open-feature/java-sdk/commit/b5d873e44d3c41b42f11569b0fafccc0a002ebdd))
|
||||
* **deps:** update actions/setup-java digest to 67aec00 ([#1504](https://github.com/open-feature/java-sdk/issues/1504)) ([08f549a](https://github.com/open-feature/java-sdk/commit/08f549afd1fd26581b2a8e063832ec986c5e3267))
|
||||
* **deps:** update actions/setup-java digest to ebb356c ([#1490](https://github.com/open-feature/java-sdk/issues/1490)) ([e67f598](https://github.com/open-feature/java-sdk/commit/e67f5983573afff805a56ef18584d1a7291ccafc))
|
||||
* **deps:** update codecov/codecov-action action to v5.4.3 ([#1454](https://github.com/open-feature/java-sdk/issues/1454)) ([e337939](https://github.com/open-feature/java-sdk/commit/e3379395e6bfb0ce811d8372761a3cb015ad2cde))
|
||||
* **deps:** update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.5 ([#1462](https://github.com/open-feature/java-sdk/issues/1462)) ([40b319c](https://github.com/open-feature/java-sdk/commit/40b319c5de0461bec13f76978ae09edc958310cd))
|
||||
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.1 ([#1493](https://github.com/open-feature/java-sdk/issues/1493)) ([b64efe8](https://github.com/open-feature/java-sdk/commit/b64efe82d993defe070dfeb9aa60e740ccf757cd))
|
||||
* **deps:** update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.9.3.2 ([#1496](https://github.com/open-feature/java-sdk/issues/1496)) ([fc430c3](https://github.com/open-feature/java-sdk/commit/fc430c3e1d57a532d8c0c879c3e7e25c46d4ad84))
|
||||
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.24.0 ([#1458](https://github.com/open-feature/java-sdk/issues/1458)) ([dcbfd26](https://github.com/open-feature/java-sdk/commit/dcbfd265a3875271695af760fce9870e53c69f13))
|
||||
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.25.0 ([#1468](https://github.com/open-feature/java-sdk/issues/1468)) ([1558a86](https://github.com/open-feature/java-sdk/commit/1558a862497c0e133d11d53ff6d7f28437653d43))
|
||||
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.25.1 ([#1489](https://github.com/open-feature/java-sdk/issues/1489)) ([312b6df](https://github.com/open-feature/java-sdk/commit/312b6df5d2c891ac758bf398f8399ecd25b7597e))
|
||||
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.26.0 ([#1494](https://github.com/open-feature/java-sdk/issues/1494)) ([300a705](https://github.com/open-feature/java-sdk/commit/300a705e0af959da7ed0e88e9975379ff6fc4138))
|
||||
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10.26.1 ([#1498](https://github.com/open-feature/java-sdk/issues/1498)) ([2e3b479](https://github.com/open-feature/java-sdk/commit/2e3b479cb1e8b0b65652ee813eaa2e1940d53c8e))
|
||||
* **deps:** update dependency maven to v3.9.10 ([#1474](https://github.com/open-feature/java-sdk/issues/1474)) ([4481537](https://github.com/open-feature/java-sdk/commit/4481537cebc213dcfe19bb8cd9b70a4c91a682b2))
|
||||
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.6 ([#1482](https://github.com/open-feature/java-sdk/issues/1482)) ([8e51e6f](https://github.com/open-feature/java-sdk/commit/8e51e6fe101882184a5d09be31fa65563d82c673))
|
||||
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.6 ([#1483](https://github.com/open-feature/java-sdk/issues/1483)) ([936ff60](https://github.com/open-feature/java-sdk/commit/936ff60fac471a83a7c14412d2e825b2a7f9704c))
|
||||
* **deps:** update dependency org.apache.maven.plugins:maven-gpg-plugin to v3.2.8 ([#1501](https://github.com/open-feature/java-sdk/issues/1501)) ([0515ad5](https://github.com/open-feature/java-sdk/commit/0515ad54c4f71863373eb1b7f429393923b27d90))
|
||||
* **deps:** update dependency org.codehaus.mojo:exec-maven-plugin to v3.5.1 ([#1461](https://github.com/open-feature/java-sdk/issues/1461)) ([b6ceff2](https://github.com/open-feature/java-sdk/commit/b6ceff2ecb0e34be2ccdb83f7f37c1177de6f27e))
|
||||
* **deps:** update dependency org.mockito:mockito-core to v5.18.0 ([#1457](https://github.com/open-feature/java-sdk/issues/1457)) ([e17b0b2](https://github.com/open-feature/java-sdk/commit/e17b0b29758ae7cdbdac9ddb2178382c55eb1277))
|
||||
* **deps:** update github/codeql-action digest to 075e08a ([#1470](https://github.com/open-feature/java-sdk/issues/1470)) ([6597de7](https://github.com/open-feature/java-sdk/commit/6597de7a98e0fae10a541a8a9b60837623c133a8))
|
||||
* **deps:** update github/codeql-action digest to 33f8489 ([#1502](https://github.com/open-feature/java-sdk/issues/1502)) ([0fd9d3d](https://github.com/open-feature/java-sdk/commit/0fd9d3dcfb1fd65197a42885b12d40a1cc152d3b))
|
||||
* **deps:** update github/codeql-action digest to 396fd27 ([#1456](https://github.com/open-feature/java-sdk/issues/1456)) ([b45a937](https://github.com/open-feature/java-sdk/commit/b45a9370173e3d3b97c78449dfc99225fb572228))
|
||||
* **deps:** update github/codeql-action digest to 3de706a ([#1481](https://github.com/open-feature/java-sdk/issues/1481)) ([99a3006](https://github.com/open-feature/java-sdk/commit/99a3006de878ab0ba1f0e61a4cb5432914425795))
|
||||
* **deps:** update github/codeql-action digest to 466d6ce ([#1477](https://github.com/open-feature/java-sdk/issues/1477)) ([0b57bca](https://github.com/open-feature/java-sdk/commit/0b57bcafc14b946000feb4a3421d73b9616e83cb))
|
||||
* **deps:** update github/codeql-action digest to 4a00331 ([#1469](https://github.com/open-feature/java-sdk/issues/1469)) ([376f81f](https://github.com/open-feature/java-sdk/commit/376f81f5c3b66d7e3e298aac30ac7544b84e7362))
|
||||
* **deps:** update github/codeql-action digest to 4c57370 ([#1497](https://github.com/open-feature/java-sdk/issues/1497)) ([49214b7](https://github.com/open-feature/java-sdk/commit/49214b7282ddde1ee16cf80f92c11cc90ef7612a))
|
||||
* **deps:** update github/codeql-action digest to 510dfa3 ([#1450](https://github.com/open-feature/java-sdk/issues/1450)) ([d9a72d2](https://github.com/open-feature/java-sdk/commit/d9a72d2aafd787a1814132f000897ad1c94181e4))
|
||||
* **deps:** update github/codeql-action digest to 57eebf6 ([#1455](https://github.com/open-feature/java-sdk/issues/1455)) ([36eed06](https://github.com/open-feature/java-sdk/commit/36eed065e763bbfa0f8f97d704202bbd219332ca))
|
||||
* **deps:** update github/codeql-action digest to 66d7255 ([#1487](https://github.com/open-feature/java-sdk/issues/1487)) ([c3eaecd](https://github.com/open-feature/java-sdk/commit/c3eaecdb8b34d3b33946bd205ee92d49584602bd))
|
||||
* **deps:** update github/codeql-action digest to 7b0fb5a ([#1459](https://github.com/open-feature/java-sdk/issues/1459)) ([6a95c00](https://github.com/open-feature/java-sdk/commit/6a95c008e975dd3c7328c32f1d7cf626bbaecfa6))
|
||||
* **deps:** update github/codeql-action digest to 7cb9b16 ([#1476](https://github.com/open-feature/java-sdk/issues/1476)) ([6cca721](https://github.com/open-feature/java-sdk/commit/6cca721be5bc6f5926fe64668a7c03728cab3cb0))
|
||||
* **deps:** update github/codeql-action digest to 7fd6215 ([#1464](https://github.com/open-feature/java-sdk/issues/1464)) ([f10aaaa](https://github.com/open-feature/java-sdk/commit/f10aaaa357581b573895f4d6e2329abb705582aa))
|
||||
* **deps:** update github/codeql-action digest to 8ef1782 ([#1495](https://github.com/open-feature/java-sdk/issues/1495)) ([86a5916](https://github.com/open-feature/java-sdk/commit/86a5916f0dc6116b5b9e5dc897ff4b8705ac01e3))
|
||||
* **deps:** update github/codeql-action digest to 9b02dc2 ([#1491](https://github.com/open-feature/java-sdk/issues/1491)) ([6f67b06](https://github.com/open-feature/java-sdk/commit/6f67b06f712c461f331681a76f5cb2c3ddb0d36b))
|
||||
* **deps:** update github/codeql-action digest to ac30a39 ([#1488](https://github.com/open-feature/java-sdk/issues/1488)) ([8fad544](https://github.com/open-feature/java-sdk/commit/8fad544b17ee08b4280d7975073d00a874c374db))
|
||||
* **deps:** update github/codeql-action digest to b1e4dc3 ([#1471](https://github.com/open-feature/java-sdk/issues/1471)) ([2dcd6a1](https://github.com/open-feature/java-sdk/commit/2dcd6a1dd0c80ee676b9860afd6a6002d0ea4aea))
|
||||
* **deps:** update github/codeql-action digest to b694213 ([#1503](https://github.com/open-feature/java-sdk/issues/1503)) ([a5d1cbc](https://github.com/open-feature/java-sdk/commit/a5d1cbced4658fadb63f362b4512bdbd68ae7d6a))
|
||||
* **deps:** update github/codeql-action digest to b86edfc ([#1453](https://github.com/open-feature/java-sdk/issues/1453)) ([b667aa3](https://github.com/open-feature/java-sdk/commit/b667aa325136b78c01867d40342f81eeb7e16f46))
|
||||
* **deps:** update github/codeql-action digest to bc02a25 ([#1460](https://github.com/open-feature/java-sdk/issues/1460)) ([5e922cf](https://github.com/open-feature/java-sdk/commit/5e922cf3efc156135563707de92e508b0a4d19f3))
|
||||
* **deps:** update github/codeql-action digest to be30325 ([#1479](https://github.com/open-feature/java-sdk/issues/1479)) ([844d5e2](https://github.com/open-feature/java-sdk/commit/844d5e244b02703b624cf75e5bf8448c07e62d3d))
|
||||
* **deps:** update github/codeql-action digest to dcc1a66 ([#1499](https://github.com/open-feature/java-sdk/issues/1499)) ([69519b1](https://github.com/open-feature/java-sdk/commit/69519b1ef7274ceae39d6746c5a5a98dc69f562f))
|
||||
* **deps:** update github/codeql-action digest to ef36b69 ([#1484](https://github.com/open-feature/java-sdk/issues/1484)) ([8bf777a](https://github.com/open-feature/java-sdk/commit/8bf777a7e99be4dfac8917b8e61cb6c23385b8ce))
|
||||
* **deps:** update io.cucumber.version to v7.23.0 ([#1465](https://github.com/open-feature/java-sdk/issues/1465)) ([2de7616](https://github.com/open-feature/java-sdk/commit/2de76166764bacd34883b13220dd0bad824c8b1a))
|
||||
* improvements to release workflow ([#1451](https://github.com/open-feature/java-sdk/issues/1451)) ([1714efe](https://github.com/open-feature/java-sdk/commit/1714efe81aa6ae025f4f8b12c9c042561498d25e))
|
||||
* migrate to new publish ([5425a34](https://github.com/open-feature/java-sdk/commit/5425a34a12baa04f9583b83fd1bfdd7e2a6ab5e8))
|
||||
* remove unneeded version information ([#1428](https://github.com/open-feature/java-sdk/issues/1428)) ([3ed65cf](https://github.com/open-feature/java-sdk/commit/3ed65cfb0cb5ee5b70793cd68a27909c81cd4fab))
|
||||
* skip tests on publish ([6194186](https://github.com/open-feature/java-sdk/commit/6194186b3e791f3cb28da24f5acb3ff96788d65e))
|
||||
* update publish env vars ([85d89ee](https://github.com/open-feature/java-sdk/commit/85d89ee79a52d960322731fb786c0f60245f0d75))
|
||||
|
||||
## [1.15.1](https://github.com/open-feature/java-sdk/compare/v1.14.2...v1.15.1) (2025-05-14)
|
||||
|
||||
|
||||
### NOTABLE CHANGES
|
||||
|
||||
* Raise required Java version to 11 ([#1393](https://github.com/open-feature/java-sdk/issues/1393))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **deps:** update dependency io.cucumber:cucumber-bom to v7.22.0 ([#1411](https://github.com/open-feature/java-sdk/issues/1411)) ([e251819](https://github.com/open-feature/java-sdk/commit/e25181982af8e5d37be4876b71b337ca86e8454b))
|
||||
* **deps:** update dependency io.cucumber:cucumber-bom to v7.22.1 ([#1427](https://github.com/open-feature/java-sdk/issues/1427)) ([1c4d2ef](https://github.com/open-feature/java-sdk/commit/1c4d2efafdebb562f099ba1ec3a6a29eabc8ff91))
|
||||
* **deps:** update dependency io.cucumber:cucumber-bom to v7.22.2 ([#1442](https://github.com/open-feature/java-sdk/issues/1442)) ([e568f3a](https://github.com/open-feature/java-sdk/commit/e568f3a4f560187586d5473aa7bc12a673340e24))
|
||||
* **deps:** update dependency org.projectlombok:lombok to v1.18.38 ([#1403](https://github.com/open-feature/java-sdk/issues/1403)) ([ef32f11](https://github.com/open-feature/java-sdk/commit/ef32f11571de4d3a981efec4f61113eb8b0d7d9d))
|
||||
* **deps:** update junit5 monorepo ([#1418](https://github.com/open-feature/java-sdk/issues/1418)) ([97b442e](https://github.com/open-feature/java-sdk/commit/97b442ed6e8f2b99ca949ffd63e5cbf57718c796))
|
||||
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
* add logging on provider state transitions ([#1444](https://github.com/open-feature/java-sdk/issues/1444)) ([e2813b2](https://github.com/open-feature/java-sdk/commit/e2813b2e5df8e548caf16e3e425b35962045ca6c))
|
||||
* add telemetry helper utils ([#1346](https://github.com/open-feature/java-sdk/issues/1346)) ([d0ae548](https://github.com/open-feature/java-sdk/commit/d0ae5482771f4d1701bce25381cdf4e92e2d4882))
|
||||
* Raise required Java version to 11 ([#1393](https://github.com/open-feature/java-sdk/issues/1393)) ([4dc988b](https://github.com/open-feature/java-sdk/commit/4dc988b637a9e9c377edf7df7b29bf6407319f16))
|
||||
|
||||
|
||||
### 🧹 Chore
|
||||
|
||||
* add DCO to release please ([45ec4b1](https://github.com/open-feature/java-sdk/commit/45ec4b1b7734c9117f43abf8fe5105c2903c3986))
|
||||
* add DCO to release please ([#1429](https://github.com/open-feature/java-sdk/issues/1429)) ([32137bf](https://github.com/open-feature/java-sdk/commit/32137bfa82e9c0391c999bf0be2a36f201620931))
|
||||
* add publish env ([#1420](https://github.com/open-feature/java-sdk/issues/1420)) ([665dd51](https://github.com/open-feature/java-sdk/commit/665dd51eb2b3b79d3ffccb6cef64d544aa5e7206))
|
||||
* **deps:** update actions/setup-java digest to 148017a ([#1404](https://github.com/open-feature/java-sdk/issues/1404)) ([f834e11](https://github.com/open-feature/java-sdk/commit/f834e11acc7ecf903e972d80e9dab324be97847e))
|
||||
* **deps:** update actions/setup-java digest to c5195ef ([#1415](https://github.com/open-feature/java-sdk/issues/1415)) ([a578903](https://github.com/open-feature/java-sdk/commit/a5789038acc36cb2b0ddf12e534a1317e1c9b8e8))
|
||||
* **deps:** update actions/setup-java digest to f4f1212 ([#1421](https://github.com/open-feature/java-sdk/issues/1421)) ([a3e2a59](https://github.com/open-feature/java-sdk/commit/a3e2a59aebee051ae8c7eb1c5769a04dc9da8de3))
|
||||
* **deps:** update amannn/action-semantic-pull-request digest to 3352882 ([#1434](https://github.com/open-feature/java-sdk/issues/1434)) ([62ba6db](https://github.com/open-feature/java-sdk/commit/62ba6db457358d759fe83f23318b1cf4200756ac))
|
||||
* **deps:** update codecov/codecov-action action to v5.4.2 ([#1419](https://github.com/open-feature/java-sdk/issues/1419)) ([a6389e8](https://github.com/open-feature/java-sdk/commit/a6389e89f60aa7f4871f47d78fedd27a7f9991b4))
|
||||
* **deps:** update dependency com.diffplug.spotless:spotless-maven-plugin to v2.44.4 ([#1414](https://github.com/open-feature/java-sdk/issues/1414)) ([e066d3f](https://github.com/open-feature/java-sdk/commit/e066d3f749c09bb1ef79e3bcace1d205a39787df))
|
||||
* **deps:** update dependency com.h3xstream.findsecbugs:findsecbugs-plugin to v1.14.0 ([#1422](https://github.com/open-feature/java-sdk/issues/1422)) ([495da27](https://github.com/open-feature/java-sdk/commit/495da271bee976a942973cd23012f60db895bf24))
|
||||
* **deps:** update dependency com.puppycrawl.tools:checkstyle to v10 ([#103](https://github.com/open-feature/java-sdk/issues/103)) ([3403510](https://github.com/open-feature/java-sdk/commit/34035105154b7945c02de2a88fe83eb2414526ef))
|
||||
* **deps:** update dependency com.tngtech.archunit:archunit-junit5 to v1.4.1 ([#1440](https://github.com/open-feature/java-sdk/issues/1440)) ([78657ee](https://github.com/open-feature/java-sdk/commit/78657ee79efdc94018387cdf8263a73d4abf7191))
|
||||
* **deps:** update dependency net.bytebuddy:byte-buddy to v1.17.5 ([#1400](https://github.com/open-feature/java-sdk/issues/1400)) ([1f2d071](https://github.com/open-feature/java-sdk/commit/1f2d0715087ebd4554826d8552b250e4b8b950c8))
|
||||
* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.17.5 ([#1401](https://github.com/open-feature/java-sdk/issues/1401)) ([07301bd](https://github.com/open-feature/java-sdk/commit/07301bda3f5b65550eff1e025fc9c0bec3c25275))
|
||||
* **deps:** update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.5.3 ([#1398](https://github.com/open-feature/java-sdk/issues/1398)) ([1fcf0e7](https://github.com/open-feature/java-sdk/commit/1fcf0e77d956c88c54e10942d96d2afd4d79315c))
|
||||
* **deps:** update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.5.3 ([#1399](https://github.com/open-feature/java-sdk/issues/1399)) ([d6ebc16](https://github.com/open-feature/java-sdk/commit/d6ebc161a93ad703e25592abdb0bf0fd9e281bbc))
|
||||
* **deps:** update dependency org.jacoco:jacoco-maven-plugin to v0.8.13 ([#1407](https://github.com/open-feature/java-sdk/issues/1407)) ([e19ccaa](https://github.com/open-feature/java-sdk/commit/e19ccaa35d9ac4d89d72ea58a70d416d202078db))
|
||||
* **deps:** update dependency org.mockito:mockito-core to v5.17.0 ([#1409](https://github.com/open-feature/java-sdk/issues/1409)) ([345cdcf](https://github.com/open-feature/java-sdk/commit/345cdcfa10da64c61d769746f335f38ac564e9ad))
|
||||
* **deps:** update github/codeql-action digest to 15bce5b ([#1443](https://github.com/open-feature/java-sdk/issues/1443)) ([bc10bac](https://github.com/open-feature/java-sdk/commit/bc10bacb5a68d0d2e498cb41c087505490f19de8))
|
||||
* **deps:** update github/codeql-action digest to 2a8cbad ([#1423](https://github.com/open-feature/java-sdk/issues/1423)) ([6b6849f](https://github.com/open-feature/java-sdk/commit/6b6849f3a3ee8a7b66d859c8e522bc101d1ccd44))
|
||||
* **deps:** update github/codeql-action digest to 362ef4c ([#1408](https://github.com/open-feature/java-sdk/issues/1408)) ([ca160ca](https://github.com/open-feature/java-sdk/commit/ca160cab7ccd71527e06a0851502353ac50b8d0d))
|
||||
* **deps:** update github/codeql-action digest to 40e16ed ([#1437](https://github.com/open-feature/java-sdk/issues/1437)) ([f965cbc](https://github.com/open-feature/java-sdk/commit/f965cbcb37d20724e15b76c15842a88574810b1a))
|
||||
* **deps:** update github/codeql-action digest to 4c3e536 ([#1417](https://github.com/open-feature/java-sdk/issues/1417)) ([0c77c84](https://github.com/open-feature/java-sdk/commit/0c77c8446032eaac7e068d48901e1423c21db326))
|
||||
* **deps:** update github/codeql-action digest to 4ffa236 ([#1425](https://github.com/open-feature/java-sdk/issues/1425)) ([a7828e7](https://github.com/open-feature/java-sdk/commit/a7828e73a8f2e30f71bd2d9d4da180b2fa436424))
|
||||
* **deps:** update github/codeql-action digest to 56dd02f ([#1416](https://github.com/open-feature/java-sdk/issues/1416)) ([4607c62](https://github.com/open-feature/java-sdk/commit/4607c62f15f7ee572207b8ec012ad4b3626e0184))
|
||||
* **deps:** update github/codeql-action digest to 5eb3ed6 ([#1439](https://github.com/open-feature/java-sdk/issues/1439)) ([f2348ea](https://github.com/open-feature/java-sdk/commit/f2348ea370412351389c60eef390f36edbea68b0))
|
||||
* **deps:** update github/codeql-action digest to 83605b3 ([#1435](https://github.com/open-feature/java-sdk/issues/1435)) ([7e74f2a](https://github.com/open-feature/java-sdk/commit/7e74f2aa3ad2dc8f7a3e4ad398e7705b3e3db364))
|
||||
* **deps:** update github/codeql-action digest to 97a2bfd ([#1438](https://github.com/open-feature/java-sdk/issues/1438)) ([85b200a](https://github.com/open-feature/java-sdk/commit/85b200a08b9f8a71de3b5a19eaa057ec04e0801e))
|
||||
* **deps:** update github/codeql-action digest to 9f45e74 ([#1396](https://github.com/open-feature/java-sdk/issues/1396)) ([37d76be](https://github.com/open-feature/java-sdk/commit/37d76be697e83f524250a82b2a67cdb4a953d7bc))
|
||||
* **deps:** update github/codeql-action digest to d26c46a ([#1413](https://github.com/open-feature/java-sdk/issues/1413)) ([5b327ee](https://github.com/open-feature/java-sdk/commit/5b327eeb770d0a4222f3599be79543b7bed9abc2))
|
||||
* **deps:** update github/codeql-action digest to dab8a02 ([#1405](https://github.com/open-feature/java-sdk/issues/1405)) ([5b2f151](https://github.com/open-feature/java-sdk/commit/5b2f1513ab75ef6692978830e59eba87ffa494d5))
|
||||
* **deps:** update github/codeql-action digest to e13fe0d ([#1406](https://github.com/open-feature/java-sdk/issues/1406)) ([e211397](https://github.com/open-feature/java-sdk/commit/e211397d517e1263e1251f9c99093bf05cecd93f))
|
||||
* **deps:** update github/codeql-action digest to ed51cb5 ([#1436](https://github.com/open-feature/java-sdk/issues/1436)) ([b09e887](https://github.com/open-feature/java-sdk/commit/b09e88798fed529161c61b96c20a8f257d355d3c))
|
||||
* **deps:** update github/codeql-action digest to efffb48 ([#1402](https://github.com/open-feature/java-sdk/issues/1402)) ([384953d](https://github.com/open-feature/java-sdk/commit/384953d30ecff83d60a2e5b9790e8228d1a52ac7))
|
||||
* **deps:** update github/codeql-action digest to f843d94 ([#1432](https://github.com/open-feature/java-sdk/issues/1432)) ([99faaf8](https://github.com/open-feature/java-sdk/commit/99faaf88aa07bd45fc473db5bafce3b8eafaf9e0))
|
||||
* **deps:** update io.cucumber.version to v7.22.0 ([#1410](https://github.com/open-feature/java-sdk/issues/1410)) ([3c69f2f](https://github.com/open-feature/java-sdk/commit/3c69f2f36c4e975d690ecc2e790df632a33001ba))
|
||||
* **deps:** update io.cucumber.version to v7.22.1 ([#1426](https://github.com/open-feature/java-sdk/issues/1426)) ([844374a](https://github.com/open-feature/java-sdk/commit/844374a42b94deffab6856e978766354a6f46576))
|
||||
* **deps:** update io.cucumber.version to v7.22.2 ([#1441](https://github.com/open-feature/java-sdk/issues/1441)) ([58454b4](https://github.com/open-feature/java-sdk/commit/58454b4eaabfd3327f7ceaff4bf335a5a839ed41))
|
||||
* **main:** release 1.15.0 ([#1431](https://github.com/open-feature/java-sdk/issues/1431)) ([7182a7f](https://github.com/open-feature/java-sdk/commit/7182a7fc4197e70218e829971dae2cff09f948c9))
|
||||
* update boostrap sha for release please ([f6bd30d](https://github.com/open-feature/java-sdk/commit/f6bd30db93e37e596d211d899315a62d9f810199))
|
||||
* update codeowners to give global maintainers code ownership ([#1412](https://github.com/open-feature/java-sdk/issues/1412)) ([498fd38](https://github.com/open-feature/java-sdk/commit/498fd382659669315b0db61db5f19ce054467bc9))
|
||||
* update release please action ([#1430](https://github.com/open-feature/java-sdk/issues/1430)) ([1cc851b](https://github.com/open-feature/java-sdk/commit/1cc851b293008a8dd273e904e4c77a650ad71146))
|
||||
* use PAT for release please ([014f8a5](https://github.com/open-feature/java-sdk/commit/014f8a59da8f1e976e440ed1ea17e85561f98e2d))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* add try-catch example for setProviderAndWait usage ([#1433](https://github.com/open-feature/java-sdk/issues/1433)) ([96cf9c7](https://github.com/open-feature/java-sdk/commit/96cf9c7f5463e4e0de394117845aebdd9a69425f))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
#
|
||||
# Managed by Peribolos: https://github.com/open-feature/community/blob/main/config/open-feature/sdk-java/workgroup.yaml
|
||||
#
|
||||
* @open-feature/sdk-java-maintainers
|
||||
* @open-feature/sdk-java-maintainers @open-feature/maintainers
|
||||
|
|
24
README.md
24
README.md
|
@ -18,8 +18,8 @@
|
|||
</a>
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
<a href="https://github.com/open-feature/java-sdk/releases/tag/v1.14.1">
|
||||
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.14.1&color=blue&style=for-the-badge" />
|
||||
<a href="https://github.com/open-feature/java-sdk/releases/tag/v1.16.0">
|
||||
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.16.0&color=blue&style=for-the-badge" />
|
||||
</a>
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
@ -46,7 +46,7 @@
|
|||
|
||||
### Requirements
|
||||
|
||||
- Java 8+ (compiler target is 1.8)
|
||||
- Java 11+ (compiler target is 11)
|
||||
|
||||
Note that this library is intended to be used in server-side contexts and has not been evaluated for use on mobile devices.
|
||||
|
||||
|
@ -59,7 +59,7 @@ Note that this library is intended to be used in server-side contexts and has no
|
|||
<dependency>
|
||||
<groupId>dev.openfeature</groupId>
|
||||
<artifactId>sdk</artifactId>
|
||||
<version>1.14.1</version>
|
||||
<version>1.16.0</version>
|
||||
</dependency>
|
||||
```
|
||||
<!-- x-release-please-end-version -->
|
||||
|
@ -84,7 +84,7 @@ If you would like snapshot builds, this is the relevant repository information:
|
|||
<!-- x-release-please-start-version -->
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation 'dev.openfeature:sdk:1.14.1'
|
||||
implementation 'dev.openfeature:sdk:1.16.0'
|
||||
}
|
||||
```
|
||||
<!-- x-release-please-end-version -->
|
||||
|
@ -104,7 +104,12 @@ public void example(){
|
|||
|
||||
// configure a provider
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setProviderAndWait(new InMemoryProvider(myFlags));
|
||||
try {
|
||||
api.setProviderAndWait(new InMemoryProvider(myFlags));
|
||||
} catch (Exception e) {
|
||||
// handle initialization failure
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// create a client
|
||||
Client client = api.getClient();
|
||||
|
@ -149,7 +154,12 @@ To register a provider in a blocking manner to ensure it is ready before further
|
|||
|
||||
```java
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setProviderAndWait(new MyProvider());
|
||||
try {
|
||||
api.setProviderAndWait(new MyProvider());
|
||||
} catch (Exception e) {
|
||||
// handle initialization failure
|
||||
e.printStackTrace();
|
||||
}
|
||||
```
|
||||
|
||||
#### Asynchronous
|
||||
|
|
578
pom.xml
578
pom.xml
|
@ -5,16 +5,20 @@
|
|||
|
||||
<groupId>dev.openfeature</groupId>
|
||||
<artifactId>sdk</artifactId>
|
||||
<version>1.14.1</version> <!--x-release-please-version -->
|
||||
<version>1.16.0</version> <!--x-release-please-version -->
|
||||
|
||||
<properties>
|
||||
<toolchain.jdk.version>[17,)</toolchain.jdk.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
|
||||
<junit.jupiter.version>5.11.4</junit.jupiter.version>
|
||||
<org.mockito.version>5.18.0</org.mockito.version>
|
||||
<!-- exclusion expression for e2e tests -->
|
||||
<testExclusions>**/e2e/*.java</testExclusions>
|
||||
<module-name>${project.groupId}.${project.artifactId}</module-name>
|
||||
<skip.tests>false</skip.tests>
|
||||
<!-- this will throw an error if we use wrong apis -->
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<name>OpenFeature Java SDK</name>
|
||||
|
@ -48,7 +52,7 @@
|
|||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
<version>1.18.38</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -63,14 +67,21 @@
|
|||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.16</version>
|
||||
<version>2.0.17</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test -->
|
||||
<dependency>
|
||||
<groupId>com.tngtech.archunit</groupId>
|
||||
<artifactId>archunit-junit5</artifactId>
|
||||
<version>1.4.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<version>${org.mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -84,35 +95,30 @@
|
|||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-suite</artifactId>
|
||||
<version>1.11.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -128,6 +134,12 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.cucumber</groupId>
|
||||
<artifactId>cucumber-picocontainer</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.simplify4u</groupId>
|
||||
<artifactId>slf4j2-mock</artifactId>
|
||||
|
@ -138,14 +150,14 @@
|
|||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.4.0-jre</version>
|
||||
<version>33.4.8-jre</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>4.2.2</version>
|
||||
<version>4.3.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -167,14 +179,14 @@
|
|||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.17.1</version>
|
||||
<version>1.17.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy-agent</artifactId>
|
||||
<version>1.17.1</version>
|
||||
<version>1.17.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- End mockito workaround-->
|
||||
|
@ -182,7 +194,7 @@
|
|||
<dependency>
|
||||
<groupId>io.cucumber</groupId>
|
||||
<artifactId>cucumber-bom</artifactId>
|
||||
<version>7.21.1</version>
|
||||
<version>7.26.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
@ -190,7 +202,7 @@
|
|||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>5.11.4</version>
|
||||
<version>5.13.3</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
@ -200,6 +212,18 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-toolchains-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>select-jdk-toolchain</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.cyclonedx</groupId>
|
||||
<artifactId>cyclonedx-maven-plugin</artifactId>
|
||||
|
@ -226,49 +250,22 @@
|
|||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>analyze</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<failOnWarning>true</failOnWarning>
|
||||
<ignoredUnusedDeclaredDependencies>
|
||||
<ignoredUnusedDeclaredDependency>com.github.spotbugs:*</ignoredUnusedDeclaredDependency>
|
||||
<ignoredUnusedDeclaredDependency>org.junit*</ignoredUnusedDeclaredDependency>
|
||||
<ignoredUnusedDeclaredDependency>org.simplify4u:slf4j2-mock*</ignoredUnusedDeclaredDependency>
|
||||
</ignoredUnusedDeclaredDependencies>
|
||||
<ignoredDependencies>
|
||||
<ignoredDependency>com.google.guava*</ignoredDependency>
|
||||
<ignoredDependency>io.cucumber*</ignoredDependency>
|
||||
<ignoredDependency>org.junit*</ignoredDependency>
|
||||
<ignoredDependency>com.google.code.findbugs*</ignoredDependency>
|
||||
<ignoredDependency>com.github.spotbugs*</ignoredDependency>
|
||||
<ignoredDependency>org.simplify4u:slf4j-mock-common:*</ignoredDependency>
|
||||
</ignoredDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<version>3.14.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.2</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<forkCount>1</forkCount>
|
||||
<reuseForks>false</reuseForks>
|
||||
<argLine>
|
||||
${surefireArgLine}
|
||||
--add-opens java.base/java.util=ALL-UNNAMED
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED
|
||||
</argLine>
|
||||
<excludes>
|
||||
<!-- tests to exclude -->
|
||||
|
@ -280,7 +277,7 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.5.2</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<argLine>
|
||||
${surefireArgLine}
|
||||
|
@ -288,65 +285,6 @@
|
|||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.12</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
|
||||
<propertyName>surefireArgLine</propertyName>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
|
||||
<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>jacoco-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
|
||||
<excludes>
|
||||
<exclude>dev/openfeature/sdk/exceptions/**</exclude>
|
||||
</excludes>
|
||||
|
||||
<rules>
|
||||
<rule>
|
||||
<element>PACKAGE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.80</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
@ -361,134 +299,217 @@
|
|||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-pmd-plugin</artifactId>
|
||||
<version>3.26.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-pmd</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
<version>4.8.6.6</version>
|
||||
<configuration>
|
||||
<excludeFilterFile>spotbugs-exclusions.xml</excludeFilterFile>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.h3xstream.findsecbugs</groupId>
|
||||
<artifactId>findsecbugs-plugin</artifactId>
|
||||
<version>1.13.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
|
||||
<dependency>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs</artifactId>
|
||||
<version>4.8.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-spotbugs</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
<encoding>UTF-8</encoding>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
<failsOnError>true</failsOnError>
|
||||
<linkXRef>false</linkXRef>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>9.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>validate</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>2.30.0</version>
|
||||
<configuration>
|
||||
<!-- optional: limit format enforcement to just the files changed by this feature branch -->
|
||||
<!-- <ratchetFrom>origin/main</ratchetFrom>-->
|
||||
<formats>
|
||||
<!-- you can define as many formats as you want, each is independent -->
|
||||
<format>
|
||||
<!-- define the files to apply to -->
|
||||
<includes>
|
||||
<include>.gitattributes</include>
|
||||
<include>.gitignore</include>
|
||||
</includes>
|
||||
<!-- define the steps to apply to those files -->
|
||||
<trimTrailingWhitespace/>
|
||||
<endWithNewline/>
|
||||
<indent>
|
||||
<spaces>true</spaces>
|
||||
<spacesPerTab>4</spacesPerTab>
|
||||
</indent>
|
||||
</format>
|
||||
</formats>
|
||||
<!-- define a language-specific format -->
|
||||
<java>
|
||||
<palantirJavaFormat/>
|
||||
|
||||
<indent>
|
||||
<spaces>true</spaces>
|
||||
<spacesPerTab>4</spacesPerTab>
|
||||
</indent>
|
||||
<importOrder/>
|
||||
|
||||
<removeUnusedImports/>
|
||||
<formatAnnotations/>
|
||||
|
||||
</java>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- Deploy related plugins are isolated so that local development can ignore them -->
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>codequality</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>analyze</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<failOnWarning>true</failOnWarning>
|
||||
<ignoredUnusedDeclaredDependencies>
|
||||
<ignoredUnusedDeclaredDependency>com.github.spotbugs:*</ignoredUnusedDeclaredDependency>
|
||||
<ignoredUnusedDeclaredDependency>org.junit*</ignoredUnusedDeclaredDependency>
|
||||
<ignoredUnusedDeclaredDependency>com.tngtech.archunit*</ignoredUnusedDeclaredDependency>
|
||||
<ignoredUnusedDeclaredDependency>org.simplify4u:slf4j2-mock*</ignoredUnusedDeclaredDependency>
|
||||
</ignoredUnusedDeclaredDependencies>
|
||||
<ignoredDependencies>
|
||||
<ignoredDependency>com.google.guava*</ignoredDependency>
|
||||
<ignoredDependency>io.cucumber*</ignoredDependency>
|
||||
<ignoredDependency>org.junit*</ignoredDependency>
|
||||
<ignoredDependency>com.tngtech.archunit*</ignoredDependency>
|
||||
<ignoredDependency>com.google.code.findbugs*</ignoredDependency>
|
||||
<ignoredDependency>com.github.spotbugs*</ignoredDependency>
|
||||
<ignoredDependency>org.simplify4u:slf4j-mock-common:*</ignoredDependency>
|
||||
</ignoredDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.13</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
|
||||
<propertyName>surefireArgLine</propertyName>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
|
||||
<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>jacoco-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
|
||||
<excludes>
|
||||
<exclude>dev/openfeature/sdk/exceptions/**</exclude>
|
||||
</excludes>
|
||||
|
||||
<rules>
|
||||
<rule>
|
||||
<element>PACKAGE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.80</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
<version>4.9.3.2</version>
|
||||
<configuration>
|
||||
<excludeFilterFile>spotbugs-exclusions.xml</excludeFilterFile>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.h3xstream.findsecbugs</groupId>
|
||||
<artifactId>findsecbugs-plugin</artifactId>
|
||||
<version>1.14.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
|
||||
<dependency>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs</artifactId>
|
||||
<version>4.8.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-spotbugs</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
<failsOnError>true</failsOnError>
|
||||
<linkXRef>false</linkXRef>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.26.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>validate</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>2.45.0</version>
|
||||
<configuration>
|
||||
<!-- optional: limit format enforcement to just the files changed by this feature branch -->
|
||||
<!-- <ratchetFrom>origin/main</ratchetFrom>-->
|
||||
<formats>
|
||||
<!-- you can define as many formats as you want, each is independent -->
|
||||
<format>
|
||||
<!-- define the files to apply to -->
|
||||
<includes>
|
||||
<include>.gitattributes</include>
|
||||
<include>.gitignore</include>
|
||||
</includes>
|
||||
<!-- define the steps to apply to those files -->
|
||||
<trimTrailingWhitespace/>
|
||||
<endWithNewline/>
|
||||
<indent>
|
||||
<spaces>true</spaces>
|
||||
<spacesPerTab>4</spacesPerTab>
|
||||
</indent>
|
||||
</format>
|
||||
</formats>
|
||||
<!-- define a language-specific format -->
|
||||
<java>
|
||||
<palantirJavaFormat/>
|
||||
|
||||
<indent>
|
||||
<spaces>true</spaces>
|
||||
<spacesPerTab>4</spacesPerTab>
|
||||
</indent>
|
||||
<importOrder/>
|
||||
|
||||
<removeUnusedImports/>
|
||||
<formatAnnotations/>
|
||||
|
||||
</java>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>deploy</id>
|
||||
<activation>
|
||||
|
@ -499,14 +520,13 @@
|
|||
<plugins>
|
||||
<!-- Begin publish to maven central -->
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.7.0</version>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.8.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<serverId>ossrh</serverId>
|
||||
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
|
||||
<autoReleaseAfterClose>true</autoReleaseAfterClose>
|
||||
<publishingServerId>central</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- End publish to maven central -->
|
||||
|
@ -550,7 +570,7 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.2.7</version>
|
||||
<version>3.2.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
|
@ -591,7 +611,7 @@
|
|||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<version>3.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>update-test-harness-submodule</id>
|
||||
|
@ -610,19 +630,75 @@
|
|||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<!-- profile for running tests under java 11 (used mostly in CI) -->
|
||||
<!-- selected automatically by JDK activation (see https://maven.apache.org/guides/introduction/introduction-to-profiles.html#implicit-profile-activation) -->
|
||||
<profile>
|
||||
<id>java11</id>
|
||||
<!-- with the next block we can define a set of sdks which still support java 8, if any of the sdks is not supporting java 8 anymore -->
|
||||
<!--<modules><module></module></modules>-->
|
||||
<properties>
|
||||
<toolchain.jdk.version>[11,)</toolchain.jdk.version>
|
||||
<skip.tests>true</skip.tests>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-toolchains-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-evaluation-gherkin-tests</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
<goal>select-jdk-toolchain</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<argLine>
|
||||
${surefireArgLine}
|
||||
</argLine>
|
||||
<excludes>
|
||||
<!-- tests to exclude -->
|
||||
<exclude>${testExclusions}</exclude>
|
||||
</excludes>
|
||||
|
||||
<skipTests>${skip.tests}</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<argLine>
|
||||
${surefireArgLine}
|
||||
</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-testCompile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
|
||||
<executable>cp</executable>
|
||||
<arguments>
|
||||
<argument>spec/specification/assets/gherkin/evaluation.feature</argument>
|
||||
<argument>src/test/resources/features/</argument>
|
||||
</arguments>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
@ -634,8 +710,8 @@
|
|||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<id>central</id>
|
||||
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"bootstrap-sha": "c701a6c4ebbe1170a25ca7636a31508b9628831c",
|
||||
"bootstrap-sha": "d7b591c9f910afad303d6d814f65c7f9dab33b89",
|
||||
"signoff": "OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>",
|
||||
"packages": {
|
||||
".": {
|
||||
"package-name": "dev.openfeature.sdk",
|
||||
|
|
|
@ -5,5 +5,10 @@
|
|||
<username>${env.OSSRH_USERNAME}</username>
|
||||
<password>${env.OSSRH_PASSWORD}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>central</id>
|
||||
<username>${env.CENTRAL_USERNAME}</username>
|
||||
<password>${env.CENTRAL_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
|
|
|
@ -3,15 +3,17 @@ package dev.openfeature.sdk;
|
|||
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<String, Value> attributes;
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return attributes == null || attributes.size() == 0;
|
||||
return attributes == null || attributes.isEmpty();
|
||||
}
|
||||
|
||||
AbstractStructure() {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
/**
|
||||
* A class to help with synchronization by allowing the optional awaiting of the associated action.
|
||||
*/
|
||||
public class Awaitable {
|
||||
|
||||
/**
|
||||
* An already-completed Awaitable. Awaiting this will return immediately.
|
||||
*/
|
||||
public static final Awaitable FINISHED = new Awaitable(true);
|
||||
|
||||
private boolean isDone = false;
|
||||
|
||||
public Awaitable() {}
|
||||
|
||||
private Awaitable(boolean isDone) {
|
||||
this.isDone = isDone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets the calling thread wait until some other thread calls {@link Awaitable#wakeup()}. If
|
||||
* {@link Awaitable#wakeup()} has been called before the current thread invokes this method, it will return
|
||||
* immediately.
|
||||
*/
|
||||
@SuppressWarnings("java:S2142")
|
||||
public synchronized void await() {
|
||||
while (!isDone) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException ignored) {
|
||||
// ignored, do not propagate the interrupted state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wakes up all threads that have called {@link Awaitable#await()} and lets them proceed.
|
||||
*/
|
||||
public synchronized void wakeup() {
|
||||
isDone = true;
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Singular;
|
||||
|
||||
/**
|
||||
* Represents an evaluation event.
|
||||
*/
|
||||
@Builder
|
||||
@Getter
|
||||
public class EvaluationEvent {
|
||||
|
||||
private String name;
|
||||
|
||||
@Singular("attribute")
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
return new HashMap<>(attributes);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -76,15 +76,32 @@ public abstract class EventProvider implements FeatureProvider {
|
|||
* @param event The event type
|
||||
* @param details The details of the event
|
||||
*/
|
||||
public void emit(ProviderEvent event, ProviderEventDetails details) {
|
||||
if (eventProviderListener != null) {
|
||||
eventProviderListener.onEmit(event, details);
|
||||
public Awaitable emit(final ProviderEvent event, final ProviderEventDetails details) {
|
||||
final var localEventProviderListener = this.eventProviderListener;
|
||||
final var localOnEmit = this.onEmit;
|
||||
|
||||
if (localEventProviderListener == null && localOnEmit == null) {
|
||||
return Awaitable.FINISHED;
|
||||
}
|
||||
|
||||
final TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> localOnEmit = this.onEmit;
|
||||
if (localOnEmit != null) {
|
||||
emitterExecutor.submit(() -> localOnEmit.accept(this, event, details));
|
||||
}
|
||||
final var awaitable = new Awaitable();
|
||||
|
||||
// These calls need to be executed on a different thread to prevent deadlocks when the provider initialization
|
||||
// relies on a ready event to be emitted
|
||||
emitterExecutor.submit(() -> {
|
||||
try (var ignored = OpenFeatureAPI.lock.readLockAutoCloseable()) {
|
||||
if (localEventProviderListener != null) {
|
||||
localEventProviderListener.onEmit(event, details);
|
||||
}
|
||||
if (localOnEmit != null) {
|
||||
localOnEmit.accept(this, event, details);
|
||||
}
|
||||
} finally {
|
||||
awaitable.wakeup();
|
||||
}
|
||||
});
|
||||
|
||||
return awaitable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,8 +110,8 @@ public abstract class EventProvider implements FeatureProvider {
|
|||
*
|
||||
* @param details The details of the event
|
||||
*/
|
||||
public void emitProviderReady(ProviderEventDetails details) {
|
||||
emit(ProviderEvent.PROVIDER_READY, details);
|
||||
public Awaitable emitProviderReady(ProviderEventDetails details) {
|
||||
return emit(ProviderEvent.PROVIDER_READY, details);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,8 +121,8 @@ public abstract class EventProvider implements FeatureProvider {
|
|||
*
|
||||
* @param details The details of the event
|
||||
*/
|
||||
public void emitProviderConfigurationChanged(ProviderEventDetails details) {
|
||||
emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details);
|
||||
public Awaitable emitProviderConfigurationChanged(ProviderEventDetails details) {
|
||||
return emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,8 +131,8 @@ public abstract class EventProvider implements FeatureProvider {
|
|||
*
|
||||
* @param details The details of the event
|
||||
*/
|
||||
public void emitProviderStale(ProviderEventDetails details) {
|
||||
emit(ProviderEvent.PROVIDER_STALE, details);
|
||||
public Awaitable emitProviderStale(ProviderEventDetails details) {
|
||||
return emit(ProviderEvent.PROVIDER_STALE, details);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,7 +141,7 @@ public abstract class EventProvider implements FeatureProvider {
|
|||
*
|
||||
* @param details The details of the event
|
||||
*/
|
||||
public void emitProviderError(ProviderEventDetails details) {
|
||||
emit(ProviderEvent.PROVIDER_ERROR, details);
|
||||
public Awaitable emitProviderError(ProviderEventDetails details) {
|
||||
return emit(ProviderEvent.PROVIDER_ERROR, details);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -23,13 +23,10 @@ class EventSupport {
|
|||
|
||||
// we use a v4 uuid as a "placeholder" for anonymous clients, since
|
||||
// ConcurrentHashMap doesn't support nulls
|
||||
private static final String defaultClientUuid = UUID.randomUUID().toString();
|
||||
private static final String DEFAULT_CLIENT_UUID = UUID.randomUUID().toString();
|
||||
private final Map<String, HandlerStore> handlerStores = new ConcurrentHashMap<>();
|
||||
private final HandlerStore globalHandlerStore = new HandlerStore();
|
||||
private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> {
|
||||
final Thread thread = new Thread(runnable);
|
||||
return thread;
|
||||
});
|
||||
private final ExecutorService taskExecutor = Executors.newCachedThreadPool();
|
||||
|
||||
/**
|
||||
* Run all the event handlers associated with this domain.
|
||||
|
@ -40,11 +37,10 @@ class EventSupport {
|
|||
* @param eventDetails the event details
|
||||
*/
|
||||
public void runClientHandlers(String domain, ProviderEvent event, EventDetails eventDetails) {
|
||||
domain = Optional.ofNullable(domain).orElse(defaultClientUuid);
|
||||
domain = Optional.ofNullable(domain).orElse(DEFAULT_CLIENT_UUID);
|
||||
|
||||
// run handlers if they exist
|
||||
Optional.ofNullable(handlerStores.get(domain))
|
||||
.filter(store -> Optional.of(store).isPresent())
|
||||
.map(store -> store.handlerMap.get(event))
|
||||
.ifPresent(handlers -> handlers.forEach(handler -> runHandler(handler, eventDetails)));
|
||||
}
|
||||
|
@ -69,7 +65,7 @@ class EventSupport {
|
|||
* @param handler the handler function to run
|
||||
*/
|
||||
public void addClientHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
final String name = Optional.ofNullable(domain).orElse(defaultClientUuid);
|
||||
final String name = Optional.ofNullable(domain).orElse(DEFAULT_CLIENT_UUID);
|
||||
|
||||
// lazily create and cache a HandlerStore if it doesn't exist
|
||||
HandlerStore store = Optional.ofNullable(this.handlerStores.get(name)).orElseGet(() -> {
|
||||
|
@ -89,7 +85,7 @@ class EventSupport {
|
|||
* @param handler the handler ref to be removed
|
||||
*/
|
||||
public void removeClientHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
domain = Optional.ofNullable(domain).orElse(defaultClientUuid);
|
||||
domain = Optional.ofNullable(domain).orElse(DEFAULT_CLIENT_UUID);
|
||||
this.handlerStores.get(domain).removeHandler(event, handler);
|
||||
}
|
||||
|
||||
|
@ -160,14 +156,14 @@ class EventSupport {
|
|||
// instantiated when a handler is added to that client.
|
||||
static class HandlerStore {
|
||||
|
||||
private final Map<ProviderEvent, List<Consumer<EventDetails>>> handlerMap;
|
||||
private final Map<ProviderEvent, Collection<Consumer<EventDetails>>> handlerMap;
|
||||
|
||||
HandlerStore() {
|
||||
handlerMap = new ConcurrentHashMap<>();
|
||||
handlerMap.put(ProviderEvent.PROVIDER_READY, new ArrayList<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, new ArrayList<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_ERROR, new ArrayList<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_STALE, new ArrayList<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_READY, new ConcurrentLinkedQueue<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, new ConcurrentLinkedQueue<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_ERROR, new ConcurrentLinkedQueue<>());
|
||||
handlerMap.put(ProviderEvent.PROVIDER_STALE, new ConcurrentLinkedQueue<>());
|
||||
}
|
||||
|
||||
void addHandler(ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
|
|
|
@ -30,6 +30,7 @@ public interface FeatureProvider {
|
|||
* can overwrite this method,
|
||||
* if they have special initialization needed prior being called for flag
|
||||
* evaluation.
|
||||
*
|
||||
* <p>
|
||||
* It is ok if the method is expensive as it is executed in the background. All
|
||||
* runtime exceptions will be
|
||||
|
@ -45,6 +46,7 @@ public interface FeatureProvider {
|
|||
* flags, or the SDK is shut down.
|
||||
* Providers can overwrite this method, if they have special shutdown actions
|
||||
* needed.
|
||||
*
|
||||
* <p>
|
||||
* It is ok if the method is expensive as it is executed in the background. All
|
||||
* runtime exceptions will be
|
||||
|
|
|
@ -2,14 +2,14 @@ package dev.openfeature.sdk;
|
|||
|
||||
import dev.openfeature.sdk.exceptions.OpenFeatureError;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.Getter;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
class FeatureProviderStateManager implements EventProviderListener {
|
||||
private final FeatureProvider delegate;
|
||||
private final AtomicBoolean isInitialized = new AtomicBoolean();
|
||||
|
||||
@Getter
|
||||
private ProviderState state = ProviderState.NOT_READY;
|
||||
private final AtomicReference<ProviderState> state = new AtomicReference<>(ProviderState.NOT_READY);
|
||||
|
||||
public FeatureProviderStateManager(FeatureProvider delegate) {
|
||||
this.delegate = delegate;
|
||||
|
@ -24,17 +24,17 @@ class FeatureProviderStateManager implements EventProviderListener {
|
|||
}
|
||||
try {
|
||||
delegate.initialize(evaluationContext);
|
||||
state = ProviderState.READY;
|
||||
setState(ProviderState.READY);
|
||||
} catch (OpenFeatureError openFeatureError) {
|
||||
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
|
||||
state = ProviderState.FATAL;
|
||||
setState(ProviderState.FATAL);
|
||||
} else {
|
||||
state = ProviderState.ERROR;
|
||||
setState(ProviderState.ERROR);
|
||||
}
|
||||
isInitialized.set(false);
|
||||
throw openFeatureError;
|
||||
} catch (Exception e) {
|
||||
state = ProviderState.ERROR;
|
||||
setState(ProviderState.ERROR);
|
||||
isInitialized.set(false);
|
||||
throw e;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class FeatureProviderStateManager implements EventProviderListener {
|
|||
|
||||
public void shutdown() {
|
||||
delegate.shutdown();
|
||||
state = ProviderState.NOT_READY;
|
||||
setState(ProviderState.NOT_READY);
|
||||
isInitialized.set(false);
|
||||
}
|
||||
|
||||
|
@ -50,17 +50,34 @@ class FeatureProviderStateManager implements EventProviderListener {
|
|||
public void onEmit(ProviderEvent event, ProviderEventDetails details) {
|
||||
if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
|
||||
if (details != null && details.getErrorCode() == ErrorCode.PROVIDER_FATAL) {
|
||||
state = ProviderState.FATAL;
|
||||
setState(ProviderState.FATAL);
|
||||
} else {
|
||||
state = ProviderState.ERROR;
|
||||
setState(ProviderState.ERROR);
|
||||
}
|
||||
} else if (ProviderEvent.PROVIDER_STALE.equals(event)) {
|
||||
state = ProviderState.STALE;
|
||||
setState(ProviderState.STALE);
|
||||
} else if (ProviderEvent.PROVIDER_READY.equals(event)) {
|
||||
state = ProviderState.READY;
|
||||
setState(ProviderState.READY);
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(ProviderState state) {
|
||||
ProviderState oldState = this.state.getAndSet(state);
|
||||
if (oldState != state) {
|
||||
String providerName;
|
||||
if (delegate.getMetadata() == null || delegate.getMetadata().getName() == null) {
|
||||
providerName = "unknown";
|
||||
} else {
|
||||
providerName = delegate.getMetadata().getName();
|
||||
}
|
||||
log.info("Provider {} transitioned from state {} to state {}", providerName, oldState, state);
|
||||
}
|
||||
}
|
||||
|
||||
public ProviderState getState() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
FeatureProvider getProvider() {
|
||||
return delegate;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport;
|
|||
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 @@ import lombok.experimental.Delegate;
|
|||
* not be modified after instantiation.
|
||||
*/
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
|
||||
public final class ImmutableContext implements EvaluationContext {
|
||||
|
||||
|
|
|
@ -97,6 +97,14 @@ public class ImmutableMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return metadata.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isNotEmpty() {
|
||||
return !metadata.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a builder for {@link ImmutableMetadata}.
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ import lombok.ToString;
|
|||
* 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 final class ImmutableStructure extends AbstractStructure {
|
|||
super(copyAttributes(attributes, null));
|
||||
}
|
||||
|
||||
protected ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
|
||||
ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
|
||||
super(copyAttributes(attributes, targetingKey));
|
||||
}
|
||||
|
||||
|
@ -70,12 +70,14 @@ public final class ImmutableStructure extends AbstractStructure {
|
|||
|
||||
private static Map<String, Value> copyAttributes(Map<String, Value> in, String targetingKey) {
|
||||
Map<String, Value> copy = new HashMap<>();
|
||||
for (Entry<String, Value> entry : in.entrySet()) {
|
||||
copy.put(
|
||||
entry.getKey(),
|
||||
Optional.ofNullable(entry.getValue())
|
||||
.map((Value val) -> val.clone())
|
||||
.orElse(null));
|
||||
if (in != null) {
|
||||
for (Entry<String, Value> 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));
|
||||
|
|
|
@ -15,8 +15,8 @@ import lombok.ToString;
|
|||
* be modified after instantiation.
|
||||
*/
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MutableStructure extends AbstractStructure {
|
||||
|
||||
public MutableStructure() {
|
||||
|
|
|
@ -5,9 +5,12 @@ import dev.openfeature.sdk.internal.AutoCloseableLock;
|
|||
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
@ -21,15 +24,15 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
||||
// package-private multi-read/single-write lock
|
||||
static AutoCloseableReentrantReadWriteLock lock = new AutoCloseableReentrantReadWriteLock();
|
||||
private final List<Hook> apiHooks;
|
||||
private final ConcurrentLinkedQueue<Hook> apiHooks;
|
||||
private ProviderRepository providerRepository;
|
||||
private EventSupport eventSupport;
|
||||
private EvaluationContext evaluationContext;
|
||||
private final AtomicReference<EvaluationContext> evaluationContext = new AtomicReference<>();
|
||||
private TransactionContextPropagator transactionContextPropagator;
|
||||
|
||||
protected OpenFeatureAPI() {
|
||||
apiHooks = new ArrayList<>();
|
||||
providerRepository = new ProviderRepository();
|
||||
apiHooks = new ConcurrentLinkedQueue<>();
|
||||
providerRepository = new ProviderRepository(this);
|
||||
eventSupport = new EventSupport();
|
||||
transactionContextPropagator = new NoOpTransactionContextPropagator();
|
||||
}
|
||||
|
@ -115,9 +118,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* @return api instance
|
||||
*/
|
||||
public OpenFeatureAPI setEvaluationContext(EvaluationContext evaluationContext) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
this.evaluationContext.set(evaluationContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -127,16 +128,14 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* @return evaluation context
|
||||
*/
|
||||
public EvaluationContext getEvaluationContext() {
|
||||
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
|
||||
return this.evaluationContext;
|
||||
}
|
||||
return evaluationContext.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction context propagator.
|
||||
*/
|
||||
public TransactionContextPropagator getTransactionContextPropagator() {
|
||||
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.readLockAutoCloseable()) {
|
||||
return this.transactionContextPropagator;
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +149,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
if (transactionContextPropagator == null) {
|
||||
throw new IllegalArgumentException("Transaction context propagator cannot be null");
|
||||
}
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
this.transactionContextPropagator = transactionContextPropagator;
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +175,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* Set the default provider.
|
||||
*/
|
||||
public void setProvider(FeatureProvider provider) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
providerRepository.setProvider(
|
||||
provider,
|
||||
this::attachEventProvider,
|
||||
|
@ -194,7 +193,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* @param provider The provider to set.
|
||||
*/
|
||||
public void setProvider(String domain, FeatureProvider provider) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
providerRepository.setProvider(
|
||||
domain,
|
||||
provider,
|
||||
|
@ -207,10 +206,16 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the default provider and wait for initialization to finish.
|
||||
* Sets the default provider and waits for its initialization to complete.
|
||||
*
|
||||
* <p>Note: If the provider fails during initialization, an {@link OpenFeatureError} will be thrown.
|
||||
* It is recommended to wrap this call in a try-catch block to handle potential initialization failures gracefully.
|
||||
*
|
||||
* @param provider the {@link FeatureProvider} to set as the default.
|
||||
* @throws OpenFeatureError if the provider fails during initialization.
|
||||
*/
|
||||
public void setProviderAndWait(FeatureProvider provider) throws OpenFeatureError {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
providerRepository.setProvider(
|
||||
provider,
|
||||
this::attachEventProvider,
|
||||
|
@ -224,11 +229,15 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
/**
|
||||
* Add a provider for a domain and wait for initialization to finish.
|
||||
*
|
||||
* <p>Note: If the provider fails during initialization, an {@link OpenFeatureError} will be thrown.
|
||||
* It is recommended to wrap this call in a try-catch block to handle potential initialization failures gracefully.
|
||||
*
|
||||
* @param domain The domain to bind the provider to.
|
||||
* @param provider The provider to set.
|
||||
* @throws OpenFeatureError if the provider fails during initialization.
|
||||
*/
|
||||
public void setProviderAndWait(String domain, FeatureProvider provider) throws OpenFeatureError {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
providerRepository.setProvider(
|
||||
domain,
|
||||
provider,
|
||||
|
@ -242,9 +251,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
|
||||
private void attachEventProvider(FeatureProvider provider) {
|
||||
if (provider instanceof EventProvider) {
|
||||
((EventProvider) provider).attach((p, event, details) -> {
|
||||
runHandlersForProvider(p, event, details);
|
||||
});
|
||||
((EventProvider) provider).attach(this::runHandlersForProvider);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,9 +304,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* @param hooks The hook to add.
|
||||
*/
|
||||
public void addHooks(Hook... hooks) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
this.apiHooks.addAll(Arrays.asList(hooks));
|
||||
}
|
||||
this.apiHooks.addAll(Arrays.asList(hooks));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,18 +313,23 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* @return A list of {@link Hook}s.
|
||||
*/
|
||||
public List<Hook> getHooks() {
|
||||
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
|
||||
return this.apiHooks;
|
||||
}
|
||||
return new ArrayList<>(this.apiHooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the collection of {@link Hook}s.
|
||||
*
|
||||
* @return The collection of {@link Hook}s.
|
||||
*/
|
||||
Collection<Hook> getMutableHooks() {
|
||||
return this.apiHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all hooks.
|
||||
*/
|
||||
public void clearHooks() {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
this.apiHooks.clear();
|
||||
}
|
||||
this.apiHooks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -329,11 +339,11 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* Once shut down is complete, API is reset and ready to use again.
|
||||
*/
|
||||
public void shutdown() {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
providerRepository.shutdown();
|
||||
eventSupport.shutdown();
|
||||
|
||||
providerRepository = new ProviderRepository();
|
||||
providerRepository = new ProviderRepository(this);
|
||||
eventSupport = new EventSupport();
|
||||
}
|
||||
}
|
||||
|
@ -375,7 +385,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
*/
|
||||
@Override
|
||||
public OpenFeatureAPI on(ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
this.eventSupport.addGlobalHandler(event, handler);
|
||||
return this;
|
||||
}
|
||||
|
@ -386,18 +396,20 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
*/
|
||||
@Override
|
||||
public OpenFeatureAPI removeHandler(ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
this.eventSupport.removeGlobalHandler(event, handler);
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
this.eventSupport.removeGlobalHandler(event, handler);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void removeHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
eventSupport.removeClientHandler(domain, event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
void addHandler(String domain, ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.writeLockAutoCloseable()) {
|
||||
// if the provider is in the state associated with event, run immediately
|
||||
if (Optional.ofNullable(this.providerRepository.getProviderState(domain))
|
||||
.orElse(ProviderState.READY)
|
||||
|
@ -421,32 +433,28 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
|
|||
* @param details the event details
|
||||
*/
|
||||
private void runHandlersForProvider(FeatureProvider provider, ProviderEvent event, ProviderEventDetails details) {
|
||||
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
|
||||
try (AutoCloseableLock ignored = lock.readLockAutoCloseable()) {
|
||||
|
||||
List<String> domainsForProvider = providerRepository.getDomainsForProvider(provider);
|
||||
|
||||
final String providerName = Optional.ofNullable(provider.getMetadata())
|
||||
.map(metadata -> metadata.getName())
|
||||
.map(Metadata::getName)
|
||||
.orElse(null);
|
||||
|
||||
// run the global handlers
|
||||
eventSupport.runGlobalHandlers(event, EventDetails.fromProviderEventDetails(details, providerName));
|
||||
|
||||
// run the handlers associated with domains for this provider
|
||||
domainsForProvider.forEach(domain -> {
|
||||
eventSupport.runClientHandlers(
|
||||
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain));
|
||||
});
|
||||
domainsForProvider.forEach(domain -> eventSupport.runClientHandlers(
|
||||
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain)));
|
||||
|
||||
if (providerRepository.isDefaultProvider(provider)) {
|
||||
// run handlers for clients that have no bound providers (since this is the default)
|
||||
Set<String> allDomainNames = eventSupport.getAllDomainNames();
|
||||
Set<String> boundDomains = providerRepository.getAllBoundDomains();
|
||||
allDomainNames.removeAll(boundDomains);
|
||||
allDomainNames.forEach(domain -> {
|
||||
eventSupport.runClientHandlers(
|
||||
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain));
|
||||
});
|
||||
allDomainNames.forEach(domain -> eventSupport.runClientHandlers(
|
||||
domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ import dev.openfeature.sdk.exceptions.FatalError;
|
|||
import dev.openfeature.sdk.exceptions.GeneralError;
|
||||
import dev.openfeature.sdk.exceptions.OpenFeatureError;
|
||||
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
|
||||
import dev.openfeature.sdk.internal.AutoCloseableLock;
|
||||
import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
|
||||
import dev.openfeature.sdk.internal.ObjectUtils;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -15,6 +14,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -46,11 +47,9 @@ public class OpenFeatureClient implements Client {
|
|||
@Getter
|
||||
private final String version;
|
||||
|
||||
private final List<Hook> clientHooks;
|
||||
private final ConcurrentLinkedQueue<Hook> clientHooks;
|
||||
private final HookSupport hookSupport;
|
||||
AutoCloseableReentrantReadWriteLock hooksLock = new AutoCloseableReentrantReadWriteLock();
|
||||
AutoCloseableReentrantReadWriteLock contextLock = new AutoCloseableReentrantReadWriteLock();
|
||||
private EvaluationContext evaluationContext;
|
||||
private final AtomicReference<EvaluationContext> evaluationContext = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Deprecated public constructor. Use OpenFeature.API.getClient() instead.
|
||||
|
@ -68,7 +67,7 @@ public class OpenFeatureClient implements Client {
|
|||
this.openfeatureApi = openFeatureAPI;
|
||||
this.domain = domain;
|
||||
this.version = version;
|
||||
this.clientHooks = new ArrayList<>();
|
||||
this.clientHooks = new ConcurrentLinkedQueue<>();
|
||||
this.hookSupport = new HookSupport();
|
||||
}
|
||||
|
||||
|
@ -125,9 +124,7 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
@Override
|
||||
public OpenFeatureClient addHooks(Hook... hooks) {
|
||||
try (AutoCloseableLock __ = this.hooksLock.writeLockAutoCloseable()) {
|
||||
this.clientHooks.addAll(Arrays.asList(hooks));
|
||||
}
|
||||
this.clientHooks.addAll(Arrays.asList(hooks));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -136,9 +133,7 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
@Override
|
||||
public List<Hook> getHooks() {
|
||||
try (AutoCloseableLock __ = this.hooksLock.readLockAutoCloseable()) {
|
||||
return this.clientHooks;
|
||||
}
|
||||
return new ArrayList<>(this.clientHooks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,9 +141,7 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
@Override
|
||||
public OpenFeatureClient setEvaluationContext(EvaluationContext evaluationContext) {
|
||||
try (AutoCloseableLock __ = contextLock.writeLockAutoCloseable()) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
this.evaluationContext.set(evaluationContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -157,38 +150,33 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
@Override
|
||||
public EvaluationContext getEvaluationContext() {
|
||||
try (AutoCloseableLock __ = contextLock.readLockAutoCloseable()) {
|
||||
return this.evaluationContext;
|
||||
}
|
||||
return this.evaluationContext.get();
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(
|
||||
value = {"REC_CATCH_EXCEPTION"},
|
||||
justification = "We don't want to allow any exception to reach the user. "
|
||||
+ "Instead, we return an evaluation result with the appropriate error code.")
|
||||
private <T> FlagEvaluationDetails<T> evaluateFlag(
|
||||
FlagValueType type, String key, T defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
|
||||
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(
|
||||
var flagOptions = ObjectUtils.defaultIfNull(
|
||||
options, () -> FlagEvaluationOptions.builder().build());
|
||||
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
|
||||
var hints = Collections.unmodifiableMap(flagOptions.getHookHints());
|
||||
|
||||
FlagEvaluationDetails<T> details = null;
|
||||
List<Hook> mergedHooks = null;
|
||||
HookContext<T> afterHookContext = null;
|
||||
FeatureProvider provider;
|
||||
|
||||
try {
|
||||
FeatureProviderStateManager stateManager = openfeatureApi.getFeatureProviderStateManager(this.domain);
|
||||
var stateManager = openfeatureApi.getFeatureProviderStateManager(this.domain);
|
||||
// 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");
|
||||
}
|
||||
var provider = stateManager.getProvider();
|
||||
var state = stateManager.getState();
|
||||
|
||||
mergedHooks = ObjectUtils.merge(
|
||||
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
|
||||
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getMutableHooks());
|
||||
|
||||
EvaluationContext mergedCtx = hookSupport.beforeHooks(
|
||||
var mergedCtx = hookSupport.beforeHooks(
|
||||
type,
|
||||
HookContext.from(
|
||||
key,
|
||||
|
@ -203,12 +191,20 @@ public class OpenFeatureClient implements Client {
|
|||
afterHookContext =
|
||||
HookContext.from(key, type, this.getMetadata(), provider.getMetadata(), mergedCtx, defaultValue);
|
||||
|
||||
ProviderEvaluation<T> providerEval =
|
||||
// "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");
|
||||
}
|
||||
|
||||
var providerEval =
|
||||
(ProviderEvaluation<T>) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx);
|
||||
|
||||
details = FlagEvaluationDetails.from(providerEval, key);
|
||||
if (details.getErrorCode() != null) {
|
||||
OpenFeatureError error =
|
||||
var error =
|
||||
ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage());
|
||||
enrichDetailsWithErrorDefaults(defaultValue, details);
|
||||
hookSupport.errorHooks(type, afterHookContext, error, mergedHooks, hints);
|
||||
|
@ -217,7 +213,7 @@ public class OpenFeatureClient implements Client {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
if (details == null) {
|
||||
details = FlagEvaluationDetails.<T>builder().build();
|
||||
details = FlagEvaluationDetails.<T>builder().flagKey(key).build();
|
||||
}
|
||||
if (e instanceof OpenFeatureError) {
|
||||
details.setErrorCode(((OpenFeatureError) e).getErrorCode());
|
||||
|
@ -262,7 +258,7 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) {
|
||||
final EvaluationContext apiContext = openfeatureApi.getEvaluationContext();
|
||||
final EvaluationContext clientContext = this.getEvaluationContext();
|
||||
final EvaluationContext clientContext = evaluationContext.get();
|
||||
final EvaluationContext transactionContext = openfeatureApi.getTransactionContext();
|
||||
return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext);
|
||||
}
|
||||
|
@ -507,7 +503,7 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
@Override
|
||||
public Client on(ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
OpenFeatureAPI.getInstance().addHandler(domain, event, handler);
|
||||
openfeatureApi.addHandler(domain, event, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -516,7 +512,7 @@ public class OpenFeatureClient implements Client {
|
|||
*/
|
||||
@Override
|
||||
public Client removeHandler(ProviderEvent event, Consumer<EventDetails> handler) {
|
||||
OpenFeatureAPI.getInstance().removeHandler(domain, event, handler);
|
||||
openfeatureApi.removeHandler(domain, event, handler);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 @@ class ProviderRepository {
|
|||
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);
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
/**
|
||||
* The Telemetry class provides constants and methods for creating OpenTelemetry compliant
|
||||
* evaluation events.
|
||||
*/
|
||||
public class Telemetry {
|
||||
|
||||
private Telemetry() {}
|
||||
|
||||
/*
|
||||
The OpenTelemetry compliant event attributes for flag evaluation.
|
||||
Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
|
||||
*/
|
||||
public static final String TELEMETRY_KEY = "feature_flag.key";
|
||||
public static final String TELEMETRY_ERROR_CODE = "error.type";
|
||||
public static final String TELEMETRY_VARIANT = "feature_flag.result.variant";
|
||||
public static final String TELEMETRY_VALUE = "feature_flag.result.value";
|
||||
public static final String TELEMETRY_CONTEXT_ID = "feature_flag.context.id";
|
||||
public static final String TELEMETRY_ERROR_MSG = "feature_flag.evaluation.error.message";
|
||||
public static final String TELEMETRY_REASON = "feature_flag.result.reason";
|
||||
public static final String TELEMETRY_PROVIDER = "feature_flag.provider.name";
|
||||
public static final String TELEMETRY_FLAG_SET_ID = "feature_flag.set.id";
|
||||
public static final String TELEMETRY_VERSION = "feature_flag.version";
|
||||
|
||||
// Well-known flag metadata attributes for telemetry events.
|
||||
// Specification: https://openfeature.dev/specification/appendix-d#flag-metadata
|
||||
public static final String TELEMETRY_FLAG_META_CONTEXT_ID = "contextId";
|
||||
public static final String TELEMETRY_FLAG_META_FLAG_SET_ID = "flagSetId";
|
||||
public static final String TELEMETRY_FLAG_META_VERSION = "version";
|
||||
|
||||
public static final String FLAG_EVALUATION_EVENT_NAME = "feature_flag.evaluation";
|
||||
|
||||
/**
|
||||
* Creates an EvaluationEvent using the provided HookContext and ProviderEvaluation.
|
||||
*
|
||||
* @param hookContext the context containing flag evaluation details
|
||||
* @param evaluationDetails the evaluation result from the provider
|
||||
*
|
||||
* @return an EvaluationEvent populated with telemetry data
|
||||
*/
|
||||
public static EvaluationEvent createEvaluationEvent(
|
||||
HookContext<?> hookContext, FlagEvaluationDetails<?> evaluationDetails) {
|
||||
EvaluationEvent.EvaluationEventBuilder evaluationEventBuilder = EvaluationEvent.builder()
|
||||
.name(FLAG_EVALUATION_EVENT_NAME)
|
||||
.attribute(TELEMETRY_KEY, hookContext.getFlagKey())
|
||||
.attribute(TELEMETRY_PROVIDER, hookContext.getProviderMetadata().getName());
|
||||
|
||||
if (evaluationDetails.getReason() != null) {
|
||||
evaluationEventBuilder.attribute(
|
||||
TELEMETRY_REASON, evaluationDetails.getReason().toLowerCase());
|
||||
} else {
|
||||
evaluationEventBuilder.attribute(
|
||||
TELEMETRY_REASON, Reason.UNKNOWN.name().toLowerCase());
|
||||
}
|
||||
|
||||
if (evaluationDetails.getVariant() != null) {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_VARIANT, evaluationDetails.getVariant());
|
||||
} else {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_VALUE, evaluationDetails.getValue());
|
||||
}
|
||||
|
||||
String contextId = evaluationDetails.getFlagMetadata().getString(TELEMETRY_FLAG_META_CONTEXT_ID);
|
||||
if (contextId != null) {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_CONTEXT_ID, contextId);
|
||||
} else {
|
||||
evaluationEventBuilder.attribute(
|
||||
TELEMETRY_CONTEXT_ID, hookContext.getCtx().getTargetingKey());
|
||||
}
|
||||
|
||||
String setID = evaluationDetails.getFlagMetadata().getString(TELEMETRY_FLAG_META_FLAG_SET_ID);
|
||||
if (setID != null) {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_FLAG_SET_ID, setID);
|
||||
}
|
||||
|
||||
String version = evaluationDetails.getFlagMetadata().getString(TELEMETRY_FLAG_META_VERSION);
|
||||
if (version != null) {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_VERSION, version);
|
||||
}
|
||||
|
||||
if (Reason.ERROR.name().equals(evaluationDetails.getReason())) {
|
||||
if (evaluationDetails.getErrorCode() != null) {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_ERROR_CODE, evaluationDetails.getErrorCode());
|
||||
} else {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_ERROR_CODE, ErrorCode.GENERAL);
|
||||
}
|
||||
|
||||
if (evaluationDetails.getErrorMessage() != null) {
|
||||
evaluationEventBuilder.attribute(TELEMETRY_ERROR_MSG, evaluationDetails.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return evaluationEventBuilder.build();
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ package dev.openfeature.sdk;
|
|||
* for the duration of a single transaction.
|
||||
* Examples of potential transaction specific context include: a user id, user agent, IP.
|
||||
* Transaction context is merged with evaluation context prior to flag evaluation.
|
||||
*
|
||||
* <p>
|
||||
* The precedence of merging context can be seen in
|
||||
* <a href=https://openfeature.dev/specification/sections/evaluation-context#requirement-323>the specification</a>.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dev.openfeature.sdk.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -64,9 +65,9 @@ public class ObjectUtils {
|
|||
* @return resulting object
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> List<T> merge(List<T>... sources) {
|
||||
public static <T> List<T> merge(Collection<T>... sources) {
|
||||
List<T> merged = new ArrayList<>();
|
||||
for (List<T> source : sources) {
|
||||
for (Collection<T> source : sources) {
|
||||
merged.addAll(source);
|
||||
}
|
||||
return merged;
|
||||
|
|
|
@ -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<T> {
|
|||
|
||||
private String defaultVariant;
|
||||
private ContextEvaluator<T> contextEvaluator;
|
||||
private ImmutableMetadata flagMetadata;
|
||||
}
|
||||
|
|
|
@ -152,6 +152,7 @@ public class InMemoryProvider extends EventProvider {
|
|||
.value(value)
|
||||
.variant(flag.getDefaultVariant())
|
||||
.reason(Reason.STATIC.toString())
|
||||
.flagMetadata(flag.getFlagMetadata())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@ package dev.openfeature.sdk;
|
|||
|
||||
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
|
||||
|
||||
public class AlwaysBrokenProvider implements FeatureProvider {
|
||||
public class AlwaysBrokenWithExceptionProvider implements FeatureProvider {
|
||||
|
||||
private final String name = "always broken";
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
@Timeout(value = 5, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
|
||||
class AwaitableTest {
|
||||
@Test
|
||||
void waitingForFinishedIsANoOp() {
|
||||
var startTime = System.currentTimeMillis();
|
||||
Awaitable.FINISHED.await();
|
||||
var endTime = System.currentTimeMillis();
|
||||
assertTrue(endTime - startTime < 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitingForNotFinishedWaitsEvenWhenInterrupted() throws InterruptedException {
|
||||
var awaitable = new Awaitable();
|
||||
var mayProceed = new AtomicBoolean(false);
|
||||
|
||||
var thread = new Thread(() -> {
|
||||
awaitable.await();
|
||||
if (!mayProceed.get()) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
var startTime = System.currentTimeMillis();
|
||||
do {
|
||||
thread.interrupt();
|
||||
} while (startTime + 1000 > System.currentTimeMillis());
|
||||
mayProceed.set(true);
|
||||
awaitable.wakeup();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingWakeUpWakesUpAllWaitingThreads() throws InterruptedException {
|
||||
var awaitable = new Awaitable();
|
||||
var isRunning = new AtomicInteger();
|
||||
|
||||
Runnable runnable = () -> {
|
||||
isRunning.incrementAndGet();
|
||||
var start = System.currentTimeMillis();
|
||||
awaitable.await();
|
||||
var end = System.currentTimeMillis();
|
||||
if (end - start > 10) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
|
||||
var numThreads = 2;
|
||||
var threads = new Thread[numThreads];
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
threads[i] = new Thread(runnable);
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
await().atMost(1, TimeUnit.SECONDS).until(() -> isRunning.get() == numThreads);
|
||||
|
||||
awaitable.wakeup();
|
||||
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,17 +2,16 @@ package dev.openfeature.sdk;
|
|||
|
||||
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");
|
||||
|
|
|
@ -8,7 +8,6 @@ import static org.mockito.Mockito.times;
|
|||
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.List;
|
|||
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 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
void clientHooks() {
|
||||
Hook<Boolean> exampleHook = mockBooleanHook();
|
||||
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setProviderAndWait(new TestEventsProvider());
|
||||
Client client = api.getClient();
|
||||
client.addHooks(exampleHook);
|
||||
|
@ -48,7 +52,6 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
Hook<Boolean> clientHook = mockBooleanHook();
|
||||
Hook<Boolean> evalHook = mockBooleanHook();
|
||||
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setProviderAndWait(new TestEventsProvider());
|
||||
Client client = api.getClient();
|
||||
client.addHooks(clientHook);
|
||||
|
@ -69,7 +72,6 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
@Test
|
||||
void providingContext() {
|
||||
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setProviderAndWait(new TestEventsProvider());
|
||||
Client client = api.getClient();
|
||||
Map<String, Value> attributes = new HashMap<>();
|
||||
|
@ -86,8 +88,7 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
|
||||
@Test
|
||||
void brokenProvider() {
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider());
|
||||
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
|
||||
Client client = api.getClient();
|
||||
FlagEvaluationDetails<Boolean> retval = client.getBooleanDetails(flagKey, false);
|
||||
assertEquals(ErrorCode.FLAG_NOT_FOUND, retval.getErrorCode());
|
||||
|
@ -99,6 +100,9 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
@Test
|
||||
void providerLockedPerTransaction() {
|
||||
|
||||
final String defaultValue = "string-value";
|
||||
final OpenFeatureAPI api = new OpenFeatureAPI();
|
||||
|
||||
class MutatingHook implements Hook {
|
||||
|
||||
@Override
|
||||
|
@ -106,16 +110,14 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
// 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 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
@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,12 +146,11 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
@Test
|
||||
void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() {
|
||||
String domain = "domain";
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
TestEventsProvider provider = new TestEventsProvider();
|
||||
api.setProviderAndWait(domain, provider);
|
||||
Client client = api.getClient(domain);
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
|
||||
provider.emitProviderError(ProviderEventDetails.builder().build());
|
||||
provider.emitProviderError(ProviderEventDetails.builder().build()).await();
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR);
|
||||
}
|
||||
|
||||
|
@ -161,12 +161,11 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
@Test
|
||||
void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() {
|
||||
String domain = "domain";
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
TestEventsProvider provider = new TestEventsProvider();
|
||||
api.setProviderAndWait(domain, provider);
|
||||
Client client = api.getClient(domain);
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
|
||||
provider.emitProviderStale(ProviderEventDetails.builder().build());
|
||||
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
|
||||
}
|
||||
|
||||
|
@ -177,14 +176,13 @@ class DeveloperExperienceTest implements HookFixtures {
|
|||
@Test
|
||||
void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() {
|
||||
String domain = "domain";
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
TestEventsProvider provider = new TestEventsProvider();
|
||||
api.setProviderAndWait(domain, provider);
|
||||
Client client = api.getClient(domain);
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
|
||||
provider.emitProviderStale(ProviderEventDetails.builder().build());
|
||||
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
|
||||
provider.emitProviderReady(ProviderEventDetails.builder().build());
|
||||
provider.emitProviderReady(ProviderEventDetails.builder().build()).await();
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.READY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,11 @@ class EventProviderTest {
|
|||
|
||||
@AfterAll
|
||||
public static void resetDefaultProvider() {
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
|
||||
new OpenFeatureAPI().setProviderAndWait(new NoOpProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 2, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
|
||||
@DisplayName("should run attached onEmit with emitters")
|
||||
void emitsEventsWhenAttached() {
|
||||
TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = mockOnEmit();
|
||||
|
@ -91,7 +92,7 @@ class EventProviderTest {
|
|||
@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 {
|
||||
|
|
|
@ -7,11 +7,11 @@ import static org.mockito.ArgumentMatchers.argThat;
|
|||
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
|
||||
void setUp() {
|
||||
api = new OpenFeatureAPI();
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
@ -49,8 +50,8 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
final Consumer<EventDetails> 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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
final Consumer<EventDetails> 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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
|
||||
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 @@ class EventsTest {
|
|||
ProviderEventDetails.builder().build());
|
||||
|
||||
verify(handlerNotToRun, after(TIMEOUT).never()).accept(any());
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
|
||||
api.setProviderAndWait(new NoOpProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -479,9 +480,9 @@ class EventsTest {
|
|||
final Consumer<EventDetails> 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 @@ class EventsTest {
|
|||
ProviderEventDetails.builder().build());
|
||||
|
||||
verify(handlerToRun, timeout(TIMEOUT)).accept(any());
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider());
|
||||
api.setProviderAndWait(new NoOpProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -509,9 +510,9 @@ class EventsTest {
|
|||
final Consumer<EventDetails> 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 @@ class EventsTest {
|
|||
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<String> flagsChanged = Arrays.asList("flag");
|
||||
|
@ -577,15 +578,15 @@ class EventsTest {
|
|||
number = "5.3.3",
|
||||
text = "Handlers attached after the provider is already in the associated state, MUST run immediately.")
|
||||
void matchingReadyEventsMustRunImmediately() {
|
||||
final String name = "matchingEventsMustRunImmediately";
|
||||
final String name = "matchingReadyEventsMustRunImmediately";
|
||||
final Consumer<EventDetails> handler = mockHandler();
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
@ -596,15 +597,14 @@ class EventsTest {
|
|||
number = "5.3.3",
|
||||
text = "Handlers attached after the provider is already in the associated state, MUST run immediately.")
|
||||
void matchingStaleEventsMustRunImmediately() {
|
||||
final String name = "matchingEventsMustRunImmediately";
|
||||
final String name = "matchingStaleEventsMustRunImmediately";
|
||||
final Consumer<EventDetails> handler = mockHandler();
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
|
||||
// provider which is already stale
|
||||
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
|
||||
Client client = api.getClient(name);
|
||||
api.setProviderAndWait(name, provider);
|
||||
provider.emitProviderStale(ProviderEventDetails.builder().build());
|
||||
provider.emitProviderStale(ProviderEventDetails.builder().build()).await();
|
||||
assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE);
|
||||
|
||||
// should run even though handler was added after stale
|
||||
|
@ -618,17 +618,17 @@ class EventsTest {
|
|||
number = "5.3.3",
|
||||
text = "Handlers attached after the provider is already in the associated state, MUST run immediately.")
|
||||
void matchingErrorEventsMustRunImmediately() {
|
||||
final String name = "matchingEventsMustRunImmediately";
|
||||
final String name = "matchingErrorEventsMustRunImmediately";
|
||||
final Consumer<EventDetails> handler = mockHandler();
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
|
||||
// provider which is already in error
|
||||
TestEventsProvider provider = new TestEventsProvider(INIT_DELAY);
|
||||
Client client = api.getClient(name);
|
||||
api.setProviderAndWait(name, provider);
|
||||
provider.emitProviderError(ProviderEventDetails.builder().build());
|
||||
provider.emitProviderError(ProviderEventDetails.builder().build()).await();
|
||||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
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 @@ class EventsTest {
|
|||
final Consumer<EventDetails> 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
|
||||
|
|
|
@ -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<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
|
||||
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
|
||||
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
|
||||
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
|
||||
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Value> getObjectEvaluation(
|
||||
String key, Value defaultValue, EvaluationContext invocationContext) {
|
||||
throw new GeneralError(TestConstants.BROKEN_MESSAGE);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import static org.mockito.Mockito.*;
|
|||
|
||||
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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
@Test
|
||||
void provider() {
|
||||
FeatureProvider mockProvider = mock(FeatureProvider.class);
|
||||
FeatureProviderTestUtils.setFeatureProvider(mockProvider);
|
||||
api.setProviderAndWait(mockProvider);
|
||||
assertThat(api.getProvider()).isEqualTo(mockProvider);
|
||||
}
|
||||
|
||||
|
@ -90,13 +75,13 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
@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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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<Boolean> 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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
"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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
"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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
"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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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<Boolean> result = c.getBooleanDetails("test", false);
|
||||
|
||||
|
@ -450,7 +435,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
"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<Boolean> result = c.getBooleanDetails("test", false);
|
||||
assertEquals(Reason.ERROR.toString(), result.getReason());
|
||||
|
@ -475,7 +460,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
"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<Boolean> result = c.getBooleanDetails("test", false);
|
||||
assertNotNull(result.getFlagMetadata());
|
||||
|
@ -487,7 +472,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
String contextKey = "some-key";
|
||||
String contextValue = "some-value";
|
||||
DoSomethingProvider provider = spy(new DoSomethingProvider());
|
||||
FeatureProviderTestUtils.setFeatureProvider(provider);
|
||||
api.setProviderAndWait(provider);
|
||||
|
||||
Map<String, Value> attributes = new HashMap<>();
|
||||
attributes.put(contextKey, new Value(contextValue));
|
||||
|
@ -514,7 +499,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
@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<Boolean> hook = spy(new Hook<Boolean>() {
|
||||
|
@ -702,7 +687,7 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
@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 @@ class FlagEvaluationSpecTest implements HookFixtures {
|
|||
@Test
|
||||
void setting_transaction_context() {
|
||||
DoSomethingProvider provider = new DoSomethingProvider();
|
||||
FeatureProviderTestUtils.setFeatureProvider(provider);
|
||||
api.setProviderAndWait(provider);
|
||||
|
||||
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
|
||||
api.setTransactionContextPropagator(transactionContextPropagator);
|
||||
|
|
|
@ -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 @@ class FlagMetadataTest {
|
|||
|
||||
@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 @@ class FlagMetadataTest {
|
|||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
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.List;
|
|||
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 @@ class HookSpecTest implements HookFixtures {
|
|||
.type(FlagValueType.INTEGER)
|
||||
.ctx(new ImmutableContext())
|
||||
.defaultValue(1)
|
||||
.clientMetadata(OpenFeatureAPI.getInstance().getClient().getMetadata())
|
||||
.clientMetadata(api.getClient().getMetadata())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -173,8 +174,8 @@ class HookSpecTest implements HookFixtures {
|
|||
"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<Boolean> evalHook = mockBooleanHook();
|
||||
|
||||
|
@ -216,8 +217,7 @@ class HookSpecTest implements HookFixtures {
|
|||
.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 @@ class HookSpecTest implements HookFixtures {
|
|||
@Test
|
||||
void hook_eval_order() {
|
||||
List<String> evalOrder = new ArrayList<>();
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
|
||||
api.setProviderAndWait("evalOrder", new TestEventsProvider() {
|
||||
public List<Hook> getProviderHooks() {
|
||||
return Collections.singletonList(new BooleanHook() {
|
||||
|
@ -411,8 +411,7 @@ class HookSpecTest implements HookFixtures {
|
|||
doThrow(RuntimeException.class).when(h).before(any(), any());
|
||||
Hook<Boolean> h2 = mockBooleanHook();
|
||||
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
api.setProviderAndWait(new AlwaysBrokenProvider());
|
||||
api.setProviderAndWait(new AlwaysBrokenWithExceptionProvider());
|
||||
Client c = api.getClient();
|
||||
|
||||
c.getBooleanDetails(
|
||||
|
@ -516,8 +515,7 @@ class HookSpecTest implements HookFixtures {
|
|||
.thenReturn(ProviderEvaluation.<Boolean>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 @@ class HookSpecTest implements HookFixtures {
|
|||
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 @@ class HookSpecTest implements HookFixtures {
|
|||
when(provider.getBooleanEvaluation(any(), any(), any()))
|
||||
.thenReturn(ProviderEvaluation.<Boolean>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 @@ class HookSpecTest implements HookFixtures {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package dev.openfeature.sdk;
|
|||
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 @@ class ImmutableContextTest {
|
|||
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<String, Value> attributes = new HashMap<>();
|
||||
attributes.put("key1", new Value("val1"));
|
||||
final ImmutableContext ctx = new ImmutableContext(attributes);
|
||||
|
||||
final Map<String, Value> 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<String, Value> attributes = new HashMap<>();
|
||||
attributes.put("key1", new Value("val1"));
|
||||
final ImmutableContext ctx = new ImmutableContext(attributes);
|
||||
|
||||
final Map<String, Value> attributes2 = new HashMap<>();
|
||||
attributes2.put("key1", new Value("val1"));
|
||||
final ImmutableContext ctx2 = new ImmutableContext(attributes2);
|
||||
|
||||
assertEquals(ctx, ctx2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 @@ class ImmutableStructureTest {
|
|||
attrs.put("null", null);
|
||||
new ImmutableStructure(attrs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unequalImmutableStructuresAreNotEqual() {
|
||||
Map<String, Value> attrs1 = new HashMap<>();
|
||||
attrs1.put("test", new Value(45));
|
||||
ImmutableStructure structure1 = new ImmutableStructure(attrs1);
|
||||
|
||||
Map<String, Value> attrs2 = new HashMap<>();
|
||||
attrs2.put("test", new Value(2));
|
||||
ImmutableStructure structure2 = new ImmutableStructure(attrs2);
|
||||
|
||||
assertNotEquals(structure1, structure2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalImmutableStructuresAreEqual() {
|
||||
Map<String, Value> attrs1 = new HashMap<>();
|
||||
attrs1.put("test", new Value(45));
|
||||
ImmutableStructure structure1 = new ImmutableStructure(attrs1);
|
||||
|
||||
Map<String, Value> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,12 @@ import org.junit.jupiter.api.Test;
|
|||
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 @@ class InitializeBehaviorSpecTest {
|
|||
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 @@ class InitializeBehaviorSpecTest {
|
|||
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 @@ class InitializeBehaviorSpecTest {
|
|||
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 @@ class InitializeBehaviorSpecTest {
|
|||
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());
|
||||
}
|
||||
|
|
|
@ -15,12 +15,11 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.parallel.Isolated;
|
||||
|
||||
@Isolated()
|
||||
class LockingTest {
|
||||
class LockingSingeltonTest {
|
||||
|
||||
private static OpenFeatureAPI api;
|
||||
private OpenFeatureClient client;
|
||||
private AutoCloseableReentrantReadWriteLock apiLock;
|
||||
private AutoCloseableReentrantReadWriteLock clientContextLock;
|
||||
private AutoCloseableReentrantReadWriteLock clientHooksLock;
|
||||
|
||||
@BeforeAll
|
||||
|
@ -36,10 +35,7 @@ class LockingTest {
|
|||
apiLock = setupLock(apiLock, mockInnerReadLock(), mockInnerWriteLock());
|
||||
OpenFeatureAPI.lock = apiLock;
|
||||
|
||||
clientContextLock = setupLock(clientContextLock, mockInnerReadLock(), mockInnerWriteLock());
|
||||
clientHooksLock = setupLock(clientHooksLock, mockInnerReadLock(), mockInnerWriteLock());
|
||||
client.contextLock = clientContextLock;
|
||||
client.hooksLock = clientHooksLock;
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
@ -137,50 +133,6 @@ class LockingTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void addHooksShouldWriteLockAndUnlock() {
|
||||
client.addHooks(new Hook() {});
|
||||
verify(clientHooksLock.writeLock()).lock();
|
||||
verify(clientHooksLock.writeLock()).unlock();
|
||||
|
||||
api.addHooks(new Hook() {});
|
||||
verify(apiLock.writeLock()).lock();
|
||||
verify(apiLock.writeLock()).unlock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHooksShouldReadLockAndUnlock() {
|
||||
client.getHooks();
|
||||
verify(clientHooksLock.readLock()).lock();
|
||||
verify(clientHooksLock.readLock()).unlock();
|
||||
|
||||
api.getHooks();
|
||||
verify(apiLock.readLock()).lock();
|
||||
verify(apiLock.readLock()).unlock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setContextShouldWriteLockAndUnlock() {
|
||||
client.setEvaluationContext(new ImmutableContext());
|
||||
verify(clientContextLock.writeLock()).lock();
|
||||
verify(clientContextLock.writeLock()).unlock();
|
||||
|
||||
api.setEvaluationContext(new ImmutableContext());
|
||||
verify(apiLock.writeLock()).lock();
|
||||
verify(apiLock.writeLock()).unlock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getContextShouldReadLockAndUnlock() {
|
||||
client.getEvaluationContext();
|
||||
verify(clientContextLock.readLock()).lock();
|
||||
verify(clientContextLock.readLock()).unlock();
|
||||
|
||||
api.getEvaluationContext();
|
||||
verify(apiLock.readLock()).lock();
|
||||
verify(apiLock.readLock()).unlock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setTransactionalContextPropagatorShouldWriteLockAndUnlock() {
|
||||
api.setTransactionContextPropagator(new NoOpTransactionContextPropagator());
|
||||
|
@ -195,13 +147,6 @@ class LockingTest {
|
|||
verify(apiLock.readLock()).unlock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearHooksShouldWriteLockAndUnlock() {
|
||||
api.clearHooks();
|
||||
verify(apiLock.writeLock()).lock();
|
||||
verify(apiLock.writeLock()).unlock();
|
||||
}
|
||||
|
||||
private static ReentrantReadWriteLock.ReadLock mockInnerReadLock() {
|
||||
ReentrantReadWriteLock.ReadLock readLockMock = mock(ReentrantReadWriteLock.ReadLock.class);
|
||||
doNothing().when(readLockMock).lock();
|
|
@ -3,6 +3,7 @@ package dev.openfeature.sdk;
|
|||
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 @@ class MutableContextTest {
|
|||
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<String, Value> attributes = new HashMap<>();
|
||||
attributes.put("key1", new Value("val1"));
|
||||
final MutableContext ctx = new MutableContext(attributes);
|
||||
|
||||
final Map<String, Value> 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<String, Value> attributes = new HashMap<>();
|
||||
attributes.put("key1", new Value("val1"));
|
||||
final MutableContext ctx = new MutableContext(attributes);
|
||||
|
||||
final Map<String, Value> attributes2 = new HashMap<>();
|
||||
attributes2.put("key1", new Value("val1"));
|
||||
final MutableContext ctx2 = new MutableContext(attributes2);
|
||||
|
||||
assertEquals(ctx, ctx2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, Value> map = new HashMap<>();
|
||||
map.put("key1", new Value("val1"));
|
||||
AbstractStructure m2 = new AbstractStructure(map) {
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return attributes.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getValue(String key) {
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Value> asMap() {
|
||||
return attributes;
|
||||
}
|
||||
};
|
||||
|
||||
assertNotEquals(m1, m2);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -5,10 +5,10 @@ import static org.assertj.core.api.Assertions.assertThatCode;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
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 +23,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 +44,10 @@ class OpenFeatureAPITest {
|
|||
String domain = "namedProviderOverwrittenTest";
|
||||
FeatureProvider provider1 = new NoOpProvider();
|
||||
FeatureProvider provider2 = new DoSomethingProvider();
|
||||
FeatureProviderTestUtils.setFeatureProvider(domain, provider1);
|
||||
FeatureProviderTestUtils.setFeatureProvider(domain, provider2);
|
||||
api.setProviderAndWait(domain, provider1);
|
||||
api.setProviderAndWait(domain, provider2);
|
||||
|
||||
assertThat(OpenFeatureAPI.getInstance()
|
||||
.getProvider(domain)
|
||||
.getMetadata()
|
||||
.getName())
|
||||
.isEqualTo(DoSomethingProvider.name);
|
||||
assertThat(api.getProvider(domain).getMetadata().getName()).isEqualTo(DoSomethingProvider.name);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -60,17 +56,17 @@ class OpenFeatureAPITest {
|
|||
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);
|
||||
api.setProviderAndWait(inMemAsEventingProvider);
|
||||
api.setProviderAndWait("clientA", inMemAsEventingProvider);
|
||||
api.setProviderAndWait("clientB", inMemAsEventingProvider);
|
||||
api.setProviderAndWait("clientC", noOpAsNonEventingProvider);
|
||||
api.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"));
|
||||
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,26 +97,23 @@ class OpenFeatureAPITest {
|
|||
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();
|
||||
verify(featureProvider, times(2)).getMetadata();
|
||||
verify(featureProvider).track(any(), any(), any());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
public class OpenFeatureAPITestUtil {
|
||||
|
||||
private OpenFeatureAPITestUtil() {}
|
||||
|
||||
public static OpenFeatureAPI createAPI() {
|
||||
return new OpenFeatureAPI();
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ class OpenFeatureClientTest implements HookFixtures {
|
|||
@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 @@ class OpenFeatureClientTest implements HookFixtures {
|
|||
@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 @@ class OpenFeatureClientTest implements HookFixtures {
|
|||
@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<Boolean> details = client.getBooleanDetails("key", true);
|
||||
|
|
|
@ -35,7 +35,7 @@ class ProviderRepositoryTest {
|
|||
|
||||
@BeforeEach
|
||||
void setupTest() {
|
||||
providerRepository = new ProviderRepository();
|
||||
providerRepository = new ProviderRepository(new OpenFeatureAPI());
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
|
@ -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 @@ import org.junit.jupiter.api.Test;
|
|||
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 @@ class ShutdownBehaviorSpecTest {
|
|||
FeatureProvider namedProvider = ProviderFixture.createMockedProvider();
|
||||
setFeatureProvider(defaultProvider);
|
||||
setFeatureProvider(DOMAIN, namedProvider);
|
||||
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
|
||||
|
||||
synchronized (OpenFeatureAPI.class) {
|
||||
api.shutdown();
|
||||
|
@ -125,15 +133,14 @@ class ShutdownBehaviorSpecTest {
|
|||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
package dev.openfeature.sdk;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TelemetryTest {
|
||||
|
||||
@Test
|
||||
void testCreatesEvaluationEventWithMandatoryFields() {
|
||||
// Arrange
|
||||
String flagKey = "test-flag";
|
||||
String providerName = "test-provider";
|
||||
String reason = "static";
|
||||
|
||||
Metadata providerMetadata = mock(Metadata.class);
|
||||
when(providerMetadata.getName()).thenReturn(providerName);
|
||||
|
||||
HookContext<Boolean> hookContext = HookContext.<Boolean>builder()
|
||||
.flagKey(flagKey)
|
||||
.providerMetadata(providerMetadata)
|
||||
.type(FlagValueType.BOOLEAN)
|
||||
.defaultValue(false)
|
||||
.ctx(new ImmutableContext())
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<Boolean> evaluation = FlagEvaluationDetails.<Boolean>builder()
|
||||
.reason(reason)
|
||||
.value(true)
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, evaluation);
|
||||
|
||||
assertEquals(Telemetry.FLAG_EVALUATION_EVENT_NAME, event.getName());
|
||||
assertEquals(flagKey, event.getAttributes().get(Telemetry.TELEMETRY_KEY));
|
||||
assertEquals(providerName, event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
|
||||
assertEquals(reason.toLowerCase(), event.getAttributes().get(Telemetry.TELEMETRY_REASON));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandlesNullReason() {
|
||||
// Arrange
|
||||
String flagKey = "test-flag";
|
||||
String providerName = "test-provider";
|
||||
|
||||
Metadata providerMetadata = mock(Metadata.class);
|
||||
when(providerMetadata.getName()).thenReturn(providerName);
|
||||
|
||||
HookContext<Boolean> hookContext = HookContext.<Boolean>builder()
|
||||
.flagKey(flagKey)
|
||||
.providerMetadata(providerMetadata)
|
||||
.type(FlagValueType.BOOLEAN)
|
||||
.defaultValue(false)
|
||||
.ctx(new ImmutableContext())
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<Boolean> evaluation = FlagEvaluationDetails.<Boolean>builder()
|
||||
.reason(null)
|
||||
.value(true)
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, evaluation);
|
||||
|
||||
assertEquals(Reason.UNKNOWN.name().toLowerCase(), event.getAttributes().get(Telemetry.TELEMETRY_REASON));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetsVariantAttributeWhenVariantExists() {
|
||||
HookContext<String> hookContext = HookContext.<String>builder()
|
||||
.flagKey("testFlag")
|
||||
.type(FlagValueType.STRING)
|
||||
.defaultValue("default")
|
||||
.ctx(mock(EvaluationContext.class))
|
||||
.clientMetadata(mock(ClientMetadata.class))
|
||||
.providerMetadata(mock(Metadata.class))
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
|
||||
.variant("testVariant")
|
||||
.flagMetadata(ImmutableMetadata.builder().build())
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
|
||||
|
||||
assertEquals("testVariant", event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_sets_value_in_body_when_variant_is_null() {
|
||||
HookContext<String> hookContext = HookContext.<String>builder()
|
||||
.flagKey("testFlag")
|
||||
.type(FlagValueType.STRING)
|
||||
.defaultValue("default")
|
||||
.ctx(mock(EvaluationContext.class))
|
||||
.clientMetadata(mock(ClientMetadata.class))
|
||||
.providerMetadata(mock(Metadata.class))
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
|
||||
.value("testValue")
|
||||
.flagMetadata(ImmutableMetadata.builder().build())
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
|
||||
|
||||
assertEquals("testValue", event.getAttributes().get(Telemetry.TELEMETRY_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllFieldsPopulated() {
|
||||
EvaluationContext evaluationContext = mock(EvaluationContext.class);
|
||||
when(evaluationContext.getTargetingKey()).thenReturn("realTargetingKey");
|
||||
|
||||
Metadata providerMetadata = mock(Metadata.class);
|
||||
when(providerMetadata.getName()).thenReturn("realProviderName");
|
||||
|
||||
HookContext<String> hookContext = HookContext.<String>builder()
|
||||
.flagKey("realFlag")
|
||||
.type(FlagValueType.STRING)
|
||||
.defaultValue("realDefault")
|
||||
.ctx(evaluationContext)
|
||||
.clientMetadata(mock(ClientMetadata.class))
|
||||
.providerMetadata(providerMetadata)
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
|
||||
.flagMetadata(ImmutableMetadata.builder()
|
||||
.addString("contextId", "realContextId")
|
||||
.addString("flagSetId", "realFlagSetId")
|
||||
.addString("version", "realVersion")
|
||||
.build())
|
||||
.reason(Reason.DEFAULT.name())
|
||||
.variant("realVariant")
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
|
||||
|
||||
assertEquals("realFlag", event.getAttributes().get(Telemetry.TELEMETRY_KEY));
|
||||
assertEquals("realProviderName", event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
|
||||
assertEquals("default", event.getAttributes().get(Telemetry.TELEMETRY_REASON));
|
||||
assertEquals("realContextId", event.getAttributes().get(Telemetry.TELEMETRY_CONTEXT_ID));
|
||||
assertEquals("realFlagSetId", event.getAttributes().get(Telemetry.TELEMETRY_FLAG_SET_ID));
|
||||
assertEquals("realVersion", event.getAttributes().get(Telemetry.TELEMETRY_VERSION));
|
||||
assertNull(event.getAttributes().get(Telemetry.TELEMETRY_ERROR_CODE));
|
||||
assertEquals("realVariant", event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorEvaluation() {
|
||||
EvaluationContext evaluationContext = mock(EvaluationContext.class);
|
||||
when(evaluationContext.getTargetingKey()).thenReturn("realTargetingKey");
|
||||
|
||||
Metadata providerMetadata = mock(Metadata.class);
|
||||
when(providerMetadata.getName()).thenReturn("realProviderName");
|
||||
|
||||
HookContext<String> hookContext = HookContext.<String>builder()
|
||||
.flagKey("realFlag")
|
||||
.type(FlagValueType.STRING)
|
||||
.defaultValue("realDefault")
|
||||
.ctx(evaluationContext)
|
||||
.clientMetadata(mock(ClientMetadata.class))
|
||||
.providerMetadata(providerMetadata)
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
|
||||
.flagMetadata(ImmutableMetadata.builder()
|
||||
.addString("contextId", "realContextId")
|
||||
.addString("flagSetId", "realFlagSetId")
|
||||
.addString("version", "realVersion")
|
||||
.build())
|
||||
.reason(Reason.ERROR.name())
|
||||
.errorMessage("realErrorMessage")
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
|
||||
|
||||
assertEquals("realFlag", event.getAttributes().get(Telemetry.TELEMETRY_KEY));
|
||||
assertEquals("realProviderName", event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
|
||||
assertEquals("error", event.getAttributes().get(Telemetry.TELEMETRY_REASON));
|
||||
assertEquals("realContextId", event.getAttributes().get(Telemetry.TELEMETRY_CONTEXT_ID));
|
||||
assertEquals("realFlagSetId", event.getAttributes().get(Telemetry.TELEMETRY_FLAG_SET_ID));
|
||||
assertEquals("realVersion", event.getAttributes().get(Telemetry.TELEMETRY_VERSION));
|
||||
assertEquals(ErrorCode.GENERAL, event.getAttributes().get(Telemetry.TELEMETRY_ERROR_CODE));
|
||||
assertEquals("realErrorMessage", event.getAttributes().get(Telemetry.TELEMETRY_ERROR_MSG));
|
||||
assertNull(event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorCodeEvaluation() {
|
||||
EvaluationContext evaluationContext = mock(EvaluationContext.class);
|
||||
when(evaluationContext.getTargetingKey()).thenReturn("realTargetingKey");
|
||||
|
||||
Metadata providerMetadata = mock(Metadata.class);
|
||||
when(providerMetadata.getName()).thenReturn("realProviderName");
|
||||
|
||||
HookContext<String> hookContext = HookContext.<String>builder()
|
||||
.flagKey("realFlag")
|
||||
.type(FlagValueType.STRING)
|
||||
.defaultValue("realDefault")
|
||||
.ctx(evaluationContext)
|
||||
.clientMetadata(mock(ClientMetadata.class))
|
||||
.providerMetadata(providerMetadata)
|
||||
.build();
|
||||
|
||||
FlagEvaluationDetails<String> providerEvaluation = FlagEvaluationDetails.<String>builder()
|
||||
.flagMetadata(ImmutableMetadata.builder()
|
||||
.addString("contextId", "realContextId")
|
||||
.addString("flagSetId", "realFlagSetId")
|
||||
.addString("version", "realVersion")
|
||||
.build())
|
||||
.reason(Reason.ERROR.name())
|
||||
.errorMessage("realErrorMessage")
|
||||
.errorCode(ErrorCode.INVALID_CONTEXT)
|
||||
.build();
|
||||
|
||||
EvaluationEvent event = Telemetry.createEvaluationEvent(hookContext, providerEvaluation);
|
||||
|
||||
assertEquals("realFlag", event.getAttributes().get(Telemetry.TELEMETRY_KEY));
|
||||
assertEquals("realProviderName", event.getAttributes().get(Telemetry.TELEMETRY_PROVIDER));
|
||||
assertEquals("error", event.getAttributes().get(Telemetry.TELEMETRY_REASON));
|
||||
assertEquals("realContextId", event.getAttributes().get(Telemetry.TELEMETRY_CONTEXT_ID));
|
||||
assertEquals("realFlagSetId", event.getAttributes().get(Telemetry.TELEMETRY_FLAG_SET_ID));
|
||||
assertEquals("realVersion", event.getAttributes().get(Telemetry.TELEMETRY_VERSION));
|
||||
assertEquals(ErrorCode.INVALID_CONTEXT, event.getAttributes().get(Telemetry.TELEMETRY_ERROR_CODE));
|
||||
assertEquals("realErrorMessage", event.getAttributes().get(Telemetry.TELEMETRY_ERROR_MSG));
|
||||
assertNull(event.getAttributes().get(Telemetry.TELEMETRY_VARIANT));
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ import static org.mockito.Mockito.verify;
|
|||
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 @@ class TrackingSpecTest {
|
|||
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 @@ class TrackingSpecTest {
|
|||
.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 @@ class TrackingSpecTest {
|
|||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package dev.openfeature.sdk;
|
|||
|
||||
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.ArrayList;
|
|||
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<Object> list = new ArrayList<>();
|
||||
|
@ -42,7 +43,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void intObjectArgShouldConvertToInt() {
|
||||
void intObjectArgShouldConvertToInt() {
|
||||
try {
|
||||
Object innerValue = 1;
|
||||
Value value = new Value(innerValue);
|
||||
|
@ -53,7 +54,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void invalidObjectArgShouldThrow() {
|
||||
void invalidObjectArgShouldThrow() {
|
||||
|
||||
class Something {}
|
||||
|
||||
|
@ -63,7 +64,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void boolArgShouldContainBool() {
|
||||
void boolArgShouldContainBool() {
|
||||
boolean innerValue = true;
|
||||
Value value = new Value(innerValue);
|
||||
assertTrue(value.isBoolean());
|
||||
|
@ -71,7 +72,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void numericArgShouldReturnDoubleOrInt() {
|
||||
void numericArgShouldReturnDoubleOrInt() {
|
||||
double innerDoubleValue = 1.75;
|
||||
Value doubleValue = new Value(innerDoubleValue);
|
||||
assertTrue(doubleValue.isNumber());
|
||||
|
@ -86,7 +87,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void stringArgShouldContainString() {
|
||||
void stringArgShouldContainString() {
|
||||
String innerValue = "hi!";
|
||||
Value value = new Value(innerValue);
|
||||
assertTrue(value.isString());
|
||||
|
@ -94,7 +95,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void dateShouldContainDate() {
|
||||
void dateShouldContainDate() {
|
||||
Instant innerValue = Instant.now();
|
||||
Value value = new Value(innerValue);
|
||||
assertTrue(value.isInstant());
|
||||
|
@ -102,7 +103,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void structureShouldContainStructure() {
|
||||
void structureShouldContainStructure() {
|
||||
String INNER_KEY = "key";
|
||||
String INNER_VALUE = "val";
|
||||
MutableStructure innerValue = new MutableStructure().add(INNER_KEY, INNER_VALUE);
|
||||
|
@ -112,7 +113,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void listArgShouldContainList() {
|
||||
void listArgShouldContainList() {
|
||||
String ITEM_VALUE = "val";
|
||||
List<Value> innerValue = new ArrayList<Value>();
|
||||
innerValue.add(new Value(ITEM_VALUE));
|
||||
|
@ -122,7 +123,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void listMustBeOfValues() {
|
||||
void listMustBeOfValues() {
|
||||
String item = "item";
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add(item);
|
||||
|
@ -135,7 +136,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void emptyListAllowed() {
|
||||
void emptyListAllowed() {
|
||||
List<String> list = new ArrayList<>();
|
||||
try {
|
||||
Value value = new Value((Object) list);
|
||||
|
@ -148,7 +149,7 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void valueConstructorValidateListInternals() {
|
||||
void valueConstructorValidateListInternals() {
|
||||
List<Object> list = new ArrayList<>();
|
||||
list.add(new Value("item"));
|
||||
list.add("item");
|
||||
|
@ -157,8 +158,22 @@ public class ValueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void noOpFinalize() {
|
||||
void noOpFinalize() {
|
||||
Value val = new Value();
|
||||
assertDoesNotThrow(val::finalize); // does nothing, but we want to defined in and make it final.
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalValuesShouldBeEqual() {
|
||||
Value val1 = new Value(12312312);
|
||||
Value val2 = new Value(12312312);
|
||||
assertEquals(val1, val2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unequalValuesShouldNotBeEqual() {
|
||||
Value val1 = new Value("a");
|
||||
Value val2 = new Value("b");
|
||||
assertNotEquals(val1, val2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package dev.openfeature.sdk.arch;
|
||||
|
||||
import static com.tngtech.archunit.base.DescribedPredicate.describe;
|
||||
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
|
||||
|
||||
import com.tngtech.archunit.junit.AnalyzeClasses;
|
||||
import com.tngtech.archunit.junit.ArchTest;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
|
||||
@AnalyzeClasses(packages = "dev.openfeature.sdk")
|
||||
public class ArchitectureTest {
|
||||
|
||||
@ArchTest
|
||||
public static final ArchRule avoidGetInstances = noClasses()
|
||||
.that()
|
||||
.resideOutsideOfPackages("..benchmark", "..e2e.*")
|
||||
.and()
|
||||
.haveSimpleNameNotEndingWith("SingeltonTest")
|
||||
.should()
|
||||
.callMethodWhere(describe(
|
||||
"Avoid Internal usage of OpenFeatureAPI.GetInstances",
|
||||
// Target method may not reside in class annotated with BusinessException
|
||||
methodCall ->
|
||||
methodCall.getTarget().getOwner().getFullName().equals("dev.openfeature.sdk.OpenFeatureAPI")
|
||||
// And target method may not have the static modifier
|
||||
&& methodCall.getTarget().getName().equals("getInstance")));
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package dev.openfeature.sdk.e2e;
|
||||
|
||||
import dev.openfeature.sdk.EvaluationContext;
|
||||
import dev.openfeature.sdk.FeatureProvider;
|
||||
import dev.openfeature.sdk.Metadata;
|
||||
import dev.openfeature.sdk.ProviderEvaluation;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class ContextStoringProvider implements FeatureProvider {
|
||||
private EvaluationContext evaluationContext;
|
||||
|
||||
@Override
|
||||
public Metadata getMetadata() {
|
||||
return () -> getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
|
||||
this.evaluationContext = ctx;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
|
||||
this.evaluationContext = ctx;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
|
||||
this.evaluationContext = ctx;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
|
||||
this.evaluationContext = ctx;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
|
||||
this.evaluationContext = ctx;
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
package dev.openfeature.sdk.e2e;
|
||||
|
||||
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
|
||||
import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME;
|
||||
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
|
||||
|
||||
import org.junit.platform.suite.api.ConfigurationParameter;
|
||||
import org.junit.platform.suite.api.IncludeEngines;
|
||||
import org.junit.platform.suite.api.SelectClasspathResource;
|
||||
import org.junit.platform.suite.api.SelectDirectories;
|
||||
import org.junit.platform.suite.api.Suite;
|
||||
|
||||
@Suite
|
||||
@IncludeEngines("cucumber")
|
||||
@SelectClasspathResource("features/evaluation.feature")
|
||||
@SelectDirectories("spec/specification/assets/gherkin")
|
||||
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
|
||||
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.sdk.e2e.evaluation")
|
||||
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.sdk.e2e.steps")
|
||||
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
|
||||
public class EvaluationTest {}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package dev.openfeature.sdk.e2e;
|
||||
|
||||
public class Flag {
|
||||
public String name;
|
||||
public Object defaultValue;
|
||||
public String type;
|
||||
|
||||
public Flag(String type, String name, Object defaultValue) {
|
||||
this.name = name;
|
||||
this.defaultValue = defaultValue;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package dev.openfeature.sdk.e2e;
|
||||
|
||||
import dev.openfeature.sdk.EvaluationContext;
|
||||
import dev.openfeature.sdk.FlagEvaluationDetails;
|
||||
import dev.openfeature.sdk.Hook;
|
||||
import dev.openfeature.sdk.HookContext;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
|
||||
public class MockHook implements Hook {
|
||||
@Getter
|
||||
private boolean beforeCalled;
|
||||
|
||||
@Getter
|
||||
private boolean afterCalled;
|
||||
|
||||
@Getter
|
||||
private boolean errorCalled;
|
||||
|
||||
@Getter
|
||||
private boolean finallyAfterCalled;
|
||||
|
||||
@Getter
|
||||
private final Map<String, FlagEvaluationDetails> evaluationDetails = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
|
||||
beforeCalled = true;
|
||||
return Optional.of(ctx.getCtx());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) {
|
||||
afterCalled = true;
|
||||
evaluationDetails.put("after", details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(HookContext ctx, Exception error, Map hints) {
|
||||
errorCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finallyAfter(HookContext ctx, FlagEvaluationDetails details, Map hints) {
|
||||
finallyAfterCalled = true;
|
||||
evaluationDetails.put("finally", details);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package dev.openfeature.sdk.e2e;
|
||||
|
||||
import dev.openfeature.sdk.Client;
|
||||
import dev.openfeature.sdk.EvaluationContext;
|
||||
import dev.openfeature.sdk.FeatureProvider;
|
||||
import dev.openfeature.sdk.FlagEvaluationDetails;
|
||||
import dev.openfeature.sdk.MutableContext;
|
||||
import java.util.List;
|
||||
|
||||
public class State {
|
||||
public Client client;
|
||||
public Flag flag;
|
||||
public MutableContext context = new MutableContext();
|
||||
public FlagEvaluationDetails evaluation;
|
||||
public MockHook hook;
|
||||
public FeatureProvider provider;
|
||||
public EvaluationContext invocationContext;
|
||||
public List<String> levels;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package dev.openfeature.sdk.e2e;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Utils {
|
||||
|
||||
private Utils() {}
|
||||
|
||||
public static Object convert(String value, String type) {
|
||||
if (Objects.equals(value, "null")) {
|
||||
return null;
|
||||
}
|
||||
switch (type.toLowerCase()) {
|
||||
case "boolean":
|
||||
return Boolean.parseBoolean(value);
|
||||
case "string":
|
||||
return value;
|
||||
case "integer":
|
||||
return Integer.parseInt(value);
|
||||
case "float":
|
||||
case "double":
|
||||
return Double.parseDouble(value);
|
||||
case "long":
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
throw new RuntimeException("Unknown config type: " + type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package dev.openfeature.sdk.e2e.steps;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import dev.openfeature.sdk.EvaluationContext;
|
||||
import dev.openfeature.sdk.Hook;
|
||||
import dev.openfeature.sdk.HookContext;
|
||||
import dev.openfeature.sdk.ImmutableContext;
|
||||
import dev.openfeature.sdk.OpenFeatureAPI;
|
||||
import dev.openfeature.sdk.ThreadLocalTransactionContextPropagator;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import dev.openfeature.sdk.e2e.ContextStoringProvider;
|
||||
import dev.openfeature.sdk.e2e.State;
|
||||
import io.cucumber.datatable.DataTable;
|
||||
import io.cucumber.java.en.And;
|
||||
import io.cucumber.java.en.Given;
|
||||
import io.cucumber.java.en.Then;
|
||||
import io.cucumber.java.en.When;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ContextSteps {
|
||||
private final State state;
|
||||
|
||||
public ContextSteps(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Given("a stable provider with retrievable context is registered")
|
||||
public void setup() {
|
||||
ContextStoringProvider provider = new ContextStoringProvider();
|
||||
state.provider = provider;
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
|
||||
state.client = OpenFeatureAPI.getInstance().getClient();
|
||||
OpenFeatureAPI.getInstance().setTransactionContextPropagator(new ThreadLocalTransactionContextPropagator());
|
||||
}
|
||||
|
||||
@When("A context entry with key {string} and value {string} is added to the {string} level")
|
||||
public void aContextWithKeyAndValueIsAddedToTheLevel(String contextKey, String contextValue, String level) {
|
||||
addContextEntry(contextKey, contextValue, level);
|
||||
}
|
||||
|
||||
private void addContextEntry(String contextKey, String contextValue, String level) {
|
||||
Map<String, Value> data = new HashMap<>();
|
||||
data.put(contextKey, new Value(contextValue));
|
||||
EvaluationContext context = new ImmutableContext(data);
|
||||
if ("API".equals(level)) {
|
||||
OpenFeatureAPI.getInstance().setEvaluationContext(context);
|
||||
} else if ("Transaction".equals(level)) {
|
||||
OpenFeatureAPI.getInstance().setTransactionContext(context);
|
||||
} else if ("Client".equals(level)) {
|
||||
state.client.setEvaluationContext(context);
|
||||
} else if ("Invocation".equals(level)) {
|
||||
state.invocationContext = context;
|
||||
} else if ("Before Hooks".equals(level)) {
|
||||
state.client.addHooks(new Hook() {
|
||||
@Override
|
||||
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
|
||||
return Optional.of(context);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown level: " + level);
|
||||
}
|
||||
}
|
||||
|
||||
@When("Some flag was evaluated")
|
||||
public void someFlagWasEvaluated() {
|
||||
state.evaluation = state.client.getStringDetails("unused", "unused", state.invocationContext);
|
||||
}
|
||||
|
||||
@Then("The merged context contains an entry with key {string} and value {string}")
|
||||
public void theMergedContextContainsAnEntryWithKeyAndValue(String contextKey, String contextValue) {
|
||||
assertInstanceOf(
|
||||
ContextStoringProvider.class,
|
||||
state.provider,
|
||||
"In order to use this step, you need to set a ContextStoringProvider");
|
||||
EvaluationContext ctx = ((ContextStoringProvider) state.provider).getEvaluationContext();
|
||||
assertNotNull(ctx);
|
||||
assertNotNull(ctx.getValue(contextKey));
|
||||
assertNotNull(ctx.getValue(contextKey).asString());
|
||||
assertEquals(contextValue, ctx.getValue(contextKey).asString());
|
||||
}
|
||||
|
||||
@Given("A table with levels of increasing precedence")
|
||||
public void aTableWithLevelsOfIncreasingPrecedence(DataTable levelsTable) {
|
||||
state.levels = levelsTable.asList();
|
||||
}
|
||||
|
||||
@And(
|
||||
"Context entries for each level from API level down to the {string} level, with key {string} and value {string}")
|
||||
public void contextEntriesForEachLevelFromAPILevelDownToTheLevelWithKeyAndValue(
|
||||
String maxLevel, String key, String value) {
|
||||
for (String level : state.levels) {
|
||||
addContextEntry(key, value, level);
|
||||
if (level.equals(maxLevel)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package dev.openfeature.sdk.e2e.steps;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.openfeature.sdk.FlagEvaluationDetails;
|
||||
import dev.openfeature.sdk.ImmutableMetadata;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import dev.openfeature.sdk.e2e.Flag;
|
||||
import dev.openfeature.sdk.e2e.State;
|
||||
import dev.openfeature.sdk.e2e.Utils;
|
||||
import io.cucumber.datatable.DataTable;
|
||||
import io.cucumber.java.en.Given;
|
||||
import io.cucumber.java.en.Then;
|
||||
import io.cucumber.java.en.When;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class FlagStepDefinitions {
|
||||
private final State state;
|
||||
|
||||
public FlagStepDefinitions(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Given("a {}-flag with key {string} and a default value {string}")
|
||||
public void givenAFlag(String type, String name, String defaultValue) {
|
||||
state.flag = new Flag(type, name, Utils.convert(defaultValue, type));
|
||||
}
|
||||
|
||||
@When("the flag was evaluated with details")
|
||||
public void the_flag_was_evaluated_with_details() {
|
||||
FlagEvaluationDetails details;
|
||||
switch (state.flag.type.toLowerCase()) {
|
||||
case "string":
|
||||
details =
|
||||
state.client.getStringDetails(state.flag.name, (String) state.flag.defaultValue, state.context);
|
||||
break;
|
||||
case "boolean":
|
||||
details = state.client.getBooleanDetails(
|
||||
state.flag.name, (Boolean) state.flag.defaultValue, state.context);
|
||||
break;
|
||||
case "float":
|
||||
details =
|
||||
state.client.getDoubleDetails(state.flag.name, (Double) state.flag.defaultValue, state.context);
|
||||
break;
|
||||
case "integer":
|
||||
details = state.client.getIntegerDetails(
|
||||
state.flag.name, (Integer) state.flag.defaultValue, state.context);
|
||||
break;
|
||||
case "object":
|
||||
details =
|
||||
state.client.getObjectDetails(state.flag.name, (Value) state.flag.defaultValue, state.context);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
state.evaluation = details;
|
||||
}
|
||||
|
||||
@Then("the resolved details value should be {string}")
|
||||
public void the_resolved_details_value_should_be(String value) {
|
||||
assertThat(state.evaluation.getValue()).isEqualTo(Utils.convert(value, state.flag.type));
|
||||
}
|
||||
|
||||
@Then("the reason should be {string}")
|
||||
public void the_reason_should_be(String reason) {
|
||||
assertThat(state.evaluation.getReason()).isEqualTo(reason);
|
||||
}
|
||||
|
||||
@Then("the variant should be {string}")
|
||||
public void the_variant_should_be(String variant) {
|
||||
assertThat(state.evaluation.getVariant()).isEqualTo(variant);
|
||||
}
|
||||
|
||||
@Then("the resolved metadata value \"{}\" with type \"{}\" should be \"{}\"")
|
||||
public void theResolvedMetadataValueShouldBe(String key, String type, String value)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Field f = state.evaluation.getFlagMetadata().getClass().getDeclaredField("metadata");
|
||||
f.setAccessible(true);
|
||||
HashMap<String, Object> metadata = (HashMap<String, Object>) f.get(state.evaluation.getFlagMetadata());
|
||||
assertThat(metadata).containsEntry(key, Utils.convert(value, type));
|
||||
}
|
||||
|
||||
@Then("the resolved metadata is empty")
|
||||
public void theResolvedMetadataIsEmpty() {
|
||||
assertThat(state.evaluation.getFlagMetadata().isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Then("the resolved metadata should contain")
|
||||
public void theResolvedMetadataShouldContain(DataTable dataTable) {
|
||||
ImmutableMetadata evaluationMetadata = state.evaluation.getFlagMetadata();
|
||||
List<List<String>> asLists = dataTable.asLists();
|
||||
for (int i = 1; i < asLists.size(); i++) { // skip the header of the table
|
||||
List<String> line = asLists.get(i);
|
||||
String key = line.get(0);
|
||||
String metadataType = line.get(1);
|
||||
Object value = Utils.convert(line.get(2), metadataType);
|
||||
|
||||
assertThat(value).isNotNull();
|
||||
assertThat(evaluationMetadata.getValue(key, value.getClass())).isEqualTo(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package dev.openfeature.sdk.e2e.steps;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import dev.openfeature.sdk.FlagEvaluationDetails;
|
||||
import dev.openfeature.sdk.e2e.MockHook;
|
||||
import dev.openfeature.sdk.e2e.State;
|
||||
import dev.openfeature.sdk.e2e.Utils;
|
||||
import io.cucumber.datatable.DataTable;
|
||||
import io.cucumber.java.en.And;
|
||||
import io.cucumber.java.en.Given;
|
||||
import io.cucumber.java.en.Then;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HookSteps {
|
||||
private final State state;
|
||||
|
||||
public HookSteps(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Given("a client with added hook")
|
||||
public void aClientWithAddedHook() {
|
||||
MockHook hook = new MockHook();
|
||||
state.hook = hook;
|
||||
state.client.addHooks(hook);
|
||||
}
|
||||
|
||||
@Then("the {string} hook should have been executed")
|
||||
public void theHookShouldHaveBeenExecuted(String hookName) {
|
||||
assertHookCalled(hookName);
|
||||
}
|
||||
|
||||
public void assertHookCalled(String hookName) {
|
||||
if ("before".equals(hookName)) {
|
||||
assertTrue(state.hook.isBeforeCalled());
|
||||
} else if ("after".equals(hookName)) {
|
||||
assertTrue(state.hook.isAfterCalled());
|
||||
} else if ("error".equals(hookName)) {
|
||||
assertTrue(state.hook.isErrorCalled());
|
||||
} else if ("finally".equals(hookName)) {
|
||||
assertTrue(state.hook.isFinallyAfterCalled());
|
||||
} else {
|
||||
throw new IllegalArgumentException(hookName + " is not a valid hook name");
|
||||
}
|
||||
}
|
||||
|
||||
@And("the {string} hooks should be called with evaluation details")
|
||||
public void theHooksShouldBeCalledWithEvaluationDetails(String hookNames, DataTable data) {
|
||||
for (String hookName : hookNames.split(", ")) {
|
||||
assertHookCalled(hookName);
|
||||
FlagEvaluationDetails evaluationDetails =
|
||||
state.hook.getEvaluationDetails().get(hookName);
|
||||
assertNotNull(evaluationDetails);
|
||||
List<Map<String, String>> dataEntries = data.asMaps();
|
||||
for (Map<String, String> line : dataEntries) {
|
||||
String key = line.get("key");
|
||||
Object expected = Utils.convert(line.get("value"), line.get("data_type"));
|
||||
Object actual;
|
||||
if ("flag_key".equals(key)) {
|
||||
actual = evaluationDetails.getFlagKey();
|
||||
} else if ("value".equals(key)) {
|
||||
actual = evaluationDetails.getValue();
|
||||
} else if ("variant".equals(key)) {
|
||||
actual = evaluationDetails.getVariant();
|
||||
} else if ("reason".equals(key)) {
|
||||
actual = evaluationDetails.getReason();
|
||||
} else if ("error_code".equals(key)) {
|
||||
actual = evaluationDetails.getErrorCode();
|
||||
if (actual != null) {
|
||||
actual = actual.toString();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(key + " is not a valid key");
|
||||
}
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package dev.openfeature.sdk.e2e.steps;
|
||||
|
||||
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
|
||||
|
||||
import dev.openfeature.sdk.OpenFeatureAPI;
|
||||
import dev.openfeature.sdk.e2e.State;
|
||||
import dev.openfeature.sdk.providers.memory.Flag;
|
||||
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
|
||||
import io.cucumber.java.en.Given;
|
||||
import java.util.Map;
|
||||
|
||||
public class ProviderSteps {
|
||||
private final State state;
|
||||
|
||||
public ProviderSteps(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Given("a stable provider")
|
||||
public void aStableProvider() {
|
||||
Map<String, Flag<?>> flags = buildFlags();
|
||||
InMemoryProvider provider = new InMemoryProvider(flags);
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
|
||||
state.client = OpenFeatureAPI.getInstance().getClient();
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
package dev.openfeature.sdk.e2e.evaluation;
|
||||
package dev.openfeature.sdk.e2e.steps;
|
||||
|
||||
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import dev.openfeature.sdk.Client;
|
||||
import dev.openfeature.sdk.EvaluationContext;
|
||||
|
@ -289,7 +288,7 @@ public class StepDefinitions {
|
|||
@Then("the reason should indicate an error and the error code should indicate a missing flag with {string}")
|
||||
public void the_reason_should_indicate_an_error_and_the_error_code_should_be_flag_not_found(String errorCode) {
|
||||
assertEquals(Reason.ERROR.toString(), notFoundDetails.getReason());
|
||||
assertTrue(notFoundDetails.getErrorCode().name().equals(errorCode));
|
||||
assertEquals(errorCode, notFoundDetails.getErrorCode().name());
|
||||
}
|
||||
|
||||
// type mismatch
|
||||
|
@ -309,6 +308,23 @@ public class StepDefinitions {
|
|||
@Then("the reason should indicate an error and the error code should indicate a type mismatch with {string}")
|
||||
public void the_reason_should_indicate_an_error_and_the_error_code_should_be_type_mismatch(String errorCode) {
|
||||
assertEquals(Reason.ERROR.toString(), typeErrorDetails.getReason());
|
||||
assertTrue(typeErrorDetails.getErrorCode().name().equals(errorCode));
|
||||
assertEquals(errorCode, typeErrorDetails.getErrorCode().name());
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S2925")
|
||||
@When("sleep for {int} milliseconds")
|
||||
public void sleepForMilliseconds(int millis) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
long endTime = startTime + millis;
|
||||
long now;
|
||||
while ((now = System.currentTimeMillis()) < endTime) {
|
||||
long remainingTime = endTime - now;
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(remainingTime);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,21 +3,28 @@ package dev.openfeature.sdk.providers.memory;
|
|||
import static dev.openfeature.sdk.Structure.mapToStructure;
|
||||
import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dev.openfeature.sdk.Client;
|
||||
import dev.openfeature.sdk.EventDetails;
|
||||
import dev.openfeature.sdk.ImmutableContext;
|
||||
import dev.openfeature.sdk.OpenFeatureAPI;
|
||||
import dev.openfeature.sdk.OpenFeatureAPITestUtil;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
|
||||
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
|
||||
import dev.openfeature.sdk.exceptions.TypeMismatchError;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -25,18 +32,21 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
class InMemoryProviderTest {
|
||||
|
||||
private static Client client;
|
||||
private Client client;
|
||||
|
||||
private static InMemoryProvider provider;
|
||||
private InMemoryProvider provider;
|
||||
private OpenFeatureAPI api;
|
||||
|
||||
@SneakyThrows
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
final var configChangedEventCounter = new AtomicInteger();
|
||||
Map<String, Flag<?>> flags = buildFlags();
|
||||
provider = spy(new InMemoryProvider(flags));
|
||||
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {});
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
|
||||
client = OpenFeatureAPI.getInstance().getClient();
|
||||
api = OpenFeatureAPITestUtil.createAPI();
|
||||
api.onProviderConfigurationChanged(eventDetails -> configChangedEventCounter.incrementAndGet());
|
||||
api.setProviderAndWait(provider);
|
||||
client = api.getClient();
|
||||
provider.updateFlags(flags);
|
||||
provider.updateFlag(
|
||||
"addedFlag",
|
||||
|
@ -45,6 +55,11 @@ class InMemoryProviderTest {
|
|||
.variant("off", false)
|
||||
.defaultVariant("on")
|
||||
.build());
|
||||
|
||||
// wait for the two config changed events to be fired, otherwise they could mess with our tests
|
||||
while (configChangedEventCounter.get() < 2) {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -107,8 +122,8 @@ class InMemoryProviderTest {
|
|||
Consumer<EventDetails> handler = mock(Consumer.class);
|
||||
Map<String, Flag<?>> flags = buildFlags();
|
||||
|
||||
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler);
|
||||
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
|
||||
api.onProviderConfigurationChanged(handler);
|
||||
api.setProviderAndWait(provider);
|
||||
|
||||
provider.updateFlags(flags);
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package dev.openfeature.sdk.testutils;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
import dev.openfeature.sdk.FeatureProvider;
|
||||
import dev.openfeature.sdk.OpenFeatureAPI;
|
||||
import java.time.Duration;
|
||||
import java.util.function.Function;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
// todo check the need of this utility class as we now have setProviderAndWait capability
|
||||
@UtilityClass
|
||||
public class FeatureProviderTestUtils {
|
||||
|
||||
public static void setFeatureProvider(FeatureProvider provider) {
|
||||
OpenFeatureAPI.getInstance().setProvider(provider);
|
||||
waitForProviderInitializationComplete(OpenFeatureAPI::getProvider, provider);
|
||||
}
|
||||
|
||||
private static void waitForProviderInitializationComplete(
|
||||
Function<OpenFeatureAPI, FeatureProvider> extractor, FeatureProvider provider) {
|
||||
await().pollDelay(Duration.ofMillis(1))
|
||||
.atMost(Duration.ofSeconds(1))
|
||||
.until(() -> extractor.apply(OpenFeatureAPI.getInstance()).equals(provider));
|
||||
}
|
||||
|
||||
public static void setFeatureProvider(String domain, FeatureProvider provider) {
|
||||
OpenFeatureAPI.getInstance().setProvider(domain, provider);
|
||||
waitForProviderInitializationComplete(api -> api.getProvider(domain), provider);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package dev.openfeature.sdk.testutils;
|
|||
import static dev.openfeature.sdk.Structure.mapToStructure;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dev.openfeature.sdk.ImmutableMetadata;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import dev.openfeature.sdk.providers.memory.Flag;
|
||||
import java.util.HashMap;
|
||||
|
@ -22,9 +23,11 @@ public class TestFlagsUtils {
|
|||
public static final String OBJECT_FLAG_KEY = "object-flag";
|
||||
public static final String CONTEXT_AWARE_FLAG_KEY = "context-aware";
|
||||
public static final String WRONG_FLAG_KEY = "wrong-flag";
|
||||
public static final String METADATA_FLAG_KEY = "metadata-flag";
|
||||
|
||||
/**
|
||||
* Building flags for testing purposes.
|
||||
*
|
||||
* @return map of flags
|
||||
*/
|
||||
public static Map<String, Flag<?>> buildFlags() {
|
||||
|
@ -90,6 +93,19 @@ public class TestFlagsUtils {
|
|||
.variant("two", "dos")
|
||||
.defaultVariant("one")
|
||||
.build());
|
||||
flags.put(
|
||||
METADATA_FLAG_KEY,
|
||||
Flag.builder()
|
||||
.variant("on", true)
|
||||
.variant("off", false)
|
||||
.defaultVariant("on")
|
||||
.flagMetadata(ImmutableMetadata.builder()
|
||||
.addString("string", "1.0.2")
|
||||
.addInteger("integer", 2)
|
||||
.addBoolean("boolean", true)
|
||||
.addDouble("float", 0.1d)
|
||||
.build())
|
||||
.build());
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.14.1
|
||||
1.16.0
|
||||
|
|
Loading…
Reference in New Issue