From b0abfd02cf9e97f7409df3296818ac990b429058 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 01:20:08 +0000 Subject: [PATCH 01/28] chore(deps): update github/codeql-action digest to 8975792 (#1241) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 1eaeaf48..82c17978 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@6f9e628e6f9a18c785dd746325ba455111df1b67 + uses: github/codeql-action/init@89757925c7adddb19b7a2f28e3e1b27da88b7304 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6f9e628e6f9a18c785dd746325ba455111df1b67 + uses: github/codeql-action/analyze@89757925c7adddb19b7a2f28e3e1b27da88b7304 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 41ea5f52..71cf81d5 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@6f9e628e6f9a18c785dd746325ba455111df1b67 + uses: github/codeql-action/init@89757925c7adddb19b7a2f28e3e1b27da88b7304 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@6f9e628e6f9a18c785dd746325ba455111df1b67 + uses: github/codeql-action/autobuild@89757925c7adddb19b7a2f28e3e1b27da88b7304 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6f9e628e6f9a18c785dd746325ba455111df1b67 + uses: github/codeql-action/analyze@89757925c7adddb19b7a2f28e3e1b27da88b7304 From 884f8fbf77c41e070526da0f73e136d4c3e41a4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 01:28:12 +0000 Subject: [PATCH 02/28] chore(deps): update github/codeql-action digest to 4d64ab6 (#1243) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 82c17978..92f2dd65 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@89757925c7adddb19b7a2f28e3e1b27da88b7304 + uses: github/codeql-action/init@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@89757925c7adddb19b7a2f28e3e1b27da88b7304 + uses: github/codeql-action/analyze@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 71cf81d5..ac3559de 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@89757925c7adddb19b7a2f28e3e1b27da88b7304 + uses: github/codeql-action/init@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@89757925c7adddb19b7a2f28e3e1b27da88b7304 + uses: github/codeql-action/autobuild@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@89757925c7adddb19b7a2f28e3e1b27da88b7304 + uses: github/codeql-action/analyze@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 From fd1c1702c6d4067c432c1522143266ddf470d18d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:04:18 +0000 Subject: [PATCH 03/28] chore(deps): update github/codeql-action digest to 78d0136 (#1245) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 92f2dd65..53fc7bd3 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 + uses: github/codeql-action/init@78d0136ff775bdfbff08f0d165181940f8bcf007 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 + uses: github/codeql-action/analyze@78d0136ff775bdfbff08f0d165181940f8bcf007 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index ac3559de..8f1a82f6 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 + uses: github/codeql-action/init@78d0136ff775bdfbff08f0d165181940f8bcf007 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 + uses: github/codeql-action/autobuild@78d0136ff775bdfbff08f0d165181940f8bcf007 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4d64ab66ada6f86ef77ffc21047dd6ffabd004d4 + uses: github/codeql-action/analyze@78d0136ff775bdfbff08f0d165181940f8bcf007 From 9acc8612a5fa7ea086da476195154a007cb55b7e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 00:54:52 +0000 Subject: [PATCH 04/28] chore(deps): update actions/setup-java digest to 7136edc (#1244) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/merge.yml | 2 +- .github/workflows/pullrequest.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index ad5c5638..4f9d6f2c 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb - name: Set up JDK 8 - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b + uses: actions/setup-java@7136edc5e8145b3c0b6bae8f4e62706c74e76538 with: java-version: '8' distribution: 'temurin' diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 53fc7bd3..5fb90877 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb - name: Set up JDK 8 - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b + uses: actions/setup-java@7136edc5e8145b3c0b6bae8f4e62706c74e76538 with: java-version: '8' distribution: 'temurin' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 251ae637..60699590 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb - name: Set up JDK 8 if: ${{ steps.release.outputs.release_created }} - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b + uses: actions/setup-java@7136edc5e8145b3c0b6bae8f4e62706c74e76538 with: java-version: '8' distribution: 'temurin' From 86e18c5d28a9f5fdd7234274720ba7ddcb529268 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 01:31:22 +0000 Subject: [PATCH 05/28] chore(deps): update actions/setup-java digest to 7a6d8a8 (#1248) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/merge.yml | 2 +- .github/workflows/pullrequest.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 4f9d6f2c..3aeb2371 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb - name: Set up JDK 8 - uses: actions/setup-java@7136edc5e8145b3c0b6bae8f4e62706c74e76538 + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b with: java-version: '8' distribution: 'temurin' diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 5fb90877..cce82684 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb - name: Set up JDK 8 - uses: actions/setup-java@7136edc5e8145b3c0b6bae8f4e62706c74e76538 + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b with: java-version: '8' distribution: 'temurin' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60699590..2b1eb9e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb - name: Set up JDK 8 if: ${{ steps.release.outputs.release_created }} - uses: actions/setup-java@7136edc5e8145b3c0b6bae8f4e62706c74e76538 + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b with: java-version: '8' distribution: 'temurin' From 4440cda6a5b42a903ba11835a975bf6247de845f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 12:48:35 +0000 Subject: [PATCH 06/28] chore(deps): update dependency net.bytebuddy:byte-buddy to v1.15.11 (#1249) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c44206f..243062e8 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,7 @@ net.bytebuddy byte-buddy - 1.15.10 + 1.15.11 test From 6d169f55e235a071033a9bf1138484f09a5e472d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 15:06:36 +0000 Subject: [PATCH 07/28] chore(deps): update github/codeql-action digest to dd75594 (#1247) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index cce82684..43c8bac5 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@78d0136ff775bdfbff08f0d165181940f8bcf007 + uses: github/codeql-action/init@dd7559424621a6dd0b32ababe9e4b271a87f78d2 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@78d0136ff775bdfbff08f0d165181940f8bcf007 + uses: github/codeql-action/analyze@dd7559424621a6dd0b32ababe9e4b271a87f78d2 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 8f1a82f6..e6eb92de 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@78d0136ff775bdfbff08f0d165181940f8bcf007 + uses: github/codeql-action/init@dd7559424621a6dd0b32ababe9e4b271a87f78d2 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@78d0136ff775bdfbff08f0d165181940f8bcf007 + uses: github/codeql-action/autobuild@dd7559424621a6dd0b32ababe9e4b271a87f78d2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@78d0136ff775bdfbff08f0d165181940f8bcf007 + uses: github/codeql-action/analyze@dd7559424621a6dd0b32ababe9e4b271a87f78d2 From 6772d3f3943fb3b7f7522c80b732aa058fd03bb9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:15:18 +0000 Subject: [PATCH 08/28] chore(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.15.11 (#1250) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 243062e8..78432e71 100644 --- a/pom.xml +++ b/pom.xml @@ -171,7 +171,7 @@ net.bytebuddy byte-buddy-agent - 1.15.10 + 1.15.11 test From 834f72071806680353f42c750b04e36956736a9e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:39:04 +0000 Subject: [PATCH 09/28] fix(deps): update junit5 monorepo (#1251) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 78432e71..cd500fce 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ UTF-8 1.8 ${maven.compiler.source} - 5.11.3 + 5.11.4 **/e2e/*.java ${project.groupId}.${project.artifactId} @@ -109,7 +109,7 @@ org.junit.platform junit-platform-suite - 1.11.3 + 1.11.4 test @@ -187,7 +187,7 @@ org.junit junit-bom - 5.11.3 + 5.11.4 pom import From 482a5aef1005b2ebe2fdb9ee43243b6c2aeeadc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:56:34 +0000 Subject: [PATCH 10/28] chore(deps): update github/codeql-action digest to 9d59969 (#1252) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 43c8bac5..bfbfd198 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@dd7559424621a6dd0b32ababe9e4b271a87f78d2 + uses: github/codeql-action/init@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd7559424621a6dd0b32ababe9e4b271a87f78d2 + uses: github/codeql-action/analyze@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index e6eb92de..52e88486 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@dd7559424621a6dd0b32ababe9e4b271a87f78d2 + uses: github/codeql-action/init@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@dd7559424621a6dd0b32ababe9e4b271a87f78d2 + uses: github/codeql-action/autobuild@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd7559424621a6dd0b32ababe9e4b271a87f78d2 + uses: github/codeql-action/analyze@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a From f39c4b5af5e341bfec230d4cecd2037fc5430400 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 06:20:23 +0000 Subject: [PATCH 11/28] chore(deps): update dependency com.google.guava:guava to v33.4.0-jre (#1253) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cd500fce..a8a0ff42 100644 --- a/pom.xml +++ b/pom.xml @@ -135,7 +135,7 @@ com.google.guava guava - 33.3.1-jre + 33.4.0-jre test From 6a7987455ef7e46d40b835c7d8dbda29322e3b2d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 03:34:59 +0000 Subject: [PATCH 12/28] chore(deps): update github/codeql-action digest to 562042d (#1254) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index bfbfd198..62339017 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a + uses: github/codeql-action/init@562042d742d834fa44c4fd29c197a62d76568c60 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a + uses: github/codeql-action/analyze@562042d742d834fa44c4fd29c197a62d76568c60 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 52e88486..81b66207 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a + uses: github/codeql-action/init@562042d742d834fa44c4fd29c197a62d76568c60 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a + uses: github/codeql-action/autobuild@562042d742d834fa44c4fd29c197a62d76568c60 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9d599696ef6f3546b15e3d9dfe46db6dfa487b9a + uses: github/codeql-action/analyze@562042d742d834fa44c4fd29c197a62d76568c60 From d274cdac3780286a0b45865864b12c3e4cff9f4b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:15:10 +0000 Subject: [PATCH 13/28] chore(deps): update codecov/codecov-action action to v5.1.2 (#1255) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/merge.yml | 2 +- .github/workflows/pullrequest.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 3aeb2371..92993ff5 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -49,7 +49,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5.1.1 + uses: codecov/codecov-action@v5.1.2 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos flags: unittests # optional diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 62339017..cc345fc2 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -36,7 +36,7 @@ jobs: run: mvn --batch-mode --update-snapshots --activate-profiles e2e verify - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5.1.1 + uses: codecov/codecov-action@v5.1.2 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos flags: unittests # optional From 992c00396cb2fca6a6a7dc63d727b063a79386b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 04:03:19 +0000 Subject: [PATCH 14/28] chore(deps): update github/codeql-action digest to 64cc90b (#1256) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index cc345fc2..9659aaf6 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@562042d742d834fa44c4fd29c197a62d76568c60 + uses: github/codeql-action/init@64cc90bcd4b0a6919309f7882f920e60de2aef1c with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@562042d742d834fa44c4fd29c197a62d76568c60 + uses: github/codeql-action/analyze@64cc90bcd4b0a6919309f7882f920e60de2aef1c diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 81b66207..0351ac45 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@562042d742d834fa44c4fd29c197a62d76568c60 + uses: github/codeql-action/init@64cc90bcd4b0a6919309f7882f920e60de2aef1c with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@562042d742d834fa44c4fd29c197a62d76568c60 + uses: github/codeql-action/autobuild@64cc90bcd4b0a6919309f7882f920e60de2aef1c - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@562042d742d834fa44c4fd29c197a62d76568c60 + uses: github/codeql-action/analyze@64cc90bcd4b0a6919309f7882f920e60de2aef1c From 6d60c962fbac48a13d86271b361fb0cfd91a5342 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:28:15 +0000 Subject: [PATCH 15/28] chore(deps): update github/codeql-action digest to d01b25e (#1257) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9659aaf6..6700b6fe 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@64cc90bcd4b0a6919309f7882f920e60de2aef1c + uses: github/codeql-action/init@d01b25e645295d11d0fbb084a8c5e7546b1e508b with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@64cc90bcd4b0a6919309f7882f920e60de2aef1c + uses: github/codeql-action/analyze@d01b25e645295d11d0fbb084a8c5e7546b1e508b diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 0351ac45..da1577a8 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@64cc90bcd4b0a6919309f7882f920e60de2aef1c + uses: github/codeql-action/init@d01b25e645295d11d0fbb084a8c5e7546b1e508b with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@64cc90bcd4b0a6919309f7882f920e60de2aef1c + uses: github/codeql-action/autobuild@d01b25e645295d11d0fbb084a8c5e7546b1e508b - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@64cc90bcd4b0a6919309f7882f920e60de2aef1c + uses: github/codeql-action/analyze@d01b25e645295d11d0fbb084a8c5e7546b1e508b From c62ade3878dabf9194536d551f3316ba5c0ce5e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:39:52 +0000 Subject: [PATCH 16/28] chore(deps): update dependency org.assertj:assertj-core to v3.27.0 (#1258) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8a0ff42..6f6e14f6 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ org.assertj assertj-core - 3.26.3 + 3.27.0 test From fc6f35e581cacb0ad149c58a5943ec1429ce25ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:46:09 +0000 Subject: [PATCH 17/28] chore(deps): update github/codeql-action digest to 7876007 (#1260) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 6700b6fe..30e172bb 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@d01b25e645295d11d0fbb084a8c5e7546b1e508b + uses: github/codeql-action/init@78760076e3f08852c2c3aeb5334f70d074e28c59 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d01b25e645295d11d0fbb084a8c5e7546b1e508b + uses: github/codeql-action/analyze@78760076e3f08852c2c3aeb5334f70d074e28c59 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index da1577a8..7891a8bb 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@d01b25e645295d11d0fbb084a8c5e7546b1e508b + uses: github/codeql-action/init@78760076e3f08852c2c3aeb5334f70d074e28c59 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@d01b25e645295d11d0fbb084a8c5e7546b1e508b + uses: github/codeql-action/autobuild@78760076e3f08852c2c3aeb5334f70d074e28c59 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d01b25e645295d11d0fbb084a8c5e7546b1e508b + uses: github/codeql-action/analyze@78760076e3f08852c2c3aeb5334f70d074e28c59 From f1817d8fef585f957de1cfb9222b03cb591ed2e9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:06:59 +0000 Subject: [PATCH 18/28] chore(deps): update github/codeql-action digest to 5b6e617 (#1263) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 30e172bb..4fa803aa 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@78760076e3f08852c2c3aeb5334f70d074e28c59 + uses: github/codeql-action/init@5b6e617dc0241b2d60c2bccea90c56b67eceb797 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@78760076e3f08852c2c3aeb5334f70d074e28c59 + uses: github/codeql-action/analyze@5b6e617dc0241b2d60c2bccea90c56b67eceb797 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 7891a8bb..f03229c2 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@78760076e3f08852c2c3aeb5334f70d074e28c59 + uses: github/codeql-action/init@5b6e617dc0241b2d60c2bccea90c56b67eceb797 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@78760076e3f08852c2c3aeb5334f70d074e28c59 + uses: github/codeql-action/autobuild@5b6e617dc0241b2d60c2bccea90c56b67eceb797 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@78760076e3f08852c2c3aeb5334f70d074e28c59 + uses: github/codeql-action/analyze@5b6e617dc0241b2d60c2bccea90c56b67eceb797 From 64ec68bcf5c8151802ce299f252bbb97dd9e0bd4 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Fri, 3 Jan 2025 19:06:22 +0100 Subject: [PATCH 19/28] build: change checkstyle to google code format, plus adding spotless (#1264) Signed-off-by: Simon Schrottner --- .editorconfig | 72 + CONTRIBUTING.md | 32 + checkstyle.xml | 259 +++- pom.xml | 1190 +++++++++-------- .../openfeature/sdk/AbstractStructure.java | 13 +- .../dev/openfeature/sdk/BaseEvaluation.java | 6 + .../java/dev/openfeature/sdk/BooleanHook.java | 2 +- .../java/dev/openfeature/sdk/DoubleHook.java | 4 +- .../java/dev/openfeature/sdk/ErrorCode.java | 8 +- .../openfeature/sdk/EvaluationContext.java | 16 +- .../java/dev/openfeature/sdk/EventBus.java | 26 +- .../dev/openfeature/sdk/EventDetails.java | 4 +- .../dev/openfeature/sdk/EventProvider.java | 1 - .../dev/openfeature/sdk/EventSupport.java | 63 +- .../dev/openfeature/sdk/FeatureProvider.java | 4 +- .../sdk/FeatureProviderStateManager.java | 4 +- .../java/dev/openfeature/sdk/Features.java | 27 +- .../sdk/FlagEvaluationDetails.java | 6 +- .../sdk/FlagEvaluationOptions.java | 2 +- .../dev/openfeature/sdk/FlagValueType.java | 6 +- src/main/java/dev/openfeature/sdk/Hook.java | 11 +- .../java/dev/openfeature/sdk/HookContext.java | 30 +- .../java/dev/openfeature/sdk/HookSupport.java | 48 +- .../dev/openfeature/sdk/ImmutableContext.java | 9 +- .../openfeature/sdk/ImmutableMetadata.java | 7 +- .../openfeature/sdk/ImmutableStructure.java | 11 +- .../sdk/ImmutableTrackingEventDetails.java | 12 +- .../java/dev/openfeature/sdk/IntegerHook.java | 2 +- .../dev/openfeature/sdk/MutableContext.java | 9 +- .../dev/openfeature/sdk/MutableStructure.java | 5 +- .../sdk/MutableTrackingEventDetails.java | 16 +- .../dev/openfeature/sdk/NoOpProvider.java | 5 +- .../sdk/NoOpTransactionContextPropagator.java | 5 +- .../dev/openfeature/sdk/OpenFeatureAPI.java | 47 +- .../openfeature/sdk/OpenFeatureClient.java | 129 +- .../openfeature/sdk/ProviderEvaluation.java | 1 + .../dev/openfeature/sdk/ProviderEvent.java | 5 +- .../openfeature/sdk/ProviderEventDetails.java | 4 +- .../openfeature/sdk/ProviderRepository.java | 82 +- .../dev/openfeature/sdk/ProviderState.java | 8 +- src/main/java/dev/openfeature/sdk/Reason.java | 9 +- .../java/dev/openfeature/sdk/StringHook.java | 2 +- .../java/dev/openfeature/sdk/Structure.java | 32 +- .../openfeature/sdk/TrackingEventDetails.java | 3 +- src/main/java/dev/openfeature/sdk/Value.java | 122 +- .../sdk/exceptions/ExceptionUtils.java | 3 +- .../sdk/exceptions/FatalError.java | 3 +- .../sdk/exceptions/FlagNotFoundError.java | 2 +- .../sdk/exceptions/GeneralError.java | 1 + .../sdk/exceptions/InvalidContextError.java | 4 +- .../sdk/exceptions/ParseError.java | 4 +- .../sdk/exceptions/ProviderNotReadyError.java | 4 +- .../exceptions/TargetingKeyMissingError.java | 4 +- .../sdk/exceptions/TypeMismatchError.java | 4 +- .../exceptions/ValueNotConvertableError.java | 1 + .../sdk/hooks/logging/LoggingHook.java | 16 +- .../sdk/internal/AutoCloseableLock.java | 2 +- .../AutoCloseableReentrantReadWriteLock.java | 4 +- .../ExcludeFromGeneratedCoverageReport.java | 7 +- .../openfeature/sdk/internal/ObjectUtils.java | 21 +- .../openfeature/sdk/internal/TriConsumer.java | 4 +- .../providers/memory/ContextEvaluator.java | 1 + .../sdk/providers/memory/Flag.java | 4 +- .../providers/memory/InMemoryProvider.java | 67 +- .../openfeature/sdk/AlwaysBrokenProvider.java | 3 +- .../sdk/AlwaysBrokenWithDetailsProvider.java | 3 +- .../sdk/ClientProviderMappingTest.java | 4 +- .../sdk/DeveloperExperienceTest.java | 39 +- .../openfeature/sdk/DoSomethingProvider.java | 6 +- .../dev/openfeature/sdk/EvalContextTest.java | 123 +- .../openfeature/sdk/EventProviderTest.java | 33 +- .../java/dev/openfeature/sdk/EventsTest.java | 266 ++-- .../sdk/FeatureProviderStateManagerTest.java | 67 +- .../sdk/FlagEvaluationDetailsTest.java | 18 +- .../sdk/FlagEvaluationSpecTest.java | 588 +++++--- .../dev/openfeature/sdk/FlagMetadataTest.java | 9 +- .../dev/openfeature/sdk/HookContextTest.java | 36 +- .../dev/openfeature/sdk/HookSpecTest.java | 358 +++-- .../dev/openfeature/sdk/HookSupportTest.java | 39 +- .../openfeature/sdk/ImmutableContextTest.java | 54 +- .../sdk/ImmutableStructureTest.java | 63 +- .../sdk/InitializeBehaviorSpecTest.java | 59 +- .../java/dev/openfeature/sdk/LockingTest.java | 28 +- .../dev/openfeature/sdk/MetadataTest.java | 14 +- .../openfeature/sdk/MutableContextTest.java | 52 +- .../sdk/MutableTrackingEventDetailsTest.java | 16 +- .../dev/openfeature/sdk/NoOpProviderTest.java | 15 +- .../NoOpTransactionContextPropagatorTest.java | 7 +- .../sdk/NotImplementedException.java | 2 +- .../openfeature/sdk/OpenFeatureAPITest.java | 32 +- .../sdk/OpenFeatureClientTest.java | 26 +- .../sdk/ProviderEvaluationTest.java | 9 +- .../sdk/ProviderRepositoryTest.java | 149 ++- .../dev/openfeature/sdk/ProviderSpecTest.java | 112 +- .../sdk/ShutdownBehaviorSpecTest.java | 61 +- .../dev/openfeature/sdk/Specification.java | 1 + .../dev/openfeature/sdk/StructureTest.java | 28 +- ...LocalTransactionContextPropagatorTest.java | 9 +- .../dev/openfeature/sdk/TrackingSpecTest.java | 111 +- .../java/dev/openfeature/sdk/ValueTest.java | 49 +- .../sdk/benchmark/AllocationBenchmark.java | 24 +- .../sdk/benchmark/AllocationProfiler.java | 27 +- .../openfeature/sdk/e2e/EvaluationTest.java | 13 +- .../sdk/e2e/evaluation/StepDefinitions.java | 115 +- .../sdk/exceptions/ExceptionUtilsTest.java | 12 +- .../sdk/fixtures/HookFixtures.java | 5 +- .../sdk/fixtures/ProviderFixture.java | 17 +- .../sdk/hooks/logging/LoggingHookTest.java | 26 +- .../sdk/internal/ObjectUtilsTest.java | 14 +- .../sdk/internal/TriConsumerTest.java | 3 +- .../memory/InMemoryProviderTest.java | 56 +- .../testutils/FeatureProviderTestUtils.java | 18 +- .../sdk/testutils/TestEventsProvider.java | 16 +- .../sdk/testutils/TestFlagsUtils.java | 114 +- .../testutils/stubbing/ConditionStubber.java | 11 +- 115 files changed, 3267 insertions(+), 2208 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5bffb8ae --- /dev/null +++ b/.editorconfig @@ -0,0 +1,72 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +tab_width = 4 +trim_trailing_whitespace = true + +ij_continuation_indent_size = 8 + +[*.md] +max_line_length = off +trim_trailing_whitespace = false + +# Following the rules of the Google Java Style Guide. +# See https://google.github.io/styleguide/javaguide.html +[*.java] +max_line_length = 120 + +ij_java_do_not_wrap_after_single_annotation_in_parameter = true +ij_java_insert_inner_class_imports = false +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_packages_to_use_import_on_demand = unset +ij_java_imports_layout = $*,|,* +ij_java_doc_align_param_comments = true +ij_java_doc_align_exception_comments = true +ij_java_doc_add_p_tag_on_empty_lines = false +ij_java_doc_do_not_wrap_if_one_line = true +ij_java_doc_keep_empty_parameter_tag = false +ij_java_doc_keep_empty_throws_tag = false +ij_java_doc_keep_empty_return_tag = false +ij_java_doc_preserve_line_breaks = true +ij_java_doc_indent_on_continuation = true +ij_java_keep_control_statement_in_one_line = false +ij_java_keep_blank_lines_in_code = 1 +ij_java_align_multiline_parameters = false +ij_java_align_multiline_resources = false +ij_java_align_multiline_for = true +ij_java_space_before_array_initializer_left_brace = true +ij_java_call_parameters_wrap = normal +ij_java_method_parameters_wrap = normal +ij_java_extends_list_wrap = normal +ij_java_throws_keyword_wrap = normal +ij_java_method_call_chain_wrap = normal +ij_java_binary_operation_wrap = normal +ij_java_binary_operation_sign_on_next_line = true +ij_java_ternary_operation_wrap = normal +ij_java_ternary_operation_signs_on_next_line = true +ij_java_keep_simple_methods_in_one_line = true +ij_java_keep_simple_lambdas_in_one_line = true +ij_java_keep_simple_classes_in_one_line = true +ij_java_for_statement_wrap = normal +ij_java_array_initializer_wrap = normal +ij_java_wrap_comments = true +ij_java_if_brace_force = always +ij_java_do_while_brace_force = always +ij_java_while_brace_force = always +ij_java_for_brace_force = always +ij_java_space_after_closing_angle_bracket_in_type_argument = false + +[{*.json,*.json5}] +indent_size = 2 +tab_width = 2 +ij_smart_tabs = false + +[*.yaml] +indent_size = 2 +tab_width = 2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 84c9645b..71b881b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,38 @@ If you think we might be out of date with the spec, you can check that by invoki If you're adding tests to cover something in the spec, use the `@Specification` annotation like you see throughout the test suites. +## Code Styles + +### Overview +Our project follows strict code formatting standards to maintain consistency and readability across the codebase. We use [Spotless](https://github.com/diffplug/spotless) integrated with the [Palantir Java Format](https://github.com/palantir/palantir-java-format) for code formatting. + +**Spotless** ensures that all code complies with the formatting rules automatically, reducing style-related issues during code reviews. + +### How to Format Your Code +1. **Before Committing Changes:** + Run the Spotless plugin to format your code. This will apply the Palantir Java Format style: + ```bash + mvn spotless:apply + ``` + +2. **Verify Formatting:** + To check if your code adheres to the style guidelines without making changes: + ```bash + mvn spotless:check + ``` + + - If this command fails, your code does not follow the required formatting. Use `mvn spotless:apply` to fix it. + +### CI/CD Integration +Our Continuous Integration (CI) pipeline automatically checks code formatting using the Spotless plugin. Any code that does not pass the `spotless:check` step will cause the build to fail. + +### Best Practices +- Regularly run `mvn spotless:apply` during your work to ensure your code remains aligned with the standards. +- Configure your IDE (e.g., IntelliJ IDEA or Eclipse) to follow the Palantir Java format guidelines to reduce discrepancies during development. + +### Support +If you encounter issues with code formatting, please raise a GitHub issue or contact the maintainers. + ## End-to-End Tests The continuous integration runs a set of [gherkin e2e tests](https://github.com/open-feature/spec/blob/main/specification/assets/gherkin/evaluation.feature) using `InMemoryProvider`. diff --git a/checkstyle.xml b/checkstyle.xml index 2cf598c3..c5cb2999 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,7 +1,7 @@ + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> - + + - + @@ -34,9 +35,16 @@ + + - + @@ -52,7 +60,7 @@ - + @@ -69,48 +77,68 @@ + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/> + value="Consider using special escape sequence instead of octal value or Unicode escaped value."/> + - - + + value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/> - + + + value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF, + INTERFACE_DEF, LITERAL_CATCH, + LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, + LITERAL_WHILE, METHOD_DEF, + OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/> + + + value=" LITERAL_DEFAULT"/> + + + + + + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF"/> + + + + + + + + @@ -118,18 +146,35 @@ + + + value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, + BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAND, + LCURLY, LE, LITERAL_DO, LITERAL_ELSE, + LITERAL_FOR, LITERAL_IF, + LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, + NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, + SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, + TYPE_EXTENSION_AND"/> + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks + may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/> + value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> + + + + + + + + @@ -140,8 +185,9 @@ + value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF"/> @@ -155,13 +201,13 @@ - + - + @@ -174,22 +220,23 @@ + value="Package name ''{0}'' must match pattern ''{1}''."/> - + + value="Type name ''{0}'' must match pattern ''{1}''."/> + value="Member name ''{0}'' must match pattern ''{1}''."/> + value="Parameter name ''{0}'' must match pattern ''{1}''."/> @@ -199,38 +246,53 @@ + value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> + value="Local variable name ''{0}'' must match pattern ''{1}''."/> + + + + + value="Class type name ''{0}'' must match pattern ''{1}''."/> + + + + + + + + + value="Method type name ''{0}'' must match pattern ''{1}''."/> + value="Interface type name ''{0}'' must match pattern ''{1}''."/> + value="GenericWhitespace ''{0}'' is followed by whitespace."/> + value="GenericWhitespace ''{0}'' is preceded with whitespace."/> + value="GenericWhitespace ''{0}'' should followed by whitespace."/> + value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> @@ -240,44 +302,62 @@ + + + + + + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF, + PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF, + RECORD_COMPONENT_DEF"/> + + + + + + + + + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, + LABELED_STAT, METHOD_REF"/> + value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_DEF, DOT, ENUM_CONSTANT_DEF, + EXPR, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, + LITERAL_WHILE, METHOD_CALL, + METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL"/> + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, + LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF, + TYPE_EXTENSION_AND "/> - + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, + RECORD_DEF, COMPACT_CTOR_DEF"/> @@ -289,46 +369,83 @@ + value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/> - + + + + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/> - + + - + + + + + - - + + value="Method name ''{0}'' must match pattern ''{1}''."/> - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 6f6e14f6..e7fe58c6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,590 +1,640 @@ - 4.0.0 - - dev.openfeature - sdk - 1.13.0 - - - UTF-8 - 1.8 - ${maven.compiler.source} - 5.11.4 - - **/e2e/*.java - ${project.groupId}.${project.artifactId} - - - OpenFeature Java SDK - This is the Java implementation of OpenFeature, a vendor-agnostic abstraction library for evaluating feature flags. - https://openfeature.dev - - - abrahms - Justin Abrahms - eBay - https://justin.abrah.ms/ - - - - - Apache License 2.0 - https://www.apache.org/licenses/LICENSE-2.0 - - - - - scm:git:https://github.com/open-feature/java-sdk.git - scm:git:https://github.com/open-feature/java-sdk.git - https://github.com/open-feature/java-sdk - - - - - - org.projectlombok - lombok - 1.18.36 - provided - - - - - com.github.spotbugs - spotbugs - 4.8.6 - provided - - - - org.slf4j - slf4j-api - 2.0.16 - - - - - org.mockito - mockito-core - 4.11.0 - test - - - - org.assertj - assertj-core - 3.27.0 - test - - - - org.junit.jupiter - junit-jupiter - ${junit.jupiter.version} - test - - - - org.junit.jupiter - junit-jupiter-engine - ${junit.jupiter.version} - test - - - - org.junit.jupiter - junit-jupiter-api - ${junit.jupiter.version} - test - - - - org.junit.jupiter - junit-jupiter-params - ${junit.jupiter.version} - test - - - - org.junit.platform - junit-platform-suite - 1.11.4 - test - - - - io.cucumber - cucumber-java - test - - - - io.cucumber - cucumber-junit-platform-engine - test - - - - org.simplify4u - slf4j2-mock - 2.4.0 - test - - - - com.google.guava - guava - 33.4.0-jre - test - - - - org.awaitility - awaitility - 4.2.2 - test - - - - org.openjdk.jmh - jmh-core - 1.37 - test - - - - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + dev.openfeature + sdk + 1.13.0 + + + UTF-8 + 1.8 + ${maven.compiler.source} + 5.11.4 + + **/e2e/*.java + ${project.groupId}.${project.artifactId} + + + OpenFeature Java SDK + This is the Java implementation of OpenFeature, a vendor-agnostic abstraction library for evaluating + feature flags. + + https://openfeature.dev + + + abrahms + Justin Abrahms + eBay + https://justin.abrah.ms/ + + + + + Apache License 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + + + scm:git:https://github.com/open-feature/java-sdk.git + scm:git:https://github.com/open-feature/java-sdk.git + https://github.com/open-feature/java-sdk + + - - - - - net.bytebuddy - byte-buddy - 1.15.11 - test - - - - net.bytebuddy - byte-buddy-agent - 1.15.11 - test - - - - - io.cucumber - cucumber-bom - 7.20.1 - pom - import - - - - org.junit - junit-bom - 5.11.4 - pom - import - + + org.projectlombok + lombok + 1.18.36 + provided + - - - - - - - org.cyclonedx - cyclonedx-maven-plugin - 2.9.1 - - library - 1.3 - true - true - true - true - true - false - false - all - - - - package - - makeAggregateBom - - - - - - - maven-dependency-plugin - 3.8.1 - - - verify - - analyze - - - - - true - - com.github.spotbugs:* - org.junit* - org.simplify4u:slf4j2-mock* - - - com.google.guava* - io.cucumber* - org.junit* - com.google.code.findbugs* - com.github.spotbugs* - org.simplify4u:slf4j-mock-common:* - - - - - - maven-compiler-plugin - 3.13.0 - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.2 - - - ${surefireArgLine} - - - - ${testExclusions} - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.5.2 - - - ${surefireArgLine} - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.12 - - - - prepare-agent - - prepare-agent - - - - ${project.build.directory}/coverage-reports/jacoco-ut.exec - surefireArgLine - - - - - report - verify - - report - - - - ${project.build.directory}/coverage-reports/jacoco-ut.exec - ${project.reporting.outputDirectory}/jacoco-ut - - - - - jacoco-check - - check - - - ${project.build.directory}/coverage-reports/jacoco-ut.exec - - dev/openfeature/sdk/exceptions/** - - - - - PACKAGE - - - LINE - COVEREDRATIO - 0.80 - - - - - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.4.2 - - - - ${module-name} - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.26.0 - - - run-pmd - verify - - check - - - - - - - com.github.spotbugs - spotbugs-maven-plugin - 4.8.6.6 - - spotbugs-exclusions.xml - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.13.0 - - - - - - + + com.github.spotbugs spotbugs 4.8.6 - - - - - run-spotbugs - verify - - check - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.6.0 - - checkstyle.xml - UTF-8 - true - true - false - + provided + + + + org.slf4j + slf4j-api + 2.0.16 + + + + + org.mockito + mockito-core + 4.11.0 + test + + + + org.assertj + assertj-core + 3.27.0 + test + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + test + + + + org.junit.platform + junit-platform-suite + 1.11.4 + test + + + + io.cucumber + cucumber-java + test + + + + io.cucumber + cucumber-junit-platform-engine + test + + + + org.simplify4u + slf4j2-mock + 2.4.0 + test + + + + com.google.guava + guava + 33.4.0-jre + test + + + + org.awaitility + awaitility + 4.2.2 + test + + + + org.openjdk.jmh + jmh-core + 1.37 + test + + + + + - - com.puppycrawl.tools - checkstyle - 8.45.1 - + + + + + + net.bytebuddy + byte-buddy + 1.15.11 + test + + + + net.bytebuddy + byte-buddy-agent + 1.15.11 + test + + + + + io.cucumber + cucumber-bom + 7.20.1 + pom + import + + + + org.junit + junit-bom + 5.11.4 + pom + import + + - - - validate - validate - - check - - - - - - - - - - - deploy - - true - - + + - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 - true - - ossrh - https://s01.oss.sonatype.org/ - true - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.3.1 - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.11.2 - - true - all,-missing - - - - attach-javadocs - - jar - - - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.7 - - - sign-artifacts - install - - sign - - - - - + + org.cyclonedx + cyclonedx-maven-plugin + 2.9.1 + + library + 1.3 + true + true + true + true + true + false + false + all + + + + package + + makeAggregateBom + + + + - - - + + maven-dependency-plugin + 3.8.1 + + + verify + + analyze + + + + + true + + com.github.spotbugs:* + org.junit* + org.simplify4u:slf4j2-mock* + + + com.google.guava* + io.cucumber* + org.junit* + com.google.code.findbugs* + com.github.spotbugs* + org.simplify4u:slf4j-mock-common:* + + + - - benchmark - - - - pw.krejci - jmh-maven-plugin - 0.2.2 - - - - - - - e2e - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.5.0 - - - update-test-harness-submodule - validate - - exec - + + maven-compiler-plugin + 3.13.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + ${surefireArgLine} + + + + ${testExclusions} + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.2 + + + ${surefireArgLine} + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + prepare-agent + + + + ${project.build.directory}/coverage-reports/jacoco-ut.exec + surefireArgLine + + + + + report + verify + + report + + + + ${project.build.directory}/coverage-reports/jacoco-ut.exec + ${project.reporting.outputDirectory}/jacoco-ut + + + + + jacoco-check + + check + + + ${project.build.directory}/coverage-reports/jacoco-ut.exec + + dev/openfeature/sdk/exceptions/** + + + + + PACKAGE + + + LINE + COVEREDRATIO + 0.80 + + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + ${module-name} + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.26.0 + + + run-pmd + verify + + check + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.6 + + spotbugs-exclusions.xml + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.13.0 + + + + + + + com.github.spotbugs + spotbugs + 4.8.6 + + + + + run-spotbugs + verify + + check + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 - - git - - submodule - update - --init - spec - + checkstyle.xml + UTF-8 + true + true + false - - - copy-evaluation-gherkin-tests - validate - - exec - + + + com.puppycrawl.tools + checkstyle + 9.3 + + + + + validate + validate + + check + + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.30.0 - - cp - - spec/specification/assets/gherkin/evaluation.feature - src/test/resources/features/ - + + + + + + + + .gitattributes + .gitignore + + + + + + true + 4 + + + + + + + + + true + 4 + + + + + + + - - - + + + + check + + + + - - - - - - - ossrh - https://s01.oss.sonatype.org/content/repositories/snapshots - - + + + + + + deploy + + true + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + true + all,-missing + + + + + attach-javadocs + + jar + + + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + install + + sign + + + + + + + + + + + benchmark + + + + pw.krejci + jmh-maven-plugin + 0.2.2 + + + + + + + e2e + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.5.0 + + + update-test-harness-submodule + validate + + exec + + + + git + + submodule + update + --init + spec + + + + + copy-evaluation-gherkin-tests + validate + + exec + + + + cp + + spec/specification/assets/gherkin/evaluation.feature + src/test/resources/features/ + + + + + + + + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index 86fdde41..6c652114 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -1,10 +1,10 @@ package dev.openfeature.sdk; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Collections; -@SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" }) +@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"}) abstract class AbstractStructure implements Structure { protected final Map attributes; @@ -24,6 +24,7 @@ public boolean isEmpty() { /** * Returns an unmodifiable representation of the internal attribute map. + * * @return immutable map */ public Map asUnmodifiableMap() { @@ -37,14 +38,12 @@ public Map asUnmodifiableMap() { */ @Override public Map asObjectMap() { - return attributes - .entrySet() - .stream() + return attributes.entrySet().stream() // custom collector, workaround for Collectors.toMap in JDK8 // https://bugs.openjdk.org/browse/JDK-8148463 - .collect(HashMap::new, + .collect( + HashMap::new, (accumulated, entry) -> accumulated.put(entry.getKey(), convertValue(entry.getValue())), HashMap::putAll); } - } diff --git a/src/main/java/dev/openfeature/sdk/BaseEvaluation.java b/src/main/java/dev/openfeature/sdk/BaseEvaluation.java index ed6e9351..d4209d9b 100644 --- a/src/main/java/dev/openfeature/sdk/BaseEvaluation.java +++ b/src/main/java/dev/openfeature/sdk/BaseEvaluation.java @@ -2,29 +2,34 @@ /** * This is a common interface between the evaluation results that providers return and what is given to the end users. + * * @param The type of flag being evaluated. */ public interface BaseEvaluation { /** * Returns the resolved value of the evaluation. + * * @return {T} the resolve value */ T getValue(); /** * Returns an identifier for this value, if applicable. + * * @return {String} value identifier */ String getVariant(); /** * Describes how we came to the value that we're returning. + * * @return {Reason} */ String getReason(); /** * The error code, if applicable. Should only be set when the Reason is ERROR. + * * @return {ErrorCode} */ ErrorCode getErrorCode(); @@ -32,6 +37,7 @@ public interface BaseEvaluation { /** * The error message (usually from exception.getMessage()), if applicable. * Should only be set when the Reason is ERROR. + * * @return {String} */ String getErrorMessage(); diff --git a/src/main/java/dev/openfeature/sdk/BooleanHook.java b/src/main/java/dev/openfeature/sdk/BooleanHook.java index e9277766..3c178ca5 100644 --- a/src/main/java/dev/openfeature/sdk/BooleanHook.java +++ b/src/main/java/dev/openfeature/sdk/BooleanHook.java @@ -3,7 +3,7 @@ /** * An extension point which can run around flag resolution. They are intended to be used as a way to add custom logic * to the lifecycle of flag evaluation. - * + * * @see Hook */ public interface BooleanHook extends Hook { diff --git a/src/main/java/dev/openfeature/sdk/DoubleHook.java b/src/main/java/dev/openfeature/sdk/DoubleHook.java index 3ccf88b1..70d17b37 100644 --- a/src/main/java/dev/openfeature/sdk/DoubleHook.java +++ b/src/main/java/dev/openfeature/sdk/DoubleHook.java @@ -3,7 +3,7 @@ /** * An extension point which can run around flag resolution. They are intended to be used as a way to add custom logic * to the lifecycle of flag evaluation. - * + * * @see Hook */ public interface DoubleHook extends Hook { @@ -12,4 +12,4 @@ public interface DoubleHook extends Hook { default boolean supportsFlagValueType(FlagValueType flagValueType) { return FlagValueType.DOUBLE == flagValueType; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/openfeature/sdk/ErrorCode.java b/src/main/java/dev/openfeature/sdk/ErrorCode.java index 00451bdf..cb5798f3 100644 --- a/src/main/java/dev/openfeature/sdk/ErrorCode.java +++ b/src/main/java/dev/openfeature/sdk/ErrorCode.java @@ -2,6 +2,12 @@ @SuppressWarnings("checkstyle:MissingJavadocType") public enum ErrorCode { - PROVIDER_NOT_READY, FLAG_NOT_FOUND, PARSE_ERROR, TYPE_MISMATCH, TARGETING_KEY_MISSING, INVALID_CONTEXT, GENERAL, + PROVIDER_NOT_READY, + FLAG_NOT_FOUND, + PARSE_ERROR, + TYPE_MISMATCH, + TARGETING_KEY_MISSING, + INVALID_CONTEXT, + GENERAL, PROVIDER_FATAL } diff --git a/src/main/java/dev/openfeature/sdk/EvaluationContext.java b/src/main/java/dev/openfeature/sdk/EvaluationContext.java index 5b2a3311..84760c0d 100644 --- a/src/main/java/dev/openfeature/sdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/sdk/EvaluationContext.java @@ -28,12 +28,13 @@ public interface EvaluationContext extends Structure { * Recursively merges the overriding map into the base Value map. * The base map is mutated, the overriding map is not. * Null maps will cause no-op. - * + * * @param newStructure function to create the right structure(s) for Values - * @param base base map to merge - * @param overriding overriding map to merge + * @param base base map to merge + * @param overriding overriding map to merge */ - static void mergeMaps(Function, Structure> newStructure, + static void mergeMaps( + Function, Structure> newStructure, Map base, Map overriding) { @@ -46,12 +47,13 @@ static void mergeMaps(Function, Structure> newStructure, for (Entry overridingEntry : overriding.entrySet()) { String key = overridingEntry.getKey(); - if (overridingEntry.getValue().isStructure() && base.containsKey(key) && base.get(key).isStructure()) { + if (overridingEntry.getValue().isStructure() + && base.containsKey(key) + && base.get(key).isStructure()) { Structure mergedValue = base.get(key).asStructure(); Structure overridingValue = overridingEntry.getValue().asStructure(); Map newMap = mergedValue.asMap(); - mergeMaps(newStructure, newMap, - overridingValue.asUnmodifiableMap()); + mergeMaps(newStructure, newMap, overridingValue.asUnmodifiableMap()); base.put(key, new Value(newStructure.apply(newMap))); } else { base.put(key, overridingEntry.getValue()); diff --git a/src/main/java/dev/openfeature/sdk/EventBus.java b/src/main/java/dev/openfeature/sdk/EventBus.java index d635e9ba..16bd8340 100644 --- a/src/main/java/dev/openfeature/sdk/EventBus.java +++ b/src/main/java/dev/openfeature/sdk/EventBus.java @@ -6,38 +6,38 @@ * Interface for attaching event handlers. */ public interface EventBus { - + /** * Add a handler for the {@link ProviderEvent#PROVIDER_READY} event. * Shorthand for {@link #on(ProviderEvent, Consumer)} - * + * * @param handler behavior to add with this event * @return this */ T onProviderReady(Consumer handler); - + /** * Add a handler for the {@link ProviderEvent#PROVIDER_CONFIGURATION_CHANGED} event. * Shorthand for {@link #on(ProviderEvent, Consumer)} - * + * * @param handler behavior to add with this event * @return this */ T onProviderConfigurationChanged(Consumer handler); - + /** * Add a handler for the {@link ProviderEvent#PROVIDER_STALE} event. * Shorthand for {@link #on(ProviderEvent, Consumer)} - * + * * @param handler behavior to add with this event * @return this */ T onProviderError(Consumer handler); - + /** * Add a handler for the {@link ProviderEvent#PROVIDER_ERROR} event. * Shorthand for {@link #on(ProviderEvent, Consumer)} - * + * * @param handler behavior to add with this event * @return this */ @@ -45,18 +45,18 @@ public interface EventBus { /** * Add a handler for the specified {@link ProviderEvent}. - * - * @param event event type + * + * @param event event type * @param handler behavior to add with this event * @return this */ T on(ProviderEvent event, Consumer handler); - + /** * Remove the previously attached handler by reference. * If the handler doesn't exists, no-op. - * - * @param event event type + * + * @param event event type * @param handler to be removed * @return this */ diff --git a/src/main/java/dev/openfeature/sdk/EventDetails.java b/src/main/java/dev/openfeature/sdk/EventDetails.java index f2113eea..e32e6101 100644 --- a/src/main/java/dev/openfeature/sdk/EventDetails.java +++ b/src/main/java/dev/openfeature/sdk/EventDetails.java @@ -17,9 +17,7 @@ static EventDetails fromProviderEventDetails(ProviderEventDetails providerEventD } static EventDetails fromProviderEventDetails( - ProviderEventDetails providerEventDetails, - String providerName, - String domain) { + ProviderEventDetails providerEventDetails, String providerName, String domain) { return builder() .domain(domain) .providerName(providerName) diff --git a/src/main/java/dev/openfeature/sdk/EventProvider.java b/src/main/java/dev/openfeature/sdk/EventProvider.java index 84893bec..e9cdae55 100644 --- a/src/main/java/dev/openfeature/sdk/EventProvider.java +++ b/src/main/java/dev/openfeature/sdk/EventProvider.java @@ -2,7 +2,6 @@ import dev.openfeature.sdk.internal.TriConsumer; - /** * Abstract EventProvider. Providers must extend this class to support events. * Emit events with {@link #emit(ProviderEvent, ProviderEventDetails)}. Please diff --git a/src/main/java/dev/openfeature/sdk/EventSupport.java b/src/main/java/dev/openfeature/sdk/EventSupport.java index 085c8eb8..d3af4599 100644 --- a/src/main/java/dev/openfeature/sdk/EventSupport.java +++ b/src/main/java/dev/openfeature/sdk/EventSupport.java @@ -1,7 +1,5 @@ package dev.openfeature.sdk; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -13,6 +11,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import lombok.extern.slf4j.Slf4j; /** * Util class for storing and running handlers. @@ -35,74 +34,68 @@ class EventSupport { /** * Run all the event handlers associated with this domain. * If the domain is null, handlers attached to unnamed clients will run. - * + * * @param domain the domain to run event handlers for, or null * @param event the event type * @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(defaultClientUuid); // 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))); + .ifPresent(handlers -> handlers.forEach(handler -> runHandler(handler, eventDetails))); } /** * Run all the API (global) event handlers. - * + * * @param event the event type * @param eventDetails the event details */ public void runGlobalHandlers(ProviderEvent event, EventDetails eventDetails) { - globalHandlerStore.handlerMap.get(event) - .forEach(handler -> { - runHandler(handler, eventDetails); - }); + globalHandlerStore.handlerMap.get(event).forEach(handler -> { + runHandler(handler, eventDetails); + }); } /** * Add a handler for the specified domain, or all unnamed clients. - * - * @param domain the domain to add handlers for, or else unnamed - * @param event the event type - * @param handler the handler function to run + * + * @param domain the domain to add handlers for, or else unnamed + * @param event the event type + * @param handler the handler function to run */ public void addClientHandler(String domain, ProviderEvent event, Consumer handler) { - final String name = Optional.ofNullable(domain) - .orElse(defaultClientUuid); + final String name = Optional.ofNullable(domain).orElse(defaultClientUuid); // lazily create and cache a HandlerStore if it doesn't exist - HandlerStore store = Optional.ofNullable(this.handlerStores.get(name)) - .orElseGet(() -> { - HandlerStore newStore = new HandlerStore(); - this.handlerStores.put(name, newStore); - return newStore; - }); + HandlerStore store = Optional.ofNullable(this.handlerStores.get(name)).orElseGet(() -> { + HandlerStore newStore = new HandlerStore(); + this.handlerStores.put(name, newStore); + return newStore; + }); store.addHandler(event, handler); } /** * Remove a client event handler for the specified event type. - * - * @param domain the domain of the client handler to remove, or null to remove - * from unnamed clients - * @param event the event type - * @param handler the handler ref to be removed + * + * @param domain the domain of the client handler to remove, or null to remove + * from unnamed clients + * @param event the event type + * @param handler the handler ref to be removed */ public void removeClientHandler(String domain, ProviderEvent event, Consumer handler) { - domain = Optional.ofNullable(domain) - .orElse(defaultClientUuid); + domain = Optional.ofNullable(domain).orElse(defaultClientUuid); this.handlerStores.get(domain).removeHandler(event, handler); } /** * Add a global event handler of the specified event type. - * + * * @param event the event type * @param handler the handler to be added */ @@ -112,7 +105,7 @@ public void addGlobalHandler(ProviderEvent event, Consumer handler /** * Remove a global event handler for the specified event type. - * + * * @param event the event type * @param handler the handler ref to be removed */ @@ -122,7 +115,7 @@ public void removeGlobalHandler(ProviderEvent event, Consumer hand /** * Get all domain names for which we have event handlers registered. - * + * * @return set of domain names */ public Set getAllDomainNames() { @@ -131,7 +124,7 @@ public Set getAllDomainNames() { /** * Run the passed handler on the taskExecutor. - * + * * @param handler the handler to run * @param eventDetails the event details */ diff --git a/src/main/java/dev/openfeature/sdk/FeatureProvider.java b/src/main/java/dev/openfeature/sdk/FeatureProvider.java index 706818e8..4c630cb8 100644 --- a/src/main/java/dev/openfeature/sdk/FeatureProvider.java +++ b/src/main/java/dev/openfeature/sdk/FeatureProvider.java @@ -78,7 +78,5 @@ default ProviderState getState() { * @param context Evaluation context used in flag evaluation (Optional) * @param details Data pertinent to a particular tracking event (Optional) */ - default void track(String eventName, EvaluationContext context, TrackingEventDetails details) { - - } + default void track(String eventName, EvaluationContext context, TrackingEventDetails details) {} } diff --git a/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java b/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java index 973d4699..2c39ece6 100644 --- a/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java +++ b/src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java @@ -1,13 +1,13 @@ package dev.openfeature.sdk; import dev.openfeature.sdk.exceptions.OpenFeatureError; -import lombok.Getter; - import java.util.concurrent.atomic.AtomicBoolean; +import lombok.Getter; class FeatureProviderStateManager implements EventProviderListener { private final FeatureProvider delegate; private final AtomicBoolean isInitialized = new AtomicBoolean(); + @Getter private ProviderState state = ProviderState.NOT_READY; diff --git a/src/main/java/dev/openfeature/sdk/Features.java b/src/main/java/dev/openfeature/sdk/Features.java index ba25021a..1f0b73d4 100644 --- a/src/main/java/dev/openfeature/sdk/Features.java +++ b/src/main/java/dev/openfeature/sdk/Features.java @@ -15,8 +15,8 @@ public interface Features { FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx); - FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options); + FlagEvaluationDetails getBooleanDetails( + String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); String getStringValue(String key, String defaultValue); @@ -28,8 +28,8 @@ FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValu FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx); - FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options); + FlagEvaluationDetails getStringDetails( + String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); Integer getIntegerValue(String key, Integer defaultValue); @@ -41,8 +41,8 @@ FlagEvaluationDetails getStringDetails(String key, String defaultValue, FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx); - FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options); + FlagEvaluationDetails getIntegerDetails( + String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); Double getDoubleValue(String key, Double defaultValue); @@ -54,22 +54,19 @@ FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValu FlagEvaluationDetails getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx); - FlagEvaluationDetails getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options); + FlagEvaluationDetails getDoubleDetails( + String key, Double defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); Value getObjectValue(String key, Value defaultValue); Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx); - Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options); + Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); FlagEvaluationDetails getObjectDetails(String key, Value defaultValue); - FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, - EvaluationContext ctx); + FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, EvaluationContext ctx); - FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, - EvaluationContext ctx, - FlagEvaluationOptions options); + FlagEvaluationDetails getObjectDetails( + String key, Value defaultValue, EvaluationContext ctx, FlagEvaluationOptions options); } diff --git a/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java b/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java index 4562ea1e..f1697e30 100644 --- a/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java +++ b/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.java @@ -1,7 +1,6 @@ package dev.openfeature.sdk; import java.util.Optional; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -25,6 +24,7 @@ public class FlagEvaluationDetails implements BaseEvaluation { private String reason; private ErrorCode errorCode; private String errorMessage; + @Builder.Default private ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build(); @@ -44,8 +44,8 @@ public static FlagEvaluationDetails from(ProviderEvaluation providerEv .reason(providerEval.getReason()) .errorMessage(providerEval.getErrorMessage()) .errorCode(providerEval.getErrorCode()) - .flagMetadata( - Optional.ofNullable(providerEval.getFlagMetadata()).orElse(ImmutableMetadata.builder().build())) + .flagMetadata(Optional.ofNullable(providerEval.getFlagMetadata()) + .orElse(ImmutableMetadata.builder().build())) .build(); } } diff --git a/src/main/java/dev/openfeature/sdk/FlagEvaluationOptions.java b/src/main/java/dev/openfeature/sdk/FlagEvaluationOptions.java index 5fa1a93f..01ecb9b2 100644 --- a/src/main/java/dev/openfeature/sdk/FlagEvaluationOptions.java +++ b/src/main/java/dev/openfeature/sdk/FlagEvaluationOptions.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import lombok.Builder; import lombok.Singular; @@ -13,6 +12,7 @@ public class FlagEvaluationOptions { @Singular List hooks; + @Builder.Default Map hookHints = new HashMap<>(); } diff --git a/src/main/java/dev/openfeature/sdk/FlagValueType.java b/src/main/java/dev/openfeature/sdk/FlagValueType.java index 11d43afb..a8938d45 100644 --- a/src/main/java/dev/openfeature/sdk/FlagValueType.java +++ b/src/main/java/dev/openfeature/sdk/FlagValueType.java @@ -2,5 +2,9 @@ @SuppressWarnings("checkstyle:MissingJavadocType") public enum FlagValueType { - STRING, INTEGER, DOUBLE, OBJECT, BOOLEAN; + STRING, + INTEGER, + DOUBLE, + OBJECT, + BOOLEAN; } diff --git a/src/main/java/dev/openfeature/sdk/Hook.java b/src/main/java/dev/openfeature/sdk/Hook.java index 3856af26..9ca7e6b9 100644 --- a/src/main/java/dev/openfeature/sdk/Hook.java +++ b/src/main/java/dev/openfeature/sdk/Hook.java @@ -16,7 +16,7 @@ public interface Hook { * @param ctx Information about the particular flag evaluation * @param hints An immutable mapping of data for users to communicate to the hooks. * @return An optional {@link EvaluationContext}. If returned, it will be merged with the EvaluationContext - * instances from other hooks, the client and API. + * instances from other hooks, the client and API. */ default Optional before(HookContext ctx, Map hints) { return Optional.empty(); @@ -29,8 +29,7 @@ default Optional before(HookContext ctx, Map ctx, FlagEvaluationDetails details, Map hints) { - } + default void after(HookContext ctx, FlagEvaluationDetails details, Map hints) {} /** * Run when evaluation encounters an error. This will always run. Errors thrown will be swallowed. @@ -39,8 +38,7 @@ default void after(HookContext ctx, FlagEvaluationDetails details, Map ctx, Exception error, Map hints) { - } + default void error(HookContext ctx, Exception error, Map hints) {} /** * Run after flag evaluation, including any error processing. This will always run. Errors will be swallowed. @@ -48,8 +46,7 @@ default void error(HookContext ctx, Exception error, Map hint * @param ctx Information about the particular flag evaluation * @param hints An immutable mapping of data for users to communicate to the hooks. */ - default void finallyAfter(HookContext ctx, Map hints) { - } + default void finallyAfter(HookContext ctx, Map hints) {} default boolean supportsFlagValueType(FlagValueType flagValueType) { return true; diff --git a/src/main/java/dev/openfeature/sdk/HookContext.java b/src/main/java/dev/openfeature/sdk/HookContext.java index 5c9091b8..e14eeb64 100644 --- a/src/main/java/dev/openfeature/sdk/HookContext.java +++ b/src/main/java/dev/openfeature/sdk/HookContext.java @@ -10,28 +10,40 @@ * * @param the type for the flag being evaluated */ -@Value @Builder @With +@Value +@Builder +@With public class HookContext { @NonNull String flagKey; + @NonNull FlagValueType type; + @NonNull T defaultValue; + @NonNull EvaluationContext ctx; + ClientMetadata clientMetadata; Metadata providerMetadata; /** * Builds a {@link HookContext} instances from request data. - * @param key feature flag key - * @param type flag value type - * @param clientMetadata info on which client is calling + * + * @param key feature flag key + * @param type flag value type + * @param clientMetadata info on which client is calling * @param providerMetadata info on the provider - * @param ctx Evaluation Context for the request - * @param defaultValue Fallback value - * @param type that the flag is evaluating against + * @param ctx Evaluation Context for the request + * @param defaultValue Fallback value + * @param type that the flag is evaluating against * @return resulting context for hook */ - public static HookContext from(String key, FlagValueType type, ClientMetadata clientMetadata, - Metadata providerMetadata, EvaluationContext ctx, T defaultValue) { + public static HookContext from( + String key, + FlagValueType type, + ClientMetadata clientMetadata, + Metadata providerMetadata, + EvaluationContext ctx, + T defaultValue) { return HookContext.builder() .flagKey(key) .type(type) diff --git a/src/main/java/dev/openfeature/sdk/HookSupport.java b/src/main/java/dev/openfeature/sdk/HookSupport.java index f0216b25..95c8ff17 100644 --- a/src/main/java/dev/openfeature/sdk/HookSupport.java +++ b/src/main/java/dev/openfeature/sdk/HookSupport.java @@ -6,39 +6,44 @@ import java.util.Map; import java.util.Optional; import java.util.function.Consumer; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @RequiredArgsConstructor -@SuppressWarnings({ "unchecked", "rawtypes" }) +@SuppressWarnings({"unchecked", "rawtypes"}) class HookSupport { - public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List hooks, - Map hints) { + public EvaluationContext beforeHooks( + FlagValueType flagValueType, HookContext hookCtx, List hooks, Map hints) { return callBeforeHooks(flagValueType, hookCtx, hooks, hints); } - public void afterHooks(FlagValueType flagValueType, HookContext hookContext, FlagEvaluationDetails details, - List hooks, Map hints) { + public void afterHooks( + FlagValueType flagValueType, + HookContext hookContext, + FlagEvaluationDetails details, + List hooks, + Map hints) { executeHooksUnchecked(flagValueType, hooks, hook -> hook.after(hookContext, details, hints)); } - public void afterAllHooks(FlagValueType flagValueType, HookContext hookCtx, List hooks, - Map hints) { + public void afterAllHooks( + FlagValueType flagValueType, HookContext hookCtx, List hooks, Map hints) { executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, hints)); } - public void errorHooks(FlagValueType flagValueType, HookContext hookCtx, Exception e, List hooks, + public void errorHooks( + FlagValueType flagValueType, + HookContext hookCtx, + Exception e, + List hooks, Map hints) { executeHooks(flagValueType, hooks, "error", hook -> hook.error(hookCtx, e, hints)); } private void executeHooks( - FlagValueType flagValueType, List hooks, - String hookMethod, - Consumer> hookCode) { + FlagValueType flagValueType, List hooks, String hookMethod, Consumer> hookCode) { if (hooks != null) { for (Hook hook : hooks) { if (hook.supportsFlagValueType(flagValueType)) { @@ -53,15 +58,16 @@ private void executeChecked(Hook hook, Consumer> hookCode, String try { hookCode.accept(hook); } catch (Exception exception) { - log.error("Unhandled exception when running {} hook {} (only 'after' hooks should throw)", hookMethod, - hook.getClass(), exception); + log.error( + "Unhandled exception when running {} hook {} (only 'after' hooks should throw)", + hookMethod, + hook.getClass(), + exception); } } // after hooks can throw in order to do validation - private void executeHooksUnchecked( - FlagValueType flagValueType, List hooks, - Consumer> hookCode) { + private void executeHooksUnchecked(FlagValueType flagValueType, List hooks, Consumer> hookCode) { if (hooks != null) { for (Hook hook : hooks) { if (hook.supportsFlagValueType(flagValueType)) { @@ -71,16 +77,16 @@ private void executeHooksUnchecked( } } - private EvaluationContext callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx, - List hooks, Map hints) { + private EvaluationContext callBeforeHooks( + FlagValueType flagValueType, HookContext hookCtx, List hooks, Map hints) { // These traverse backwards from normal. List reversedHooks = new ArrayList<>(hooks); Collections.reverse(reversedHooks); EvaluationContext context = hookCtx.getCtx(); for (Hook hook : reversedHooks) { if (hook.supportsFlagValueType(flagValueType)) { - Optional optional = Optional.ofNullable(hook.before(hookCtx, hints)) - .orElse(Optional.empty()); + Optional optional = + Optional.ofNullable(hook.before(hookCtx, hints)).orElse(Optional.empty()); if (optional.isPresent()) { context = context.merge(optional.get()); } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index d0dae605..23a452e0 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -1,10 +1,9 @@ package dev.openfeature.sdk; +import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; import java.util.HashMap; import java.util.Map; import java.util.function.Function; - -import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; import lombok.ToString; import lombok.experimental.Delegate; @@ -88,15 +87,15 @@ public EvaluationContext merge(EvaluationContext overridingContext) { } Map attributes = this.asMap(); - EvaluationContext.mergeMaps(ImmutableStructure::new, attributes, - overridingContext.asUnmodifiableMap()); + EvaluationContext.mergeMaps(ImmutableStructure::new, attributes, overridingContext.asUnmodifiableMap()); return new ImmutableContext(attributes); } @SuppressWarnings("all") private static class DelegateExclusions { @ExcludeFromGeneratedCoverageReport - public Map merge(Function, Structure> newStructure, + public Map merge( + Function, Structure> newStructure, Map base, Map overriding) { return null; diff --git a/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java b/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java index 75f89847..c2b6f583 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableMetadata.java @@ -1,10 +1,9 @@ package dev.openfeature.sdk; -import lombok.EqualsAndHashCode; -import lombok.extern.slf4j.Slf4j; - import java.util.HashMap; import java.util.Map; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; /** * Immutable Flag Metadata representation. Implementation is backed by a {@link Map} and immutability is provided @@ -98,7 +97,6 @@ public T getValue(final String key, final Class type) { } } - /** * Obtain a builder for {@link ImmutableMetadata}. */ @@ -188,6 +186,5 @@ public ImmutableMetadataBuilder addBoolean(final String key, final Boolean value public ImmutableMetadata build() { return new ImmutableMetadata(this.metadata); } - } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java index 06c2551f..c47a49eb 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java @@ -6,7 +6,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; - import lombok.EqualsAndHashCode; import lombok.ToString; @@ -20,7 +19,7 @@ */ @ToString @EqualsAndHashCode -@SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" }) +@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"}) public final class ImmutableStructure extends AbstractStructure { /** @@ -72,13 +71,15 @@ private static Map copyAttributes(Map in) { private static Map copyAttributes(Map in, String targetingKey) { Map copy = new HashMap<>(); for (Entry entry : in.entrySet()) { - copy.put(entry.getKey(), - Optional.ofNullable(entry.getValue()).map((Value val) -> val.clone()).orElse(null)); + 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)); } return copy; } - } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableTrackingEventDetails.java b/src/main/java/dev/openfeature/sdk/ImmutableTrackingEventDetails.java index b535bb7d..6a499874 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableTrackingEventDetails.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableTrackingEventDetails.java @@ -1,12 +1,10 @@ package dev.openfeature.sdk; import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; -import lombok.experimental.Delegate; - import java.util.Map; import java.util.Optional; import java.util.function.Function; - +import lombok.experimental.Delegate; /** * ImmutableTrackingEventDetails represents data pertinent to a particular tracking event. @@ -40,13 +38,13 @@ public Optional getValue() { return Optional.ofNullable(value); } - @SuppressWarnings("all") private static class DelegateExclusions { @ExcludeFromGeneratedCoverageReport - public Map merge(Function, Structure> newStructure, - Map base, - Map overriding) { + public Map merge( + Function, Structure> newStructure, + Map base, + Map overriding) { return null; } } diff --git a/src/main/java/dev/openfeature/sdk/IntegerHook.java b/src/main/java/dev/openfeature/sdk/IntegerHook.java index ada05c78..971c2b3d 100644 --- a/src/main/java/dev/openfeature/sdk/IntegerHook.java +++ b/src/main/java/dev/openfeature/sdk/IntegerHook.java @@ -3,7 +3,7 @@ /** * An extension point which can run around flag resolution. They are intended to be used as a way to add custom logic * to the lifecycle of flag evaluation. - * + * * @see Hook */ public interface IntegerHook extends Hook { diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index ffab28af..7fda5806 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -1,11 +1,11 @@ package dev.openfeature.sdk; +import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; -import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.experimental.Delegate; @@ -96,7 +96,6 @@ public MutableContext setTargetingKey(String targetingKey) { return this; } - /** * Retrieve targetingKey from the context. */ @@ -122,8 +121,7 @@ public EvaluationContext merge(EvaluationContext overridingContext) { } Map attributes = this.asMap(); - EvaluationContext.mergeMaps( - MutableStructure::new, attributes, overridingContext.asUnmodifiableMap()); + EvaluationContext.mergeMaps(MutableStructure::new, attributes, overridingContext.asUnmodifiableMap()); return new MutableContext(attributes); } @@ -134,7 +132,8 @@ public EvaluationContext merge(EvaluationContext overridingContext) { private static class DelegateExclusions { @ExcludeFromGeneratedCoverageReport - public Map merge(Function, Structure> newStructure, + public Map merge( + Function, Structure> newStructure, Map base, Map overriding) { diff --git a/src/main/java/dev/openfeature/sdk/MutableStructure.java b/src/main/java/dev/openfeature/sdk/MutableStructure.java index 1246aa5e..a06e2f2d 100644 --- a/src/main/java/dev/openfeature/sdk/MutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/MutableStructure.java @@ -5,14 +5,13 @@ import java.util.List; import java.util.Map; import java.util.Set; - import lombok.EqualsAndHashCode; import lombok.ToString; /** - * {@link MutableStructure} represents a potentially nested object type which is used to represent + * {@link MutableStructure} represents a potentially nested object type which is used to represent * structured data. - * The MutableStructure is a Structure implementation which is not threadsafe, and whose attributes can + * The MutableStructure is a Structure implementation which is not threadsafe, and whose attributes can * be modified after instantiation. */ @ToString diff --git a/src/main/java/dev/openfeature/sdk/MutableTrackingEventDetails.java b/src/main/java/dev/openfeature/sdk/MutableTrackingEventDetails.java index 9f0de8c3..5ab8aa4a 100644 --- a/src/main/java/dev/openfeature/sdk/MutableTrackingEventDetails.java +++ b/src/main/java/dev/openfeature/sdk/MutableTrackingEventDetails.java @@ -1,15 +1,14 @@ package dev.openfeature.sdk; import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import lombok.experimental.Delegate; - import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.experimental.Delegate; /** * MutableTrackingEventDetails represents data pertinent to a particular tracking event. @@ -19,6 +18,7 @@ public class MutableTrackingEventDetails implements TrackingEventDetails { private final Number value; + @Delegate(excludes = MutableTrackingEventDetails.DelegateExclusions.class) private final MutableStructure structure; @@ -81,13 +81,13 @@ public MutableTrackingEventDetails add(String key, Value value) { return this; } - @SuppressWarnings("all") private static class DelegateExclusions { @ExcludeFromGeneratedCoverageReport - public Map merge(Function, Structure> newStructure, - Map base, - Map overriding) { + public Map merge( + Function, Structure> newStructure, + Map base, + Map overriding) { return null; } } diff --git a/src/main/java/dev/openfeature/sdk/NoOpProvider.java b/src/main/java/dev/openfeature/sdk/NoOpProvider.java index 2ad59c8b..e427b970 100644 --- a/src/main/java/dev/openfeature/sdk/NoOpProvider.java +++ b/src/main/java/dev/openfeature/sdk/NoOpProvider.java @@ -7,6 +7,7 @@ */ public class NoOpProvider implements FeatureProvider { public static final String PASSED_IN_DEFAULT = "Passed in default"; + @Getter private final String name = "No-op Provider"; @@ -58,8 +59,8 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, - EvaluationContext invocationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext invocationContext) { return ProviderEvaluation.builder() .value(defaultValue) .variant(PASSED_IN_DEFAULT) diff --git a/src/main/java/dev/openfeature/sdk/NoOpTransactionContextPropagator.java b/src/main/java/dev/openfeature/sdk/NoOpTransactionContextPropagator.java index a31b39b4..f0949b79 100644 --- a/src/main/java/dev/openfeature/sdk/NoOpTransactionContextPropagator.java +++ b/src/main/java/dev/openfeature/sdk/NoOpTransactionContextPropagator.java @@ -7,6 +7,7 @@ public class NoOpTransactionContextPropagator implements TransactionContextPropa /** * {@inheritDoc} + * * @return empty immutable context */ @Override @@ -18,7 +19,5 @@ public EvaluationContext getTransactionContext() { * {@inheritDoc} */ @Override - public void setTransactionContext(EvaluationContext evaluationContext) { - - } + public void setTransactionContext(EvaluationContext evaluationContext) {} } diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java index ad528568..9175a7cd 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java @@ -3,10 +3,13 @@ import dev.openfeature.sdk.exceptions.OpenFeatureError; import dev.openfeature.sdk.internal.AutoCloseableLock; import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock; -import lombok.extern.slf4j.Slf4j; - -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; +import lombok.extern.slf4j.Slf4j; /** * A global singleton which holds base configuration for the OpenFeature @@ -102,11 +105,7 @@ public Client getClient(String domain) { * @return a new client instance */ public Client getClient(String domain, String version) { - return new OpenFeatureClient( - this, - domain, - version - ); + return new OpenFeatureClient(this, domain, version); } /** @@ -196,7 +195,8 @@ public void setProvider(FeatureProvider provider) { */ public void setProvider(String domain, FeatureProvider provider) { try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { - providerRepository.setProvider(domain, + providerRepository.setProvider( + domain, provider, this::attachEventProvider, this::emitReady, @@ -229,7 +229,8 @@ public void setProviderAndWait(FeatureProvider provider) throws OpenFeatureError */ public void setProviderAndWait(String domain, FeatureProvider provider) throws OpenFeatureError { try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { - providerRepository.setProvider(domain, + providerRepository.setProvider( + domain, provider, this::attachEventProvider, this::emitReady, @@ -248,7 +249,10 @@ private void attachEventProvider(FeatureProvider provider) { } private void emitReady(FeatureProvider provider) { - runHandlersForProvider(provider, ProviderEvent.PROVIDER_READY, ProviderEventDetails.builder().build()); + runHandlersForProvider( + provider, + ProviderEvent.PROVIDER_READY, + ProviderEventDetails.builder().build()); } private void detachEventProvider(FeatureProvider provider) { @@ -258,7 +262,9 @@ private void detachEventProvider(FeatureProvider provider) { } private void emitError(FeatureProvider provider, OpenFeatureError exception) { - runHandlersForProvider(provider, ProviderEvent.PROVIDER_ERROR, + runHandlersForProvider( + provider, + ProviderEvent.PROVIDER_ERROR, ProviderEventDetails.builder().message(exception.getMessage()).build()); } @@ -394,8 +400,10 @@ void addHandler(String domain, ProviderEvent event, Consumer handl try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { // if the provider is in the state associated with event, run immediately if (Optional.ofNullable(this.providerRepository.getProviderState(domain)) - .orElse(ProviderState.READY).matchesEvent(event)) { - eventSupport.runHandler(handler, EventDetails.builder().domain(domain).build()); + .orElse(ProviderState.READY) + .matchesEvent(event)) { + eventSupport.runHandler( + handler, EventDetails.builder().domain(domain).build()); } eventSupport.addClientHandler(domain, event, handler); } @@ -415,8 +423,7 @@ FeatureProviderStateManager getFeatureProviderStateManager(String domain) { private void runHandlersForProvider(FeatureProvider provider, ProviderEvent event, ProviderEventDetails details) { try (AutoCloseableLock __ = lock.readLockAutoCloseable()) { - List domainsForProvider = providerRepository - .getDomainsForProvider(provider); + List domainsForProvider = providerRepository.getDomainsForProvider(provider); final String providerName = Optional.ofNullable(provider.getMetadata()) .map(metadata -> metadata.getName()) @@ -427,8 +434,8 @@ private void runHandlersForProvider(FeatureProvider provider, ProviderEvent even // run the handlers associated with domains for this provider domainsForProvider.forEach(domain -> { - eventSupport.runClientHandlers(domain, event, - EventDetails.fromProviderEventDetails(details, providerName, domain)); + eventSupport.runClientHandlers( + domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain)); }); if (providerRepository.isDefaultProvider(provider)) { @@ -437,8 +444,8 @@ private void runHandlersForProvider(FeatureProvider provider, ProviderEvent even Set boundDomains = providerRepository.getAllBoundDomains(); allDomainNames.removeAll(boundDomains); allDomainNames.forEach(domain -> { - eventSupport.runClientHandlers(domain, event, - EventDetails.fromProviderEventDetails(details, providerName, domain)); + eventSupport.runClientHandlers( + domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain)); }); } } diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index ea566e65..60f987b7 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -8,9 +8,6 @@ import dev.openfeature.sdk.internal.AutoCloseableLock; import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock; import dev.openfeature.sdk.internal.ObjectUtils; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -19,6 +16,8 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; /** * OpenFeature Client implementation. @@ -29,16 +28,24 @@ * @deprecated // TODO: eventually we will make this non-public. See issue #872 */ @Slf4j -@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable", - "unchecked", "rawtypes"}) +@SuppressWarnings({ + "PMD.DataflowAnomalyAnalysis", + "PMD.BeanMembersShouldSerialize", + "PMD.UnusedLocalVariable", + "unchecked", + "rawtypes" +}) @Deprecated() // TODO: eventually we will make this non-public. See issue #872 public class OpenFeatureClient implements Client { private final OpenFeatureAPI openfeatureApi; + @Getter private final String domain; + @Getter private final String version; + private final List clientHooks; private final HookSupport hookSupport; AutoCloseableReentrantReadWriteLock hooksLock = new AutoCloseableReentrantReadWriteLock(); @@ -53,14 +60,11 @@ public class OpenFeatureClient implements Client { * providers (used by observability tools). * @param version Version of the client (used by observability tools). * @deprecated Do not use this constructor. It's for internal use only. - * Clients created using it will not run event handlers. - * Use the OpenFeatureAPI's getClient factory method instead. + * Clients created using it will not run event handlers. + * Use the OpenFeatureAPI's getClient factory method instead. */ @Deprecated() // TODO: eventually we will make this non-public. See issue #872 - public OpenFeatureClient( - OpenFeatureAPI openFeatureAPI, - String domain, - String version) { + public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String domain, String version) { this.openfeatureApi = openFeatureAPI; this.domain = domain; this.version = version; @@ -85,7 +89,6 @@ public void track(String trackingEventName) { invokeTrack(trackingEventName, null, null); } - /** * {@inheritDoc} */ @@ -117,7 +120,6 @@ public void track(String trackingEventName, EvaluationContext context, TrackingE invokeTrack(trackingEventName, mergeEvaluationContext(context), details); } - /** * {@inheritDoc} */ @@ -160,10 +162,10 @@ public EvaluationContext getEvaluationContext() { } } - private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key, T defaultValue, - EvaluationContext ctx, FlagEvaluationOptions options) { - FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options, - () -> FlagEvaluationOptions.builder().build()); + private FlagEvaluationDetails evaluateFlag( + FlagValueType type, String key, T defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { + FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull( + options, () -> FlagEvaluationOptions.builder().build()); Map hints = Collections.unmodifiableMap(flagOptions.getHookHints()); FlagEvaluationDetails details = null; @@ -183,23 +185,31 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key throw new FatalError("provider is in an irrecoverable error state"); } - mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, - openfeatureApi.getHooks()); + mergedHooks = ObjectUtils.merge( + provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks()); - EvaluationContext mergedCtx = hookSupport.beforeHooks(type, HookContext.from(key, type, this.getMetadata(), - provider.getMetadata(), mergeEvaluationContext(ctx), defaultValue), mergedHooks, hints); + EvaluationContext mergedCtx = hookSupport.beforeHooks( + type, + HookContext.from( + key, + type, + this.getMetadata(), + provider.getMetadata(), + mergeEvaluationContext(ctx), + defaultValue), + mergedHooks, + hints); - afterHookContext = HookContext.from(key, type, this.getMetadata(), - provider.getMetadata(), mergedCtx, defaultValue); + afterHookContext = + HookContext.from(key, type, this.getMetadata(), provider.getMetadata(), mergedCtx, defaultValue); - ProviderEvaluation providerEval = (ProviderEvaluation) createProviderEvaluation(type, key, - defaultValue, provider, mergedCtx); + ProviderEvaluation providerEval = + (ProviderEvaluation) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx); details = FlagEvaluationDetails.from(providerEval, key); if (details.getErrorCode() != null) { - OpenFeatureError error = ExceptionUtils.instantiateErrorByErrorCode( - details.getErrorCode(), - details.getErrorMessage()); + OpenFeatureError error = + ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage()); enrichDetailsWithErrorDefaults(defaultValue, details); hookSupport.errorHooks(type, afterHookContext, error, mergedHooks, hints); } else { @@ -237,7 +247,8 @@ private static void validateTrackingEventName(String str) { } private void invokeTrack(String trackingEventName, EvaluationContext context, TrackingEventDetails details) { - openfeatureApi.getFeatureProviderStateManager(domain) + openfeatureApi + .getFeatureProviderStateManager(domain) .getProvider() .track(trackingEventName, mergeEvaluationContext(context), details); } @@ -262,8 +273,7 @@ private EvaluationContext mergeContextMaps(EvaluationContext... contexts) { Map merged = new HashMap<>(); for (EvaluationContext evaluationContext : contexts) { if (evaluationContext != null && !evaluationContext.isEmpty()) { - EvaluationContext.mergeMaps(ImmutableStructure::new, merged, - evaluationContext.asUnmodifiableMap()); + EvaluationContext.mergeMaps(ImmutableStructure::new, merged, evaluationContext.asUnmodifiableMap()); } } return new ImmutableContext(merged); @@ -302,8 +312,8 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte } @Override - public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public Boolean getBooleanValue( + String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return getBooleanDetails(key, defaultValue, ctx, options).getValue(); } @@ -314,12 +324,13 @@ public FlagEvaluationDetails getBooleanDetails(String key, Boolean defa @Override public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx) { - return getBooleanDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); + return getBooleanDetails( + key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override - public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public FlagEvaluationDetails getBooleanDetails( + String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options); } @@ -334,8 +345,8 @@ public String getStringValue(String key, String defaultValue, EvaluationContext } @Override - public String getStringValue(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public String getStringValue( + String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return getStringDetails(key, defaultValue, ctx, options).getValue(); } @@ -346,12 +357,13 @@ public FlagEvaluationDetails getStringDetails(String key, String default @Override public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx) { - return getStringDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); + return getStringDetails( + key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override - public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public FlagEvaluationDetails getStringDetails( + String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options); } @@ -366,8 +378,8 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte } @Override - public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public Integer getIntegerValue( + String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return getIntegerDetails(key, defaultValue, ctx, options).getValue(); } @@ -378,12 +390,13 @@ public FlagEvaluationDetails getIntegerDetails(String key, Integer defa @Override public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx) { - return getIntegerDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); + return getIntegerDetails( + key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override - public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public FlagEvaluationDetails getIntegerDetails( + String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options); } @@ -398,9 +411,10 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext } @Override - public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { - return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue(); + public Double getDoubleValue( + String key, Double defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { + return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options) + .getValue(); } @Override @@ -414,8 +428,8 @@ public FlagEvaluationDetails getDoubleDetails(String key, Double default } @Override - public FlagEvaluationDetails getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public FlagEvaluationDetails getDoubleDetails( + String key, Double defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options); } @@ -430,8 +444,7 @@ public Value getObjectValue(String key, Value defaultValue, EvaluationContext ct } @Override - public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return getObjectDetails(key, defaultValue, ctx, options).getValue(); } @@ -441,14 +454,14 @@ public FlagEvaluationDetails getObjectDetails(String key, Value defaultVa } @Override - public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, - EvaluationContext ctx) { - return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); + public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, EvaluationContext ctx) { + return getObjectDetails( + key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override - public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + public FlagEvaluationDetails getObjectDetails( + String key, Value defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options); } diff --git a/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java b/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java index 004f5cfd..39fddf24 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java +++ b/src/main/java/dev/openfeature/sdk/ProviderEvaluation.java @@ -20,6 +20,7 @@ public class ProviderEvaluation implements BaseEvaluation { private String reason; ErrorCode errorCode; private String errorMessage; + @Builder.Default private ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build(); } diff --git a/src/main/java/dev/openfeature/sdk/ProviderEvent.java b/src/main/java/dev/openfeature/sdk/ProviderEvent.java index dcefd606..47ac8c95 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderEvent.java +++ b/src/main/java/dev/openfeature/sdk/ProviderEvent.java @@ -4,5 +4,8 @@ * Provider event types. */ public enum ProviderEvent { - PROVIDER_READY, PROVIDER_CONFIGURATION_CHANGED, PROVIDER_ERROR, PROVIDER_STALE; + PROVIDER_READY, + PROVIDER_CONFIGURATION_CHANGED, + PROVIDER_ERROR, + PROVIDER_STALE; } diff --git a/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java b/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java index d927e429..f202574d 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java +++ b/src/main/java/dev/openfeature/sdk/ProviderEventDetails.java @@ -1,14 +1,14 @@ package dev.openfeature.sdk; import java.util.List; - import lombok.Data; import lombok.experimental.SuperBuilder; /** * The details of a particular event. */ -@Data @SuperBuilder(toBuilder = true) +@Data +@SuperBuilder(toBuilder = true) public class ProviderEventDetails { private List flagsChanged; private String message; diff --git a/src/main/java/dev/openfeature/sdk/ProviderRepository.java b/src/main/java/dev/openfeature/sdk/ProviderRepository.java index d3a5c1ac..bec86682 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderRepository.java +++ b/src/main/java/dev/openfeature/sdk/ProviderRepository.java @@ -2,8 +2,6 @@ import dev.openfeature.sdk.exceptions.GeneralError; import dev.openfeature.sdk.exceptions.OpenFeatureError; -import lombok.extern.slf4j.Slf4j; - import java.util.List; import java.util.Map; import java.util.Optional; @@ -16,14 +14,14 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.extern.slf4j.Slf4j; @Slf4j class ProviderRepository { private final Map stateManagers = new ConcurrentHashMap<>(); - private final AtomicReference defaultStateManger = new AtomicReference<>( - new FeatureProviderStateManager(new NoOpProvider()) - ); + private final AtomicReference defaultStateManger = + new AtomicReference<>(new FeatureProviderStateManager(new NoOpProvider())); private final ExecutorService taskExecutor = Executors.newCachedThreadPool(runnable -> { final Thread thread = new Thread(runnable); thread.setDaemon(true); @@ -96,7 +94,8 @@ public ProviderState getProviderState(String domain) { public List getDomainsForProvider(FeatureProvider provider) { return stateManagers.entrySet().stream() .filter(entry -> entry.getValue().hasSameProvider(provider)) - .map(Map.Entry::getKey).collect(Collectors.toList()); + .map(Map.Entry::getKey) + .collect(Collectors.toList()); } public Set getAllBoundDomains() { @@ -110,12 +109,13 @@ public boolean isDefaultProvider(FeatureProvider provider) { /** * Set the default provider. */ - public void setProvider(FeatureProvider provider, - Consumer afterSet, - Consumer afterInit, - Consumer afterShutdown, - BiConsumer afterError, - boolean waitForInit) { + public void setProvider( + FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { if (provider == null) { throw new IllegalArgumentException("Provider cannot be null"); } @@ -130,13 +130,14 @@ public void setProvider(FeatureProvider provider, * @param waitForInit When true, wait for initialization to finish, then returns. * Otherwise, initialization happens in the background. */ - public void setProvider(String domain, - FeatureProvider provider, - Consumer afterSet, - Consumer afterInit, - Consumer afterShutdown, - BiConsumer afterError, - boolean waitForInit) { + public void setProvider( + String domain, + FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { if (provider == null) { throw new IllegalArgumentException("Provider cannot be null"); } @@ -146,13 +147,14 @@ public void setProvider(String domain, prepareAndInitializeProvider(domain, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit); } - private void prepareAndInitializeProvider(String domain, - FeatureProvider newProvider, - Consumer afterSet, - Consumer afterInit, - Consumer afterShutdown, - BiConsumer afterError, - boolean waitForInit) { + private void prepareAndInitializeProvider( + String domain, + FeatureProvider newProvider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { final FeatureProviderStateManager newStateManager; final FeatureProviderStateManager oldStateManager; @@ -195,11 +197,12 @@ private FeatureProviderStateManager getExistingStateManagerForProvider(FeaturePr return null; } - private void initializeProvider(FeatureProviderStateManager newManager, - Consumer afterInit, - Consumer afterShutdown, - BiConsumer afterError, - FeatureProviderStateManager oldManager) { + private void initializeProvider( + FeatureProviderStateManager newManager, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + FeatureProviderStateManager oldManager) { try { if (ProviderState.NOT_READY.equals(newManager.getState())) { newManager.initialize(OpenFeatureAPI.getInstance().getEvaluationContext()); @@ -210,15 +213,13 @@ private void initializeProvider(FeatureProviderStateManager newManager, log.error( "Exception when initializing feature provider {}", newManager.getProvider().getClass().getName(), - e - ); + e); afterError.accept(newManager.getProvider(), e); } catch (Exception e) { log.error( "Exception when initializing feature provider {}", newManager.getProvider().getClass().getName(), - e - ); + e); afterError.accept(newManager.getProvider(), new GeneralError(e)); } } @@ -238,7 +239,8 @@ private void shutDownOld(FeatureProviderStateManager oldManager, Consumer { diff --git a/src/main/java/dev/openfeature/sdk/Structure.java b/src/main/java/dev/openfeature/sdk/Structure.java index f2fdc53e..bfb74499 100644 --- a/src/main/java/dev/openfeature/sdk/Structure.java +++ b/src/main/java/dev/openfeature/sdk/Structure.java @@ -1,23 +1,23 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.exceptions.ValueNotConvertableError; +import static dev.openfeature.sdk.Value.objectToValue; +import dev.openfeature.sdk.exceptions.ValueNotConvertableError; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import static dev.openfeature.sdk.Value.objectToValue; - /** - * {@link Structure} represents a potentially nested object type which is used to represent + * {@link Structure} represents a potentially nested object type which is used to represent * structured data. */ @SuppressWarnings("PMD.BeanMembersShouldSerialize") public interface Structure { - + /** * Boolean indicating if this structure is empty. + * * @return boolean for emptiness */ boolean isEmpty(); @@ -51,7 +51,6 @@ public interface Structure { */ Map asUnmodifiableMap(); - /** * Get all values, with as a map of Object. * @@ -93,20 +92,15 @@ default Object convertValue(Value value) { } if (value.isList()) { - return value.asList() - .stream() - .map(this::convertValue) - .collect(Collectors.toList()); + return value.asList().stream().map(this::convertValue).collect(Collectors.toList()); } if (value.isStructure()) { Structure s = value.asStructure(); - return s.asUnmodifiableMap() - .entrySet() - .stream() - .collect(HashMap::new, - (accumulated, entry) -> accumulated.put(entry.getKey(), - convertValue(entry.getValue())), + return s.asUnmodifiableMap().entrySet().stream() + .collect( + HashMap::new, + (accumulated, entry) -> accumulated.put(entry.getKey(), convertValue(entry.getValue())), HashMap::putAll); } @@ -121,9 +115,9 @@ default Object convertValue(Value value) { */ static Structure mapToStructure(Map map) { return new MutableStructure(map.entrySet().stream() - .collect(HashMap::new, - (accumulated, entry) -> accumulated.put(entry.getKey(), - objectToValue(entry.getValue())), + .collect( + HashMap::new, + (accumulated, entry) -> accumulated.put(entry.getKey(), objectToValue(entry.getValue())), HashMap::putAll)); } } diff --git a/src/main/java/dev/openfeature/sdk/TrackingEventDetails.java b/src/main/java/dev/openfeature/sdk/TrackingEventDetails.java index 76b20fbb..15b0208c 100644 --- a/src/main/java/dev/openfeature/sdk/TrackingEventDetails.java +++ b/src/main/java/dev/openfeature/sdk/TrackingEventDetails.java @@ -3,5 +3,4 @@ /** * Data pertinent to a particular tracking event. */ -public interface TrackingEventDetails extends Structure { -} +public interface TrackingEventDetails extends Structure {} diff --git a/src/main/java/dev/openfeature/sdk/Value.java b/src/main/java/dev/openfeature/sdk/Value.java index 7464ce5a..05e538e5 100644 --- a/src/main/java/dev/openfeature/sdk/Value.java +++ b/src/main/java/dev/openfeature/sdk/Value.java @@ -1,17 +1,16 @@ package dev.openfeature.sdk; +import static dev.openfeature.sdk.Structure.mapToStructure; + +import dev.openfeature.sdk.exceptions.TypeMismatchError; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.stream.Collectors; - -import dev.openfeature.sdk.exceptions.TypeMismatchError; import lombok.EqualsAndHashCode; import lombok.SneakyThrows; import lombok.ToString; -import static dev.openfeature.sdk.Structure.mapToStructure; - /** * Values serve as a generic return type for structure data from providers. * Providers may deal in JSON, protobuf, XML or some other data-interchange format. @@ -37,33 +36,34 @@ public Value() { /** * Construct a new Value with an Object. + * * @param value to be wrapped. * @throws InstantiationException if value is not a valid type - * (boolean, string, int, double, list, structure, instant) + * (boolean, string, int, double, list, structure, instant) */ public Value(Object value) throws InstantiationException { this.innerObject = value; if (!this.isNull() - && !this.isBoolean() - && !this.isString() - && !this.isNumber() - && !this.isStructure() - && !this.isList() - && !this.isInstant()) { + && !this.isBoolean() + && !this.isString() + && !this.isNumber() + && !this.isStructure() + && !this.isList() + && !this.isInstant()) { throw new InstantiationException("Invalid value type: " + value.getClass()); } } public Value(Value value) { - this.innerObject = value.innerObject; + this.innerObject = value.innerObject; } public Value(Boolean value) { - this.innerObject = value; + this.innerObject = value; } public Value(String value) { - this.innerObject = value; + this.innerObject = value; } public Value(Integer value) { @@ -71,69 +71,69 @@ public Value(Integer value) { } public Value(Double value) { - this.innerObject = value; + this.innerObject = value; } public Value(Structure value) { - this.innerObject = value; + this.innerObject = value; } public Value(List value) { - this.innerObject = value; + this.innerObject = value; } public Value(Instant value) { this.innerObject = value; } - /** + /** * Check if this Value represents null. - * + * * @return boolean */ public boolean isNull() { return this.innerObject == null; } - /** + /** * Check if this Value represents a Boolean. - * + * * @return boolean */ public boolean isBoolean() { return this.innerObject instanceof Boolean; } - /** + /** * Check if this Value represents a String. - * + * * @return boolean */ public boolean isString() { return this.innerObject instanceof String; } - /** + /** * Check if this Value represents a numeric value. - * + * * @return boolean */ public boolean isNumber() { return this.innerObject instanceof Number; } - /** + /** * Check if this Value represents a Structure. - * + * * @return boolean */ public boolean isStructure() { return this.innerObject instanceof Structure; } - - /** + + /** * Check if this Value represents a List of Values. - * + * * @return boolean */ public boolean isList() { @@ -155,87 +155,88 @@ public boolean isList() { return true; } - /** + /** * Check if this Value represents an Instant. - * + * * @return boolean */ public boolean isInstant() { return this.innerObject instanceof Instant; } - - /** + + /** * Retrieve the underlying Boolean value, or null. - * + * * @return Boolean */ - @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", - justification = "This is not a plain true/false method. It's understood it can return null.") + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "NP_BOOLEAN_RETURN_NULL", + justification = "This is not a plain true/false method. It's understood it can return null.") public Boolean asBoolean() { if (this.isBoolean()) { - return (Boolean)this.innerObject; + return (Boolean) this.innerObject; } return null; } - - /** + + /** * Retrieve the underlying object. - * + * * @return Object */ public Object asObject() { return this.innerObject; } - /** + /** * Retrieve the underlying String value, or null. - * + * * @return String */ public String asString() { if (this.isString()) { - return (String)this.innerObject; + return (String) this.innerObject; } return null; } - /** + /** * Retrieve the underlying numeric value as an Integer, or null. * If the value is not an integer, it will be rounded using Math.round(). - * + * * @return Integer */ public Integer asInteger() { if (this.isNumber() && !this.isNull()) { - return ((Number)this.innerObject).intValue(); + return ((Number) this.innerObject).intValue(); } return null; } - - /** + + /** * Retrieve the underlying numeric value as a Double, or null. - * + * * @return Double */ public Double asDouble() { if (this.isNumber() && !isNull()) { - return ((Number)this.innerObject).doubleValue(); + return ((Number) this.innerObject).doubleValue(); } return null; } - /** + /** * Retrieve the underlying Structure value, or null. - * + * * @return Structure */ public Structure asStructure() { if (this.isStructure()) { - return (Structure)this.innerObject; + return (Structure) this.innerObject; } return null; } - + /** * Retrieve the underlying List value, or null. * @@ -249,14 +250,14 @@ public List asList() { return null; } - /** + /** * Retrieve the underlying Instant value, or null. - * + * * @return Instant */ public Instant asInstant() { if (this.isInstant()) { - return (Instant)this.innerObject; + return (Instant) this.innerObject; } return null; } @@ -305,9 +306,8 @@ public static Value objectToValue(Object object) { } else if (object instanceof Structure) { return new Value((Structure) object); } else if (object instanceof List) { - return new Value(((List) object).stream() - .map(o -> objectToValue(o)) - .collect(Collectors.toList())); + return new Value( + ((List) object).stream().map(o -> objectToValue(o)).collect(Collectors.toList())); } else if (object instanceof Instant) { return new Value((Instant) object); } else if (object instanceof Map) { diff --git a/src/main/java/dev/openfeature/sdk/exceptions/ExceptionUtils.java b/src/main/java/dev/openfeature/sdk/exceptions/ExceptionUtils.java index 28c7cd71..f44dcea2 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/ExceptionUtils.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/ExceptionUtils.java @@ -9,7 +9,8 @@ public class ExceptionUtils { /** * Creates an Error for the specific error code. - * @param errorCode the ErrorCode to use + * + * @param errorCode the ErrorCode to use * @param errorMessage the error message to include in the returned error * @return the specific OpenFeatureError for the errorCode */ diff --git a/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java b/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java index d50d1a42..93d11dc8 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/FatalError.java @@ -8,6 +8,7 @@ @StandardException public class FatalError extends OpenFeatureError { private static final long serialVersionUID = 1L; + @Getter private final ErrorCode errorCode = ErrorCode.PROVIDER_FATAL; -} \ No newline at end of file +} diff --git a/src/main/java/dev/openfeature/sdk/exceptions/FlagNotFoundError.java b/src/main/java/dev/openfeature/sdk/exceptions/FlagNotFoundError.java index 7c88ebb4..e60ce416 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/FlagNotFoundError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/FlagNotFoundError.java @@ -8,7 +8,7 @@ @StandardException public class FlagNotFoundError extends OpenFeatureErrorWithoutStacktrace { private static final long serialVersionUID = 1L; + @Getter private final ErrorCode errorCode = ErrorCode.FLAG_NOT_FOUND; - } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/GeneralError.java b/src/main/java/dev/openfeature/sdk/exceptions/GeneralError.java index d7256c3f..e89bd1cb 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/GeneralError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/GeneralError.java @@ -8,6 +8,7 @@ @StandardException public class GeneralError extends OpenFeatureError { private static final long serialVersionUID = 1L; + @Getter private final ErrorCode errorCode = ErrorCode.GENERAL; } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/InvalidContextError.java b/src/main/java/dev/openfeature/sdk/exceptions/InvalidContextError.java index e70c3efe..34e5505e 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/InvalidContextError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/InvalidContextError.java @@ -11,6 +11,6 @@ public class InvalidContextError extends OpenFeatureError { private static final long serialVersionUID = 1L; - @Getter private final ErrorCode errorCode = ErrorCode.INVALID_CONTEXT; - + @Getter + private final ErrorCode errorCode = ErrorCode.INVALID_CONTEXT; } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/ParseError.java b/src/main/java/dev/openfeature/sdk/exceptions/ParseError.java index ac8fca87..dd2b6438 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/ParseError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/ParseError.java @@ -11,6 +11,6 @@ public class ParseError extends OpenFeatureError { private static final long serialVersionUID = 1L; - @Getter private final ErrorCode errorCode = ErrorCode.PARSE_ERROR; - + @Getter + private final ErrorCode errorCode = ErrorCode.PARSE_ERROR; } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/ProviderNotReadyError.java b/src/main/java/dev/openfeature/sdk/exceptions/ProviderNotReadyError.java index 0416eae2..5498b6f1 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/ProviderNotReadyError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/ProviderNotReadyError.java @@ -8,5 +8,7 @@ @StandardException public class ProviderNotReadyError extends OpenFeatureErrorWithoutStacktrace { private static final long serialVersionUID = 1L; - @Getter private final ErrorCode errorCode = ErrorCode.PROVIDER_NOT_READY; + + @Getter + private final ErrorCode errorCode = ErrorCode.PROVIDER_NOT_READY; } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/TargetingKeyMissingError.java b/src/main/java/dev/openfeature/sdk/exceptions/TargetingKeyMissingError.java index 12437dc7..05924ec7 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/TargetingKeyMissingError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/TargetingKeyMissingError.java @@ -11,6 +11,6 @@ public class TargetingKeyMissingError extends OpenFeatureError { private static final long serialVersionUID = 1L; - @Getter private final ErrorCode errorCode = ErrorCode.TARGETING_KEY_MISSING; - + @Getter + private final ErrorCode errorCode = ErrorCode.TARGETING_KEY_MISSING; } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/TypeMismatchError.java b/src/main/java/dev/openfeature/sdk/exceptions/TypeMismatchError.java index 9d88922c..13bf48bb 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/TypeMismatchError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/TypeMismatchError.java @@ -12,6 +12,6 @@ public class TypeMismatchError extends OpenFeatureError { private static final long serialVersionUID = 1L; - @Getter private final ErrorCode errorCode = ErrorCode.TYPE_MISMATCH; - + @Getter + private final ErrorCode errorCode = ErrorCode.TYPE_MISMATCH; } diff --git a/src/main/java/dev/openfeature/sdk/exceptions/ValueNotConvertableError.java b/src/main/java/dev/openfeature/sdk/exceptions/ValueNotConvertableError.java index a681b5ef..13d46c8b 100644 --- a/src/main/java/dev/openfeature/sdk/exceptions/ValueNotConvertableError.java +++ b/src/main/java/dev/openfeature/sdk/exceptions/ValueNotConvertableError.java @@ -10,6 +10,7 @@ @StandardException public class ValueNotConvertableError extends OpenFeatureError { private static final long serialVersionUID = 1L; + @Getter private final ErrorCode errorCode = ErrorCode.GENERAL; } diff --git a/src/main/java/dev/openfeature/sdk/hooks/logging/LoggingHook.java b/src/main/java/dev/openfeature/sdk/hooks/logging/LoggingHook.java index 716168f0..7465aa77 100644 --- a/src/main/java/dev/openfeature/sdk/hooks/logging/LoggingHook.java +++ b/src/main/java/dev/openfeature/sdk/hooks/logging/LoggingHook.java @@ -17,8 +17,9 @@ * Flag evaluation data is logged at debug and error in before/after stages and error stages, respectively. */ @Slf4j -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", - justification = "we can ignore return values of chainables (builders) here") +@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "RV_RETURN_VALUE_IGNORED", + justification = "we can ignore return values of chainables (builders) here") public class LoggingHook implements Hook { static final String DOMAIN_KEY = "domain"; @@ -43,6 +44,7 @@ public LoggingHook() { /** * Construct a new LoggingHook. + * * @param includeEvaluationContext include a serialized evaluation context in the log message (defaults to false) */ public LoggingHook(boolean includeEvaluationContext) { @@ -59,8 +61,8 @@ public Optional before(HookContext hookContext, Map hookContext, FlagEvaluationDetails details, - Map hints) { + public void after( + HookContext hookContext, FlagEvaluationDetails details, Map hints) { LoggingEventBuilder builder = log.atDebug() .addKeyValue(REASON_KEY, details.getReason()) .addKeyValue(VARIANT_KEY, details.getVariant()) @@ -71,8 +73,7 @@ public void after(HookContext hookContext, FlagEvaluationDetails @Override public void error(HookContext hookContext, Exception error, Map hints) { - LoggingEventBuilder builder = log.atError() - .addKeyValue(ERROR_MESSAGE_KEY, error.getMessage()); + LoggingEventBuilder builder = log.atError().addKeyValue(ERROR_MESSAGE_KEY, error.getMessage()); addCommonProps(builder, hookContext); ErrorCode errorCode = error instanceof OpenFeatureError ? ((OpenFeatureError) error).getErrorCode() : null; builder.addKeyValue(ERROR_CODE_KEY, errorCode); @@ -81,7 +82,8 @@ public void error(HookContext hookContext, Exception error, Map hookContext) { builder.addKeyValue(DOMAIN_KEY, hookContext.getClientMetadata().getDomain()) - .addKeyValue(PROVIDER_NAME_KEY, hookContext.getProviderMetadata().getName()) + .addKeyValue( + PROVIDER_NAME_KEY, hookContext.getProviderMetadata().getName()) .addKeyValue(FLAG_KEY_KEY, hookContext.getFlagKey()) .addKeyValue(DEFAULT_VALUE_KEY, hookContext.getDefaultValue()); diff --git a/src/main/java/dev/openfeature/sdk/internal/AutoCloseableLock.java b/src/main/java/dev/openfeature/sdk/internal/AutoCloseableLock.java index bf2f3042..2569aaf3 100644 --- a/src/main/java/dev/openfeature/sdk/internal/AutoCloseableLock.java +++ b/src/main/java/dev/openfeature/sdk/internal/AutoCloseableLock.java @@ -2,7 +2,7 @@ @SuppressWarnings("checkstyle:MissingJavadocType") public interface AutoCloseableLock extends AutoCloseable { - + /** * Override the exception in AutoClosable. */ diff --git a/src/main/java/dev/openfeature/sdk/internal/AutoCloseableReentrantReadWriteLock.java b/src/main/java/dev/openfeature/sdk/internal/AutoCloseableReentrantReadWriteLock.java index 92827ef6..1e94e3ae 100644 --- a/src/main/java/dev/openfeature/sdk/internal/AutoCloseableReentrantReadWriteLock.java +++ b/src/main/java/dev/openfeature/sdk/internal/AutoCloseableReentrantReadWriteLock.java @@ -10,6 +10,7 @@ public class AutoCloseableReentrantReadWriteLock extends ReentrantReadWriteLock /** * Get the single write lock as an AutoCloseableLock. + * * @return unlock method ref */ public AutoCloseableLock writeLockAutoCloseable() { @@ -19,10 +20,11 @@ public AutoCloseableLock writeLockAutoCloseable() { /** * Get the multi read lock as an AutoCloseableLock. + * * @return unlock method ref */ public AutoCloseableLock readLockAutoCloseable() { this.readLock().lock(); return this.readLock()::unlock; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/openfeature/sdk/internal/ExcludeFromGeneratedCoverageReport.java b/src/main/java/dev/openfeature/sdk/internal/ExcludeFromGeneratedCoverageReport.java index e25f1260..f91fb815 100644 --- a/src/main/java/dev/openfeature/sdk/internal/ExcludeFromGeneratedCoverageReport.java +++ b/src/main/java/dev/openfeature/sdk/internal/ExcludeFromGeneratedCoverageReport.java @@ -1,14 +1,13 @@ package dev.openfeature.sdk.internal; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.ElementType; +import java.lang.annotation.Target; /** * JaCoCo ignores coverage of methods annotated with any annotation with "generated" in the name. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface ExcludeFromGeneratedCoverageReport { -} \ No newline at end of file +public @interface ExcludeFromGeneratedCoverageReport {} diff --git a/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java b/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java index 9e5dcf61..b367820c 100644 --- a/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java +++ b/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; - import lombok.experimental.UtilityClass; @SuppressWarnings("checkstyle:MissingJavadocType") @@ -13,9 +12,10 @@ public class ObjectUtils { /** * If the source param is null, return the default value. - * @param source maybe null object + * + * @param source maybe null object * @param defaultValue thing to use if source is null - * @param list type + * @param list type * @return resulting object */ public static List defaultIfNull(List source, Supplier> defaultValue) { @@ -27,10 +27,11 @@ public static List defaultIfNull(List source, Supplier> defaul /** * If the source param is null, return the default value. - * @param source maybe null object + * + * @param source maybe null object * @param defaultValue thing to use if source is null - * @param map key type - * @param map value type + * @param map key type + * @param map value type * @return resulting map */ public static Map defaultIfNull(Map source, Supplier> defaultValue) { @@ -42,9 +43,10 @@ public static Map defaultIfNull(Map source, Supplier type + * @param type * @return resulting object */ public static T defaultIfNull(T source, Supplier defaultValue) { @@ -56,8 +58,9 @@ public static T defaultIfNull(T source, Supplier defaultValue) { /** * Concatenate a bunch of lists. + * * @param sources bunch of lists. - * @param list type + * @param list type * @return resulting object */ @SafeVarargs diff --git a/src/main/java/dev/openfeature/sdk/internal/TriConsumer.java b/src/main/java/dev/openfeature/sdk/internal/TriConsumer.java index 723f4aeb..83130780 100644 --- a/src/main/java/dev/openfeature/sdk/internal/TriConsumer.java +++ b/src/main/java/dev/openfeature/sdk/internal/TriConsumer.java @@ -4,7 +4,7 @@ /** * Like {@link java.util.function.BiConsumer} but with 3 params. - * + * * @see java.util.function.BiConsumer */ @FunctionalInterface @@ -35,4 +35,4 @@ default TriConsumer andThen(TriConsumer after) { after.accept(t, u, v); }; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/openfeature/sdk/providers/memory/ContextEvaluator.java b/src/main/java/dev/openfeature/sdk/providers/memory/ContextEvaluator.java index 02fa323c..715868be 100644 --- a/src/main/java/dev/openfeature/sdk/providers/memory/ContextEvaluator.java +++ b/src/main/java/dev/openfeature/sdk/providers/memory/ContextEvaluator.java @@ -4,6 +4,7 @@ /** * Context evaluator - use for resolving flag according to evaluation context, for handling targeting. + * * @param expected value type */ public interface ContextEvaluator { diff --git a/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java b/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java index 8cfe85c9..61778d85 100644 --- a/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java +++ b/src/main/java/dev/openfeature/sdk/providers/memory/Flag.java @@ -1,12 +1,11 @@ package dev.openfeature.sdk.providers.memory; +import java.util.Map; import lombok.Builder; import lombok.Getter; import lombok.Singular; import lombok.ToString; -import java.util.Map; - /** * Flag representation for the in-memory provider. */ @@ -16,6 +15,7 @@ public class Flag { @Singular private Map variants; + private String defaultVariant; private ContextEvaluator contextEvaluator; } diff --git a/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java b/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java index 2bea3e4e..d3fdb985 100644 --- a/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java +++ b/src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java @@ -1,17 +1,28 @@ package dev.openfeature.sdk.providers.memory; -import dev.openfeature.sdk.*; -import dev.openfeature.sdk.exceptions.*; -import lombok.Getter; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; - +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EventProvider; +import dev.openfeature.sdk.Metadata; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.ProviderEventDetails; +import dev.openfeature.sdk.ProviderState; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.FatalError; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.OpenFeatureError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; +import dev.openfeature.sdk.exceptions.TypeMismatchError; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; /** * In-memory provider. @@ -38,6 +49,7 @@ public InMemoryProvider(Map> flags) { /** * Initializes the provider. + * * @param evaluationContext evaluation context * @throws Exception on error */ @@ -60,9 +72,9 @@ public void updateFlags(Map> newFlags) { this.flags.putAll(newFlags); ProviderEventDetails details = ProviderEventDetails.builder() - .flagsChanged(new ArrayList<>(flagsChanged)) - .message("flags changed") - .build(); + .flagsChanged(new ArrayList<>(flagsChanged)) + .message("flags changed") + .build(); emitProviderConfigurationChanged(details); } @@ -76,46 +88,45 @@ public void updateFlags(Map> newFlags) { public void updateFlag(String flagKey, Flag newFlag) { this.flags.put(flagKey, newFlag); ProviderEventDetails details = ProviderEventDetails.builder() - .flagsChanged(Collections.singletonList(flagKey)) - .message("flag added/updated") - .build(); + .flagsChanged(Collections.singletonList(flagKey)) + .message("flag added/updated") + .build(); emitProviderConfigurationChanged(details); } @Override - public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, - EvaluationContext evaluationContext) { + public ProviderEvaluation getBooleanEvaluation( + String key, Boolean defaultValue, EvaluationContext evaluationContext) { return getEvaluation(key, evaluationContext, Boolean.class); } @Override - public ProviderEvaluation getStringEvaluation(String key, String defaultValue, - EvaluationContext evaluationContext) { + public ProviderEvaluation getStringEvaluation( + String key, String defaultValue, EvaluationContext evaluationContext) { return getEvaluation(key, evaluationContext, String.class); } @Override - public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, - EvaluationContext evaluationContext) { + public ProviderEvaluation getIntegerEvaluation( + String key, Integer defaultValue, EvaluationContext evaluationContext) { return getEvaluation(key, evaluationContext, Integer.class); } @Override - public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, - EvaluationContext evaluationContext) { + public ProviderEvaluation getDoubleEvaluation( + String key, Double defaultValue, EvaluationContext evaluationContext) { return getEvaluation(key, evaluationContext, Double.class); } @SneakyThrows @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, - EvaluationContext evaluationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext evaluationContext) { return getEvaluation(key, evaluationContext, Value.class); } private ProviderEvaluation getEvaluation( - String key, EvaluationContext evaluationContext, Class expectedType - ) throws OpenFeatureError { + String key, EvaluationContext evaluationContext, Class expectedType) throws OpenFeatureError { if (!ProviderState.READY.equals(state)) { if (ProviderState.NOT_READY.equals(state)) { throw new ProviderNotReadyError("provider not yet initialized"); @@ -138,9 +149,9 @@ private ProviderEvaluation getEvaluation( value = (T) flag.getVariants().get(flag.getDefaultVariant()); } return ProviderEvaluation.builder() - .value(value) - .variant(flag.getDefaultVariant()) - .reason(Reason.STATIC.toString()) - .build(); + .value(value) + .variant(flag.getDefaultVariant()) + .reason(Reason.STATIC.toString()) + .build(); } } diff --git a/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java b/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java index 841d738e..2f214d8a 100644 --- a/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java +++ b/src/test/java/dev/openfeature/sdk/AlwaysBrokenProvider.java @@ -32,7 +32,8 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext invocationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext invocationContext) { throw new FlagNotFoundError(TestConstants.BROKEN_MESSAGE); } } diff --git a/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java b/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java index b3ead41b..8f304eaa 100644 --- a/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java +++ b/src/test/java/dev/openfeature/sdk/AlwaysBrokenWithDetailsProvider.java @@ -44,7 +44,8 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext invocationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext invocationContext) { return ProviderEvaluation.builder() .errorMessage(TestConstants.BROKEN_MESSAGE) .errorCode(ErrorCode.FLAG_NOT_FOUND) diff --git a/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java b/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java index 8f022a38..cd7e8b29 100644 --- a/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java +++ b/src/test/java/dev/openfeature/sdk/ClientProviderMappingTest.java @@ -1,10 +1,10 @@ package dev.openfeature.sdk; +import static org.junit.jupiter.api.Assertions.*; + import dev.openfeature.sdk.testutils.FeatureProviderTestUtils; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - class ClientProviderMappingTest { @Test diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java index 4502699b..c39c5ba3 100644 --- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java +++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java @@ -1,13 +1,5 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.fixtures.HookFixtures; -import dev.openfeature.sdk.testutils.FeatureProviderTestUtils; -import dev.openfeature.sdk.testutils.TestEventsProvider; -import lombok.SneakyThrows; -import org.junit.jupiter.api.Test; - -import java.util.*; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -15,6 +7,17 @@ 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; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + class DeveloperExperienceTest implements HookFixtures { transient String flagKey = "mykey"; @@ -49,7 +52,10 @@ void evalHooks() { api.setProviderAndWait(new TestEventsProvider()); Client client = api.getClient(); client.addHooks(clientHook); - Boolean retval = client.getBooleanValue(flagKey, false, null, + Boolean retval = client.getBooleanValue( + flagKey, + false, + null, FlagEvaluationOptions.builder().hook(evalHook).build()); verify(clientHook, times(1)).finallyAfter(any(), any()); verify(evalHook, times(1)).finallyAfter(any(), any()); @@ -132,7 +138,10 @@ void setProviderAndWaitShouldPutTheProviderInReadyState() { assertThat(client.getProviderState()).isEqualTo(ProviderState.READY); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() { String domain = "domain"; @@ -145,7 +154,10 @@ void shouldPutTheProviderInStateErrorAfterEmittingErrorEvent() { assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() { String domain = "domain"; @@ -158,7 +170,10 @@ void shouldPutTheProviderInStateStaleAfterEmittingStaleEvent() { assertThat(client.getProviderState()).isEqualTo(ProviderState.STALE); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldPutTheProviderInStateReadyAfterEmittingReadyEvent() { String domain = "domain"; diff --git a/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java b/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java index 32293446..0477a725 100644 --- a/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java +++ b/src/test/java/dev/openfeature/sdk/DoSomethingProvider.java @@ -4,7 +4,8 @@ class DoSomethingProvider implements FeatureProvider { static final String name = "Something"; // Flag evaluation metadata - static final ImmutableMetadata DEFAULT_METADATA = ImmutableMetadata.builder().build(); + static final ImmutableMetadata DEFAULT_METADATA = + ImmutableMetadata.builder().build(); private ImmutableMetadata flagMetadata; public DoSomethingProvider() { @@ -53,7 +54,8 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext invocationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext invocationContext) { return ProviderEvaluation.builder() .value(null) .flagMetadata(flagMetadata) diff --git a/src/test/java/dev/openfeature/sdk/EvalContextTest.java b/src/test/java/dev/openfeature/sdk/EvalContextTest.java index c7f3aa44..0f910b00 100644 --- a/src/test/java/dev/openfeature/sdk/EvalContextTest.java +++ b/src/test/java/dev/openfeature/sdk/EvalContextTest.java @@ -1,6 +1,7 @@ package dev.openfeature.sdk; -import org.junit.jupiter.api.Test; +import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -8,23 +9,26 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - -import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY; -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; public class EvalContextTest { - @Specification(number="3.1.1", - text="The `evaluation context` structure **MUST** define an optional `targeting key` field of " + - "type string, identifying the subject of the flag evaluation.") - @Test void requires_targeting_key() { + @Specification( + number = "3.1.1", + text = "The `evaluation context` structure **MUST** define an optional `targeting key` field of " + + "type string, identifying the subject of the flag evaluation.") + @Test + void requires_targeting_key() { EvaluationContext ec = new ImmutableContext("targeting-key", new HashMap<>()); assertEquals("targeting-key", ec.getTargetingKey()); } - @Specification(number="3.1.2", text= "The evaluation context MUST support the inclusion of " + - "custom fields, having keys of type `string`, and " + - "values of type `boolean | string | number | datetime | structure`.") - @Test void eval_context() { + @Specification( + number = "3.1.2", + text = "The evaluation context MUST support the inclusion of " + + "custom fields, having keys of type `string`, and " + + "values of type `boolean | string | number | datetime | structure`.") + @Test + void eval_context() { Map attributes = new HashMap<>(); Instant dt = Instant.now().truncatedTo(ChronoUnit.MILLIS); attributes.put("str", new Value("test")); @@ -42,16 +46,21 @@ public class EvalContextTest { assertEquals(dt, ec.getValue("dt").asInstant().truncatedTo(ChronoUnit.MILLIS)); } - @Specification(number="3.1.2", text="The evaluation context MUST support the inclusion of " + - "custom fields, having keys of type `string`, and " + - "values of type `boolean | string | number | datetime | structure`.") - @Test void eval_context_structure_array() { + @Specification( + number = "3.1.2", + text = "The evaluation context MUST support the inclusion of " + + "custom fields, having keys of type `string`, and " + + "values of type `boolean | string | number | datetime | structure`.") + @Test + void eval_context_structure_array() { Map attributes = new HashMap<>(); attributes.put("obj", new Value(new MutableStructure().add("val1", 1).add("val2", "2"))); - List values = new ArrayList(){{ - add(new Value("one")); - add(new Value("two")); - }}; + List values = new ArrayList() { + { + add(new Value("one")); + add(new Value("two")); + } + }; attributes.put("arr", new Value(values)); EvaluationContext ec = new ImmutableContext(attributes); @@ -64,11 +73,16 @@ public class EvalContextTest { assertEquals("two", arr.get(1).asString()); } - @Specification(number="3.1.3", text="The evaluation context MUST support fetching the custom fields by key and also fetching all key value pairs.") - @Test void fetch_all() { - Map attributes = new HashMap<>(); + @Specification( + number = "3.1.3", + text = + "The evaluation context MUST support fetching the custom fields by key and also fetching all key value pairs.") + @Test + void fetch_all() { + Map attributes = new HashMap<>(); Instant dt = Instant.now(); - MutableStructure mutableStructure = new MutableStructure().add("val1", 1).add("val2", "2"); + MutableStructure mutableStructure = + new MutableStructure().add("val1", 1).add("val2", "2"); attributes.put("str", new Value("test")); attributes.put("str2", new Value("test2")); attributes.put("bool", new Value(true)); @@ -96,8 +110,9 @@ public class EvalContextTest { assertEquals("2", foundObj.getValue("val2").asString()); } - @Specification(number="3.1.4", text="The evaluation context fields MUST have an unique key.") - @Test void unique_key_across_types() { + @Specification(number = "3.1.4", text = "The evaluation context fields MUST have an unique key.") + @Test + void unique_key_across_types() { MutableContext ec = new MutableContext(); ec.add("key", "val"); ec.add("key", "val2"); @@ -107,8 +122,9 @@ public class EvalContextTest { assertEquals(3, ec.getValue("key").asInteger()); } - @Test void unique_key_across_types_immutableContext() { - HashMap attributes = new HashMap<>(); + @Test + void unique_key_across_types_immutableContext() { + HashMap attributes = new HashMap<>(); attributes.put("key", new Value("val")); attributes.put("key", new Value("val2")); attributes.put("key", new Value(3)); @@ -117,23 +133,23 @@ public class EvalContextTest { assertEquals(3, ec.getValue("key").asInteger()); } - @Test void can_chain_attribute_addition() { + @Test + void can_chain_attribute_addition() { MutableContext ec = new MutableContext(); - MutableContext out = ec.add("str", "test") - .add("int", 4) - .add("bool", false) - .add("str", new MutableStructure()); + MutableContext out = + ec.add("str", "test").add("int", 4).add("bool", false).add("str", new MutableStructure()); assertEquals(MutableContext.class, out.getClass()); } - @Test void can_add_key_with_null() { + @Test + void can_add_key_with_null() { MutableContext ec = new MutableContext() - .add("Boolean", (Boolean)null) - .add("String", (String)null) - .add("Double", (Double)null) - .add("Structure", (MutableStructure)null) - .add("List", (List)null) - .add("Instant", (Instant)null); + .add("Boolean", (Boolean) null) + .add("String", (String) null) + .add("Double", (Double) null) + .add("Structure", (MutableStructure) null) + .add("List", (List) null) + .add("Instant", (Instant) null); assertEquals(6, ec.asMap().size()); assertEquals(null, ec.getValue("Boolean").asBoolean()); assertEquals(null, ec.getValue("String").asString()); @@ -143,7 +159,8 @@ public class EvalContextTest { assertEquals(null, ec.getValue("Instant").asString()); } - @Test void Immutable_context_merge_targeting_key() { + @Test + void Immutable_context_merge_targeting_key() { String key1 = "key1"; EvaluationContext ctx1 = new ImmutableContext(key1, new HashMap<>()); EvaluationContext ctx2 = new ImmutableContext(new HashMap<>()); @@ -156,19 +173,21 @@ public class EvalContextTest { ctxMerged = ctx1.merge(ctx2); assertEquals(key2, ctxMerged.getTargetingKey()); - ctx2 = new ImmutableContext(" ",new HashMap<>()); + ctx2 = new ImmutableContext(" ", new HashMap<>()); ctxMerged = ctx1.merge(ctx2); assertEquals(key1, ctxMerged.getTargetingKey()); } - @Test void merge_null_returns_value() { + @Test + void merge_null_returns_value() { MutableContext ctx1 = new MutableContext("key"); ctx1.add("mything", "value"); EvaluationContext result = ctx1.merge(null); assertEquals(ctx1, result); } - @Test void merge_targeting_key() { + @Test + void merge_targeting_key() { String key1 = "key1"; MutableContext ctx1 = new MutableContext(key1); MutableContext ctx2 = new MutableContext(); @@ -186,14 +205,15 @@ public class EvalContextTest { assertEquals(key2, ctxMerged.getTargetingKey()); } - @Test void asObjectMap() { + @Test + void asObjectMap() { String key1 = "key1"; MutableContext ctx = new MutableContext(key1); ctx.add("stringItem", "stringValue"); ctx.add("boolItem", false); ctx.add("integerItem", 1); ctx.add("doubleItem", 1.2); - ctx.add("instantItem", Instant.ofEpochSecond(1663331342)); + ctx.add("instantItem", Instant.ofEpochSecond(1663331342)); List listItem = new ArrayList<>(); listItem.add(new Value("item1")); listItem.add(new Value("item2")); @@ -207,18 +227,17 @@ public class EvalContextTest { structureValue.put("structBoolItem", new Value(false)); structureValue.put("structIntegerItem", new Value(1)); structureValue.put("structDoubleItem", new Value(1.2)); - structureValue.put("structInstantItem", new Value(Instant.ofEpochSecond(1663331342))); + structureValue.put("structInstantItem", new Value(Instant.ofEpochSecond(1663331342))); Structure structure = new MutableStructure(structureValue); ctx.add("structureItem", structure); - Map want = new HashMap<>(); want.put(TARGETING_KEY, key1); want.put("stringItem", "stringValue"); want.put("boolItem", false); want.put("integerItem", 1); want.put("doubleItem", 1.2); - want.put("instantItem", Instant.ofEpochSecond(1663331342)); + want.put("instantItem", Instant.ofEpochSecond(1663331342)); List wantListItem = new ArrayList<>(); wantListItem.add("item1"); wantListItem.add("item2"); @@ -232,9 +251,9 @@ public class EvalContextTest { wantStructureValue.put("structBoolItem", false); wantStructureValue.put("structIntegerItem", 1); wantStructureValue.put("structDoubleItem", 1.2); - wantStructureValue.put("structInstantItem", Instant.ofEpochSecond(1663331342)); - want.put("structureItem",wantStructureValue); + wantStructureValue.put("structInstantItem", Instant.ofEpochSecond(1663331342)); + want.put("structureItem", wantStructureValue); - assertEquals(want,ctx.asObjectMap()); + assertEquals(want, ctx.asObjectMap()); } } diff --git a/src/test/java/dev/openfeature/sdk/EventProviderTest.java b/src/test/java/dev/openfeature/sdk/EventProviderTest.java index acf2ce6b..d8af6e8d 100644 --- a/src/test/java/dev/openfeature/sdk/EventProviderTest.java +++ b/src/test/java/dev/openfeature/sdk/EventProviderTest.java @@ -1,20 +1,15 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.exceptions.FatalError; -import dev.openfeature.sdk.exceptions.GeneralError; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import dev.openfeature.sdk.internal.TriConsumer; import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - class EventProviderTest { private TestEventProvider eventProvider; @@ -71,7 +66,6 @@ void throwsWhenOnEmitDifferent() { assertThrows(IllegalStateException.class, () -> eventProvider.attach(onEmit2)); } - @Test @DisplayName("should not throw if second same onEmit attached") void doesNotThrowWhenOnEmitSame() { @@ -91,32 +85,29 @@ public Metadata getMetadata() { } @Override - public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, - EvaluationContext ctx) { + public ProviderEvaluation getBooleanEvaluation( + String key, Boolean defaultValue, EvaluationContext ctx) { throw new UnsupportedOperationException("Unimplemented method 'getBooleanEvaluation'"); } @Override - public ProviderEvaluation getStringEvaluation(String key, String defaultValue, - EvaluationContext ctx) { + public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { throw new UnsupportedOperationException("Unimplemented method 'getStringEvaluation'"); } @Override - public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, - EvaluationContext ctx) { + public ProviderEvaluation getIntegerEvaluation( + String key, Integer defaultValue, EvaluationContext ctx) { throw new UnsupportedOperationException("Unimplemented method 'getIntegerEvaluation'"); } @Override - public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, - EvaluationContext ctx) { + public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { throw new UnsupportedOperationException("Unimplemented method 'getDoubleEvaluation'"); } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, - EvaluationContext ctx) { + public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { throw new UnsupportedOperationException("Unimplemented method 'getObjectEvaluation'"); } @@ -130,4 +121,4 @@ public void attach(TriConsumer mockOnEmit() { return (TriConsumer) mock(TriConsumer.class); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/EventsTest.java b/src/test/java/dev/openfeature/sdk/EventsTest.java index 41bcf86c..02a5953b 100644 --- a/src/test/java/dev/openfeature/sdk/EventsTest.java +++ b/src/test/java/dev/openfeature/sdk/EventsTest.java @@ -1,23 +1,22 @@ package dev.openfeature.sdk; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +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.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.*; - class EventsTest { private static final int TIMEOUT = 300; @@ -41,8 +40,10 @@ class Initialization { @Test @DisplayName("should fire initial READY event when provider init succeeds") - @Specification(number = "5.3.1", text = "If the provider's initialize function terminates normally," + - " PROVIDER_READY handlers MUST run.") + @Specification( + number = "5.3.1", + text = "If the provider's initialize function terminates normally," + + " PROVIDER_READY handlers MUST run.") void apiInitReady() { final Consumer handler = mockHandler(); final String name = "apiInitReady"; @@ -50,14 +51,15 @@ void apiInitReady() { TestEventsProvider provider = new TestEventsProvider(INIT_DELAY); OpenFeatureAPI.getInstance().onProviderReady(handler); OpenFeatureAPI.getInstance().setProviderAndWait(name, provider); - verify(handler, timeout(TIMEOUT).atLeastOnce()) - .accept(any()); + verify(handler, timeout(TIMEOUT).atLeastOnce()).accept(any()); } @Test @DisplayName("should fire initial ERROR event when provider init errors") - @Specification(number = "5.3.2", text = "If the provider's initialize function terminates abnormally," + - " PROVIDER_ERROR handlers MUST run.") + @Specification( + number = "5.3.2", + text = "If the provider's initialize function terminates abnormally," + + " PROVIDER_ERROR handlers MUST run.") void apiInitError() { final Consumer handler = mockHandler(); final String name = "apiInitError"; @@ -78,9 +80,10 @@ class ProviderEvents { @Test @DisplayName("should propagate events") - @Specification(number = "5.1.2", text = "When a provider signals the occurrence of a particular event, " - + - "the associated client and API event handlers MUST run.") + @Specification( + number = "5.1.2", + text = "When a provider signals the occurrence of a particular event, " + + "the associated client and API event handlers MUST run.") void apiShouldPropagateEvents() { final Consumer handler = mockHandler(); final String name = "apiShouldPropagateEvents"; @@ -89,18 +92,24 @@ void apiShouldPropagateEvents() { OpenFeatureAPI.getInstance().setProviderAndWait(name, provider); OpenFeatureAPI.getInstance().onProviderConfigurationChanged(handler); - provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, EventDetails.builder().build()); + provider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + EventDetails.builder().build()); verify(handler, timeout(TIMEOUT)).accept(any()); } @Test @DisplayName("should support all event types") - @Specification(number = "5.1.1", text = "The provider MAY define a mechanism for signaling the occurrence " - + "of one of a set of events, including PROVIDER_READY, PROVIDER_ERROR, " - + "PROVIDER_CONFIGURATION_CHANGED and PROVIDER_STALE, with a provider event details payload.") - @Specification(number = "5.2.2", text = "The API MUST provide a function for associating handler functions" - + - " with a particular provider event type.") + @Specification( + number = "5.1.1", + text = + "The provider MAY define a mechanism for signaling the occurrence " + + "of one of a set of events, including PROVIDER_READY, PROVIDER_ERROR, " + + "PROVIDER_CONFIGURATION_CHANGED and PROVIDER_STALE, with a provider event details payload.") + @Specification( + number = "5.2.2", + text = "The API MUST provide a function for associating handler functions" + + " with a particular provider event type.") void apiShouldSupportAllEventTypes() { final String name = "apiShouldSupportAllEventTypes"; final Consumer handler1 = mockHandler(); @@ -117,7 +126,8 @@ void apiShouldSupportAllEventTypes() { OpenFeatureAPI.getInstance().onProviderError(handler4); Arrays.asList(ProviderEvent.values()).stream().forEach(eventType -> { - provider.mockEvent(eventType, ProviderEventDetails.builder().build()); + provider.mockEvent( + eventType, ProviderEventDetails.builder().build()); }); verify(handler1, timeout(TIMEOUT).atLeastOnce()).accept(any()); @@ -143,7 +153,10 @@ class ProviderEvents { @Test @DisplayName("should propagate events for default provider and anonymous client") - @Specification(number = "5.1.2", text = "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") + @Specification( + number = "5.1.2", + text = + "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") void shouldPropagateDefaultAndAnon() { final Consumer handler = mockHandler(); @@ -153,13 +166,17 @@ void shouldPropagateDefaultAndAnon() { Client client = OpenFeatureAPI.getInstance().getClient(); client.onProviderStale(handler); - provider.mockEvent(ProviderEvent.PROVIDER_STALE, EventDetails.builder().build()); + provider.mockEvent( + ProviderEvent.PROVIDER_STALE, EventDetails.builder().build()); verify(handler, timeout(TIMEOUT)).accept(any()); } @Test @DisplayName("should propagate events for default provider and named client") - @Specification(number = "5.1.2", text = "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") + @Specification( + number = "5.1.2", + text = + "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") void shouldPropagateDefaultAndNamed() { final Consumer handler = mockHandler(); final String name = "shouldPropagateDefaultAndNamed"; @@ -170,7 +187,8 @@ void shouldPropagateDefaultAndNamed() { Client client = OpenFeatureAPI.getInstance().getClient(name); client.onProviderStale(handler); - provider.mockEvent(ProviderEvent.PROVIDER_STALE, EventDetails.builder().build()); + provider.mockEvent( + ProviderEvent.PROVIDER_STALE, EventDetails.builder().build()); verify(handler, timeout(TIMEOUT)).accept(any()); } } @@ -186,7 +204,10 @@ class NamedProvider { class Initialization { @Test @DisplayName("should fire initial READY event when provider init succeeds after client retrieved") - @Specification(number = "5.3.1", text = "If the provider's initialize function terminates normally, PROVIDER_READY handlers MUST run.") + @Specification( + number = "5.3.1", + text = + "If the provider's initialize function terminates normally, PROVIDER_READY handlers MUST run.") void initReadyProviderBefore() { final Consumer handler = mockHandler(); final String name = "initReadyProviderBefore"; @@ -202,7 +223,10 @@ void initReadyProviderBefore() { @Test @DisplayName("should fire initial READY event when provider init succeeds before client retrieved") - @Specification(number = "5.3.1", text = "If the provider's initialize function terminates normally, PROVIDER_READY handlers MUST run.") + @Specification( + number = "5.3.1", + text = + "If the provider's initialize function terminates normally, PROVIDER_READY handlers MUST run.") void initReadyProviderAfter() { final Consumer handler = mockHandler(); final String name = "initReadyProviderAfter"; @@ -218,7 +242,10 @@ void initReadyProviderAfter() { @Test @DisplayName("should fire initial ERROR event when provider init errors after client retrieved") - @Specification(number = "5.3.2", text = "If the provider's initialize function terminates abnormally, PROVIDER_ERROR handlers MUST run.") + @Specification( + number = "5.3.2", + text = + "If the provider's initialize function terminates abnormally, PROVIDER_ERROR handlers MUST run.") void initErrorProviderAfter() { final Consumer handler = mockHandler(); final String name = "initErrorProviderAfter"; @@ -230,14 +257,16 @@ void initErrorProviderAfter() { // set provider after getting a client OpenFeatureAPI.getInstance().setProvider(name, provider); verify(handler, timeout(TIMEOUT)).accept(argThat(details -> { - return name.equals(details.getDomain()) - && errMessage.equals(details.getMessage()); + return name.equals(details.getDomain()) && errMessage.equals(details.getMessage()); })); } @Test @DisplayName("should fire initial ERROR event when provider init errors before client retrieved") - @Specification(number = "5.3.2", text = "If the provider's initialize function terminates abnormally, PROVIDER_ERROR handlers MUST run.") + @Specification( + number = "5.3.2", + text = + "If the provider's initialize function terminates abnormally, PROVIDER_ERROR handlers MUST run.") void initErrorProviderBefore() { final Consumer handler = mockHandler(); final String name = "initErrorProviderBefore"; @@ -249,8 +278,7 @@ void initErrorProviderBefore() { Client client = OpenFeatureAPI.getInstance().getClient(name); client.onProviderError(handler); verify(handler, timeout(TIMEOUT)).accept(argThat(details -> { - return name.equals(details.getDomain()) - && errMessage.equals(details.getMessage()); + return name.equals(details.getDomain()) && errMessage.equals(details.getMessage()); })); } } @@ -261,7 +289,10 @@ class ProviderEvents { @Test @DisplayName("should propagate events when provider set before client retrieved") - @Specification(number = "5.1.2", text = "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") + @Specification( + number = "5.1.2", + text = + "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") void shouldPropagateBefore() { final Consumer handler = mockHandler(); final String name = "shouldPropagateBefore"; @@ -272,13 +303,19 @@ void shouldPropagateBefore() { Client client = OpenFeatureAPI.getInstance().getClient(name); client.onProviderConfigurationChanged(handler); - provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, EventDetails.builder().build()); - verify(handler, timeout(TIMEOUT)).accept(argThat(details -> details.getDomain().equals(name))); + provider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + EventDetails.builder().build()); + verify(handler, timeout(TIMEOUT)) + .accept(argThat(details -> details.getDomain().equals(name))); } @Test @DisplayName("should propagate events when provider set after client retrieved") - @Specification(number = "5.1.2", text = "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") + @Specification( + number = "5.1.2", + text = + "When a provider signals the occurrence of a particular event, the associated client and API event handlers MUST run.") void shouldPropagateAfter() { final Consumer handler = mockHandler(); @@ -290,18 +327,25 @@ void shouldPropagateAfter() { // set provider after getting a client OpenFeatureAPI.getInstance().setProviderAndWait(name, provider); - provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, EventDetails.builder().build()); - verify(handler, timeout(TIMEOUT)).accept(argThat(details -> details.getDomain().equals(name))); + provider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + EventDetails.builder().build()); + verify(handler, timeout(TIMEOUT)) + .accept(argThat(details -> details.getDomain().equals(name))); } @Test @DisplayName("should support all event types") - @Specification(number = "5.1.1", text = "The provider MAY define a mechanism for signaling the occurrence " - + "of one of a set of events, including PROVIDER_READY, PROVIDER_ERROR, " - + "PROVIDER_CONFIGURATION_CHANGED and PROVIDER_STALE, with a provider event details payload.") - @Specification(number = "5.2.1", text = "The client MUST provide a function for associating handler functions" - + - " with a particular provider event type.") + @Specification( + number = "5.1.1", + text = + "The provider MAY define a mechanism for signaling the occurrence " + + "of one of a set of events, including PROVIDER_READY, PROVIDER_ERROR, " + + "PROVIDER_CONFIGURATION_CHANGED and PROVIDER_STALE, with a provider event details payload.") + @Specification( + number = "5.2.1", + text = "The client MUST provide a function for associating handler functions" + + " with a particular provider event type.") void shouldSupportAllEventTypes() { final String name = "shouldSupportAllEventTypes"; final Consumer handler1 = mockHandler(); @@ -321,8 +365,8 @@ void shouldSupportAllEventTypes() { Arrays.asList(ProviderEvent.values()).stream().forEach(eventType -> { provider.mockEvent(eventType, ProviderEventDetails.builder().build()); }); - ArgumentMatcher nameMatches = (EventDetails details) -> details.getDomain() - .equals(name); + ArgumentMatcher nameMatches = + (EventDetails details) -> details.getDomain().equals(name); verify(handler1, timeout(TIMEOUT).atLeastOnce()).accept(argThat(nameMatches)); verify(handler2, timeout(TIMEOUT).atLeastOnce()).accept(argThat(nameMatches)); verify(handler3, timeout(TIMEOUT).atLeastOnce()).accept(argThat(nameMatches)); @@ -353,7 +397,9 @@ void shouldNotRunHandlers() { await().until(() -> provider1.isShutDown()); // fire old event - provider1.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, EventDetails.builder().build()); + provider1.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + EventDetails.builder().build()); // a bit of waiting here, but we want to make sure these are indeed never // called. @@ -363,8 +409,10 @@ void shouldNotRunHandlers() { @Test @DisplayName("other client handlers should not run") - @Specification(number = "5.1.3", text = "When a provider signals the occurrence of a particular event, " + - "event handlers on clients which are not associated with that provider MUST NOT run.") + @Specification( + number = "5.1.3", + text = "When a provider signals the occurrence of a particular event, " + + "event handlers on clients which are not associated with that provider MUST NOT run.") void otherClientHandlersShouldNotRun() { final String name1 = "otherClientHandlersShouldNotRun1"; final String name2 = "otherClientHandlersShouldNotRun2"; @@ -382,7 +430,9 @@ void otherClientHandlersShouldNotRun() { client1.onProviderConfigurationChanged(handlerToRun); client2.onProviderConfigurationChanged(handlerNotToRun); - provider1.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); + provider1.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); verify(handlerToRun, timeout(TIMEOUT)).accept(any()); verify(handlerNotToRun, never()).accept(any()); @@ -390,8 +440,10 @@ void otherClientHandlersShouldNotRun() { @Test @DisplayName("bound named client handlers should not run with default") - @Specification(number = "5.1.3", text = "When a provider signals the occurrence of a particular event, " + - "event handlers on clients which are not associated with that provider MUST NOT run.") + @Specification( + number = "5.1.3", + text = "When a provider signals the occurrence of a particular event, " + + "event handlers on clients which are not associated with that provider MUST NOT run.") void boundShouldNotRunWithDefault() { final String name = "boundShouldNotRunWithDefault"; final Consumer handlerNotToRun = mockHandler(); @@ -408,7 +460,9 @@ void boundShouldNotRunWithDefault() { await().until(() -> namedProvider.getState().equals(ProviderState.READY)); // fire event on default provider - defaultProvider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); + defaultProvider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); verify(handlerNotToRun, after(TIMEOUT).never()).accept(any()); OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider()); @@ -416,8 +470,10 @@ void boundShouldNotRunWithDefault() { @Test @DisplayName("unbound named client handlers should run with default") - @Specification(number = "5.1.3", text = "When a provider signals the occurrence of a particular event, " + - "event handlers on clients which are not associated with that provider MUST NOT run.") + @Specification( + number = "5.1.3", + text = "When a provider signals the occurrence of a particular event, " + + "event handlers on clients which are not associated with that provider MUST NOT run.") void unboundShouldRunWithDefault() { final String name = "unboundShouldRunWithDefault"; final Consumer handlerToRun = mockHandler(); @@ -432,7 +488,9 @@ void unboundShouldRunWithDefault() { await().until(() -> defaultProvider.getState().equals(ProviderState.READY)); // fire event on default provider - defaultProvider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); + defaultProvider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); verify(handlerToRun, timeout(TIMEOUT)).accept(any()); OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider()); @@ -440,7 +498,9 @@ void unboundShouldRunWithDefault() { @Test @DisplayName("subsequent handlers run if earlier throws") - @Specification(number = "5.2.5", text = "If a handler function terminates abnormally, other handler functions MUST run.") + @Specification( + number = "5.2.5", + text = "If a handler function terminates abnormally, other handler functions MUST run.") void handlersRunIfOneThrows() { final String name = "handlersRunIfOneThrows"; final Consumer errorHandler = mockHandler(); @@ -457,7 +517,9 @@ void handlersRunIfOneThrows() { client1.onProviderConfigurationChanged(nextHandler); client1.onProviderConfigurationChanged(lastHandler); - provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); + provider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); verify(errorHandler, timeout(TIMEOUT)).accept(any()); verify(nextHandler, timeout(TIMEOUT)).accept(any()); verify(lastHandler, timeout(TIMEOUT)).accept(any()); @@ -466,7 +528,9 @@ void handlersRunIfOneThrows() { @Test @DisplayName("should have all properties") @Specification(number = "5.2.4", text = "The handler function MUST accept a event details parameter.") - @Specification(number = "5.2.3", text = "The `event details` MUST contain the `provider name` associated with the event.") + @Specification( + number = "5.2.3", + text = "The `event details` MUST contain the `provider name` associated with the event.") void shouldHaveAllProperties() { final Consumer handler1 = mockHandler(); final Consumer handler2 = mockHandler(); @@ -481,7 +545,8 @@ void shouldHaveAllProperties() { client.onProviderConfigurationChanged(handler2); List flagsChanged = Arrays.asList("flag"); - ImmutableMetadata metadata = ImmutableMetadata.builder().addInteger("int", 1).build(); + ImmutableMetadata metadata = + ImmutableMetadata.builder().addInteger("int", 1).build(); String message = "a message"; ProviderEventDetails details = ProviderEventDetails.builder() .eventMetadata(metadata) @@ -492,25 +557,25 @@ void shouldHaveAllProperties() { provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details); // both global and client handler should have all the fields. - verify(handler1, timeout(TIMEOUT)) - .accept(argThat((EventDetails eventDetails) -> { - return metadata.equals(eventDetails.getEventMetadata()) - // TODO: issue for client name in events - && flagsChanged.equals(eventDetails.getFlagsChanged()) - && message.equals(eventDetails.getMessage()); - })); - verify(handler2, timeout(TIMEOUT)) - .accept(argThat((EventDetails eventDetails) -> { - return metadata.equals(eventDetails.getEventMetadata()) - && flagsChanged.equals(eventDetails.getFlagsChanged()) - && message.equals(eventDetails.getMessage()) - && name.equals(eventDetails.getDomain()); - })); + verify(handler1, timeout(TIMEOUT)).accept(argThat((EventDetails eventDetails) -> { + return metadata.equals(eventDetails.getEventMetadata()) + // TODO: issue for client name in events + && flagsChanged.equals(eventDetails.getFlagsChanged()) + && message.equals(eventDetails.getMessage()); + })); + verify(handler2, timeout(TIMEOUT)).accept(argThat((EventDetails eventDetails) -> { + return metadata.equals(eventDetails.getEventMetadata()) + && flagsChanged.equals(eventDetails.getFlagsChanged()) + && message.equals(eventDetails.getMessage()) + && name.equals(eventDetails.getDomain()); + })); } @Test @DisplayName("if the provider is ready handlers must run immediately") - @Specification(number = "5.3.3", text = "Handlers attached after the provider is already in the associated state, MUST run immediately.") + @Specification( + 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 Consumer handler = mockHandler(); @@ -527,7 +592,9 @@ void matchingReadyEventsMustRunImmediately() { @Test @DisplayName("if the provider is ready handlers must run immediately") - @Specification(number = "5.3.3", text = "Handlers attached after the provider is already in the associated state, MUST run immediately.") + @Specification( + 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 Consumer handler = mockHandler(); @@ -547,7 +614,9 @@ void matchingStaleEventsMustRunImmediately() { @Test @DisplayName("if the provider is ready handlers must run immediately") - @Specification(number = "5.3.3", text = "Handlers attached after the provider is already in the associated state, MUST run immediately.") + @Specification( + 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 Consumer handler = mockHandler(); @@ -560,7 +629,6 @@ void matchingErrorEventsMustRunImmediately() { provider.emitProviderError(ProviderEventDetails.builder().build()); assertThat(client.getProviderState()).isEqualTo(ProviderState.ERROR); - // should run even thought handler was added after error client.onProviderError(handler); verify(handler, timeout(TIMEOUT)).accept(any()); @@ -580,8 +648,11 @@ void mustPersistAcrossChanges() { Client client = OpenFeatureAPI.getInstance().getClient(name); client.onProviderConfigurationChanged(handler); - provider1.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); - ArgumentMatcher nameMatches = (EventDetails details) -> details.getDomain().equals(name); + provider1.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); + ArgumentMatcher nameMatches = + (EventDetails details) -> details.getDomain().equals(name); verify(handler, timeout(TIMEOUT).times(1)).accept(argThat(nameMatches)); @@ -590,13 +661,17 @@ void mustPersistAcrossChanges() { // verify that with the new provider under the same name, the handler is called // again. - provider2.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); + provider2.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); verify(handler, timeout(TIMEOUT).times(2)).accept(argThat(nameMatches)); } @Nested class HandlerRemoval { - @Specification(number = "5.2.7", text = "The API and client MUST provide a function allowing the removal of event handlers.") + @Specification( + number = "5.2.7", + text = "The API and client MUST provide a function allowing the removal of event handlers.") @Test @DisplayName("should not run removed events") @SneakyThrows @@ -617,18 +692,21 @@ void removedEventsShouldNotRun() { client.removeHandler(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, handler2); // emit event - provider.mockEvent(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, ProviderEventDetails.builder().build()); - + provider.mockEvent( + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, + ProviderEventDetails.builder().build()); + // both global and client handlers should not run. verify(handler1, after(TIMEOUT).never()).accept(any()); verify(handler2, never()).accept(any()); } } - @Specification(number = "5.1.4", text = "PROVIDER_ERROR events SHOULD populate the provider event details's error message field.") + @Specification( + number = "5.1.4", + text = "PROVIDER_ERROR events SHOULD populate the provider event details's error message field.") @Test - void thisIsAProviderRequirement() { - } + void thisIsAProviderRequirement() {} @SuppressWarnings("unchecked") private static Consumer mockHandler() { diff --git a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java index 9d05524f..ff3f3a3f 100644 --- a/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java +++ b/src/test/java/dev/openfeature/sdk/FeatureProviderStateManagerTest.java @@ -1,17 +1,16 @@ package dev.openfeature.sdk; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import dev.openfeature.sdk.exceptions.FatalError; import dev.openfeature.sdk.exceptions.GeneralError; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.annotation.Nullable; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - class FeatureProviderStateManagerTest { private FeatureProviderStateManager wrapper; @@ -48,7 +47,10 @@ void shouldSetStateToNotReadyAfterConstruction() { @SneakyThrows @Test - @Specification(number = "1.7.3", text = "The client's provider status accessor MUST indicate READY if the initialize function of the associated provider terminates normally.") + @Specification( + number = "1.7.3", + text = + "The client's provider status accessor MUST indicate READY if the initialize function of the associated provider terminates normally.") void shouldSetStateToReadyAfterInit() { assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY); wrapper.initialize(null); @@ -65,7 +67,10 @@ void shouldSetStateToNotReadyAfterShutdown() { assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY); } - @Specification(number = "1.7.4", text = "The client's provider status accessor MUST indicate ERROR if the initialize function of the associated provider terminates abnormally.") + @Specification( + number = "1.7.4", + text = + "The client's provider status accessor MUST indicate ERROR if the initialize function of the associated provider terminates abnormally.") @Test void shouldSetStateToErrorAfterErrorOnInit() { testDelegate.throwOnInit = new Exception(); @@ -74,7 +79,10 @@ void shouldSetStateToErrorAfterErrorOnInit() { assertThat(wrapper.getState()).isEqualTo(ProviderState.ERROR); } - @Specification(number = "1.7.4", text = "The client's provider status accessor MUST indicate ERROR if the initialize function of the associated provider terminates abnormally.") + @Specification( + number = "1.7.4", + text = + "The client's provider status accessor MUST indicate ERROR if the initialize function of the associated provider terminates abnormally.") @Test void shouldSetStateToErrorAfterOpenFeatureErrorOnInit() { testDelegate.throwOnInit = new GeneralError(); @@ -83,7 +91,10 @@ void shouldSetStateToErrorAfterOpenFeatureErrorOnInit() { assertThat(wrapper.getState()).isEqualTo(ProviderState.ERROR); } - @Specification(number = "1.7.5", text = "The client's provider status accessor MUST indicate FATAL if the initialize function of the associated provider terminates abnormally and indicates error code PROVIDER_FATAL.") + @Specification( + number = "1.7.5", + text = + "The client's provider status accessor MUST indicate FATAL if the initialize function of the associated provider terminates abnormally and indicates error code PROVIDER_FATAL.") @Test void shouldSetStateToErrorAfterFatalErrorOnInit() { testDelegate.throwOnInit = new FatalError(); @@ -92,7 +103,10 @@ void shouldSetStateToErrorAfterFatalErrorOnInit() { assertThat(wrapper.getState()).isEqualTo(ProviderState.FATAL); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldSetTheStateToReadyWhenAReadyEventIsEmitted() { assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY); @@ -100,7 +114,10 @@ void shouldSetTheStateToReadyWhenAReadyEventIsEmitted() { assertThat(wrapper.getState()).isEqualTo(ProviderState.READY); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldSetTheStateToStaleWhenAStaleEventIsEmitted() { assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY); @@ -108,25 +125,31 @@ void shouldSetTheStateToStaleWhenAStaleEventIsEmitted() { assertThat(wrapper.getState()).isEqualTo(ProviderState.STALE); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldSetTheStateToErrorWhenAnErrorEventIsEmitted() { assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY); wrapper.onEmit( ProviderEvent.PROVIDER_ERROR, - ProviderEventDetails.builder().errorCode(ErrorCode.GENERAL).build() - ); + ProviderEventDetails.builder().errorCode(ErrorCode.GENERAL).build()); assertThat(wrapper.getState()).isEqualTo(ProviderState.ERROR); } - @Specification(number = "5.3.5", text = "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") + @Specification( + number = "5.3.5", + text = + "If the provider emits an event, the value of the client's provider status MUST be updated accordingly.") @Test void shouldSetTheStateToFatalWhenAFatalErrorEventIsEmitted() { assertThat(wrapper.getState()).isEqualTo(ProviderState.NOT_READY); wrapper.onEmit( ProviderEvent.PROVIDER_ERROR, - ProviderEventDetails.builder().errorCode(ErrorCode.PROVIDER_FATAL).build() - ); + ProviderEventDetails.builder() + .errorCode(ErrorCode.PROVIDER_FATAL) + .build()); assertThat(wrapper.getState()).isEqualTo(ProviderState.FATAL); } @@ -141,7 +164,8 @@ public Metadata getMetadata() { } @Override - public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + public ProviderEvaluation getBooleanEvaluation( + String key, Boolean defaultValue, EvaluationContext ctx) { return null; } @@ -151,7 +175,8 @@ public ProviderEvaluation getStringEvaluation(String key, String default } @Override - public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { + public ProviderEvaluation getIntegerEvaluation( + String key, Integer defaultValue, EvaluationContext ctx) { return null; } @@ -178,4 +203,4 @@ public void shutdown() { shutdownCalled.incrementAndGet(); } } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationDetailsTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationDetailsTest.java index dfa77274..345a7eff 100644 --- a/src/test/java/dev/openfeature/sdk/FlagEvaluationDetailsTest.java +++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationDetailsTest.java @@ -29,13 +29,7 @@ public void sevenArgConstructor() { ImmutableMetadata metadata = ImmutableMetadata.builder().build(); FlagEvaluationDetails details = new FlagEvaluationDetails<>( - flagKey, - value, - variant, - reason.toString(), - errorCode, - errorMessage, - metadata); + flagKey, value, variant, reason.toString(), errorCode, errorMessage, metadata); assertEquals(flagKey, details.getFlagKey()); assertEquals(value, details.getValue()); @@ -48,13 +42,14 @@ public void sevenArgConstructor() { @Test @DisplayName("should be able to compare 2 FlagEvaluationDetails") - public void compareFlagEvaluationDetails(){ + public void compareFlagEvaluationDetails() { FlagEvaluationDetails fed1 = FlagEvaluationDetails.builder() .reason(Reason.ERROR.toString()) .value(false) .errorCode(ErrorCode.GENERAL) .errorMessage("error XXX") - .flagMetadata(ImmutableMetadata.builder().addString("metadata","1").build()) + .flagMetadata( + ImmutableMetadata.builder().addString("metadata", "1").build()) .build(); FlagEvaluationDetails fed2 = FlagEvaluationDetails.builder() @@ -62,9 +57,10 @@ public void compareFlagEvaluationDetails(){ .value(false) .errorCode(ErrorCode.GENERAL) .errorMessage("error XXX") - .flagMetadata(ImmutableMetadata.builder().addString("metadata","1").build()) + .flagMetadata( + ImmutableMetadata.builder().addString("metadata", "1").build()) .build(); - assertEquals(fed1,fed2); + assertEquals(fed1, fed2); } } diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java index a2316a59..2ad88d32 100644 --- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java @@ -1,10 +1,20 @@ package dev.openfeature.sdk; +import static dev.openfeature.sdk.DoSomethingProvider.DEFAULT_METADATA; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + import dev.openfeature.sdk.exceptions.GeneralError; import dev.openfeature.sdk.fixtures.HookFixtures; -import dev.openfeature.sdk.providers.memory.InMemoryProvider; import dev.openfeature.sdk.testutils.FeatureProviderTestUtils; import dev.openfeature.sdk.testutils.TestEventsProvider; +import java.util.HashMap; +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; @@ -13,18 +23,6 @@ import org.simplify4u.slf4jmock.LoggerMock; import org.slf4j.Logger; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static dev.openfeature.sdk.DoSomethingProvider.DEFAULT_METADATA; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.*; - class FlagEvaluationSpecTest implements HookFixtures { private Logger logger; @@ -48,34 +46,49 @@ void getApiInstance() { api = OpenFeatureAPI.getInstance(); } - @AfterEach void reset_ctx() { + @AfterEach + void reset_ctx() { api.setEvaluationContext(null); } - @BeforeEach void set_logger() { + @BeforeEach + void set_logger() { logger = Mockito.mock(Logger.class); LoggerMock.setMock(OpenFeatureClient.class, logger); } - @AfterEach void reset_logs() { + @AfterEach + void reset_logs() { LoggerMock.setMock(OpenFeatureClient.class, logger); } - @Specification(number="1.1.1", text="The API, and any state it maintains SHOULD exist as a global singleton, even in cases wherein multiple versions of the API are present at runtime.") - @Test void global_singleton() { + @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="The API MUST define a provider mutator, a function to set the default provider, which accepts an API-conformant provider implementation.") - @Test void provider() { + @Specification( + number = "1.1.2.1", + text = + "The API MUST define a provider mutator, a function to set the default provider, which accepts an API-conformant provider implementation.") + @Test + void provider() { FeatureProvider mockProvider = mock(FeatureProvider.class); FeatureProviderTestUtils.setFeatureProvider(mockProvider); assertThat(api.getProvider()).isEqualTo(mockProvider); } @SneakyThrows - @Specification(number="1.1.8", text="The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw.") - @Test void providerAndWait() { + @Specification( + number = "1.1.8", + text = + "The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw.") + @Test + void providerAndWait() { FeatureProvider provider = new TestEventsProvider(500); OpenFeatureAPI.getInstance().setProviderAndWait(provider); Client client = api.getClient(); @@ -89,8 +102,12 @@ void getApiInstance() { } @SneakyThrows - @Specification(number="1.1.8", text="The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw.") - @Test void providerAndWaitError() { + @Specification( + number = "1.1.8", + text = + "The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw.") + @Test + void providerAndWaitError() { FeatureProvider provider1 = new TestEventsProvider(500, true, "fake error"); assertThrows(GeneralError.class, () -> api.setProviderAndWait(provider1)); @@ -99,8 +116,12 @@ void getApiInstance() { assertThrows(GeneralError.class, () -> api.setProviderAndWait(providerName, provider2)); } - @Specification(number="2.4.5", text="The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready.") - @Test void shouldReturnNotReadyIfNotInitialized() { + @Specification( + number = "2.4.5", + text = + "The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready.") + @Test + void shouldReturnNotReadyIfNotInitialized() { FeatureProvider provider = new TestEventsProvider(100); String providerName = "shouldReturnNotReadyIfNotInitialized"; OpenFeatureAPI.getInstance().setProvider(providerName, provider); @@ -110,14 +131,21 @@ void getApiInstance() { assertEquals(Reason.ERROR.toString(), details.getReason()); } - @Specification(number="1.1.5", text="The API MUST provide a function for retrieving the metadata field of the configured provider.") - @Test void provider_metadata() { + @Specification( + number = "1.1.5", + text = "The API MUST provide a function for retrieving the metadata field of the configured provider.") + @Test + void provider_metadata() { FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider()); assertThat(api.getProviderMetadata().getName()).isEqualTo(DoSomethingProvider.name); } - @Specification(number="1.1.4", text="The API MUST provide a function to add hooks which accepts one or more API-conformant hooks, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed.") - @Test void hook_addition() { + @Specification( + number = "1.1.4", + text = + "The API MUST provide a function to add hooks which accepts one or more API-conformant hooks, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed.") + @Test + void hook_addition() { Hook h1 = mock(Hook.class); Hook h2 = mock(Hook.class); api.addHooks(h1); @@ -130,8 +158,12 @@ void getApiInstance() { assertEquals(h2, api.getHooks().get(1)); } - @Specification(number="1.1.6", text="The API MUST provide a function for creating a client which accepts the following options: - domain (optional): A logical string identifier for binding clients to provider.") - @Test void domainName() { + @Specification( + number = "1.1.6", + text = + "The API MUST provide a function for creating a client which accepts the following options: - domain (optional): A logical string identifier for binding clients to provider.") + @Test + void domainName() { assertNull(api.getClient().getMetadata().getDomain()); String domain = "Sir Calls-a-lot"; @@ -139,8 +171,12 @@ void getApiInstance() { assertEquals(domain, clientForDomain.getMetadata().getDomain()); } - @Specification(number="1.2.1", text="The client MUST provide a method to add hooks which accepts one or more API-conformant hooks, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed.") - @Test void hookRegistration() { + @Specification( + number = "1.2.1", + text = + "The client MUST provide a method to add hooks which accepts one or more API-conformant hooks, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed.") + @Test + void hookRegistration() { Client c = _client(); Hook m1 = mock(Hook.class); Hook m2 = mock(Hook.class); @@ -152,9 +188,16 @@ void getApiInstance() { assertTrue(hooks.contains(m2)); } - @Specification(number="1.3.1.1", text="The client MUST provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters flag key (string, required), default value (boolean | number | string | structure, required), evaluation context (optional), and evaluation options (optional), which returns the flag value.") - @Specification(number="1.3.3.1", text="The client SHOULD provide functions for floating-point numbers and integers, consistent with language idioms.") - @Test void value_flags() { + @Specification( + number = "1.3.1.1", + text = + "The client MUST provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters flag key (string, required), default value (boolean | number | string | structure, required), evaluation context (optional), and evaluation options (optional), which returns the flag value.") + @Specification( + number = "1.3.3.1", + text = + "The client SHOULD provide functions for floating-point numbers and integers, consistent with language idioms.") + @Test + void value_flags() { FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider()); Client c = api.getClient(); @@ -162,32 +205,80 @@ void getApiInstance() { assertEquals(true, c.getBooleanValue(key, false)); assertEquals(true, c.getBooleanValue(key, false, new ImmutableContext())); - assertEquals(true, c.getBooleanValue(key, false, new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + true, + c.getBooleanValue( + key, + false, + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); assertEquals("gnirts-ym", c.getStringValue(key, "my-string")); assertEquals("gnirts-ym", c.getStringValue(key, "my-string", new ImmutableContext())); - assertEquals("gnirts-ym", c.getStringValue(key, "my-string", new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + "gnirts-ym", + c.getStringValue( + key, + "my-string", + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); assertEquals(400, c.getIntegerValue(key, 4)); assertEquals(400, c.getIntegerValue(key, 4, new ImmutableContext())); - assertEquals(400, c.getIntegerValue(key, 4, new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + 400, + c.getIntegerValue( + key, + 4, + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); assertEquals(40.0, c.getDoubleValue(key, .4)); assertEquals(40.0, c.getDoubleValue(key, .4, new ImmutableContext())); - assertEquals(40.0, c.getDoubleValue(key, .4, new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + 40.0, + c.getDoubleValue( + key, + .4, + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); assertEquals(null, c.getObjectValue(key, new Value())); assertEquals(null, c.getObjectValue(key, new Value(), new ImmutableContext())); - assertEquals(null, c.getObjectValue(key, new Value(), new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + null, + c.getObjectValue( + key, + new Value(), + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); } - @Specification(number="1.4.1.1", text="The client MUST provide methods for detailed flag value evaluation with parameters flag key (string, required), default value (boolean | number | string | structure, required), evaluation context (optional), and evaluation options (optional), which returns an evaluation details structure.") - @Specification(number="1.4.3", text="The evaluation details structure's value field MUST contain the evaluated flag value.") - @Specification(number="1.4.4.1", text="The evaluation details structure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped value field.") - @Specification(number="1.4.5", text="The evaluation details structure's flag key field MUST contain the flag key argument passed to the detailed flag evaluation method.") - @Specification(number="1.4.6", text="In cases of normal execution, the evaluation details structure's variant field MUST contain the value of the variant field in the flag resolution structure returned by the configured provider, if the field is set.") - @Specification(number="1.4.7", text="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() { + @Specification( + number = "1.4.1.1", + text = + "The client MUST provide methods for detailed flag value evaluation with parameters flag key (string, required), default value (boolean | number | string | structure, required), evaluation context (optional), and evaluation options (optional), which returns an evaluation details structure.") + @Specification( + number = "1.4.3", + text = "The evaluation details structure's value field MUST contain the evaluated flag value.") + @Specification( + number = "1.4.4.1", + text = + "The evaluation details structure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped value field.") + @Specification( + number = "1.4.5", + text = + "The evaluation details structure's flag key field MUST contain the flag key argument passed to the detailed flag evaluation method.") + @Specification( + number = "1.4.6", + text = + "In cases of normal execution, the evaluation details structure's variant field MUST contain the value of the variant field in the flag resolution structure returned by the configured provider, if the field is set.") + @Specification( + number = "1.4.7", + text = + "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()); Client c = api.getClient(); String key = "key"; @@ -200,7 +291,13 @@ void getApiInstance() { .build(); assertEquals(bd, c.getBooleanDetails(key, true)); assertEquals(bd, c.getBooleanDetails(key, true, new ImmutableContext())); - assertEquals(bd, c.getBooleanDetails(key, true, new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + bd, + c.getBooleanDetails( + key, + true, + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); FlagEvaluationDetails sd = FlagEvaluationDetails.builder() .flagKey(key) @@ -210,7 +307,13 @@ void getApiInstance() { .build(); assertEquals(sd, c.getStringDetails(key, "test")); assertEquals(sd, c.getStringDetails(key, "test", new ImmutableContext())); - assertEquals(sd, c.getStringDetails(key, "test", new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + sd, + c.getStringDetails( + key, + "test", + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); FlagEvaluationDetails id = FlagEvaluationDetails.builder() .flagKey(key) @@ -219,7 +322,13 @@ void getApiInstance() { .build(); assertEquals(id, c.getIntegerDetails(key, 4)); assertEquals(id, c.getIntegerDetails(key, 4, new ImmutableContext())); - assertEquals(id, c.getIntegerDetails(key, 4, new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + id, + c.getIntegerDetails( + key, + 4, + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); FlagEvaluationDetails dd = FlagEvaluationDetails.builder() .flagKey(key) @@ -228,30 +337,55 @@ void getApiInstance() { .build(); assertEquals(dd, c.getDoubleDetails(key, .4)); assertEquals(dd, c.getDoubleDetails(key, .4, new ImmutableContext())); - assertEquals(dd, c.getDoubleDetails(key, .4, new ImmutableContext(), FlagEvaluationOptions.builder().build())); + assertEquals( + dd, + c.getDoubleDetails( + key, + .4, + new ImmutableContext(), + FlagEvaluationOptions.builder().build())); // TODO: Structure detail tests. } - @Specification(number="1.5.1", text="The evaluation options structure's hooks field denotes an ordered collection of hooks that the client MUST execute for the respective flag evaluation, in addition to those already configured.") + @Specification( + number = "1.5.1", + text = + "The evaluation options structure's hooks field denotes an ordered collection of hooks that the client MUST execute for the respective flag evaluation, in addition to those already configured.") @SneakyThrows - @Test void hooks() { + @Test + void hooks() { Client c = _initializedClient(); Hook clientHook = mockBooleanHook(); Hook invocationHook = mockBooleanHook(); c.addHooks(clientHook); - c.getBooleanValue("key", false, null, FlagEvaluationOptions.builder() - .hook(invocationHook) - .build()); + c.getBooleanValue( + "key", + false, + null, + FlagEvaluationOptions.builder().hook(invocationHook).build()); verify(clientHook, times(1)).before(any(), any()); verify(invocationHook, times(1)).before(any(), any()); } - @Specification(number="1.4.8", text="In cases of abnormal execution, the `evaluation details` structure's `error code` field **MUST** contain an `error code`.") - @Specification(number="1.4.9", text="In cases of abnormal execution (network failure, unhandled error, etc) the `reason` field in the `evaluation details` SHOULD indicate an error.") - @Specification(number="1.4.10", text="Methods, functions, or operations on the client MUST NOT throw exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the `default value` in the event of abnormal execution. Exceptions include functions or methods for the purposes for configuration or setup.") - @Specification(number="1.4.13", 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() { + @Specification( + number = "1.4.8", + text = + "In cases of abnormal execution, the `evaluation details` structure's `error code` field **MUST** contain an `error code`.") + @Specification( + number = "1.4.9", + text = + "In cases of abnormal execution (network failure, unhandled error, etc) the `reason` field in the `evaluation details` SHOULD indicate an error.") + @Specification( + number = "1.4.10", + text = + "Methods, functions, or operations on the client MUST NOT throw exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the `default value` in the event of abnormal execution. Exceptions include functions or methods for the purposes for configuration or setup.") + @Specification( + number = "1.4.13", + 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() { FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider()); Client c = api.getClient(); boolean defaultValue = false; @@ -263,11 +397,24 @@ void getApiInstance() { assertEquals(defaultValue, details.getValue()); } - @Specification(number="1.4.8", text="In cases of abnormal execution, the `evaluation details` structure's `error code` field **MUST** contain an `error code`.") - @Specification(number="1.4.9", text="In cases of abnormal execution (network failure, unhandled error, etc) the `reason` field in the `evaluation details` SHOULD indicate an error.") - @Specification(number="1.4.10", text="Methods, functions, or operations on the client MUST NOT throw exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the `default value` in the event of abnormal execution. Exceptions include functions or methods for the purposes for configuration or setup.") - @Specification(number="1.4.13", 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() { + @Specification( + number = "1.4.8", + text = + "In cases of abnormal execution, the `evaluation details` structure's `error code` field **MUST** contain an `error code`.") + @Specification( + number = "1.4.9", + text = + "In cases of abnormal execution (network failure, unhandled error, etc) the `reason` field in the `evaluation details` SHOULD indicate an error.") + @Specification( + number = "1.4.10", + text = + "Methods, functions, or operations on the client MUST NOT throw exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the `default value` in the event of abnormal execution. Exceptions include functions or methods for the purposes for configuration or setup.") + @Specification( + number = "1.4.13", + 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()); Client c = api.getClient(); boolean defaultValue = false; @@ -279,21 +426,25 @@ void getApiInstance() { assertEquals(defaultValue, details.getValue()); } - @Specification(number="1.4.11", text="Methods, functions, or operations on the client SHOULD NOT write log messages.") - @Test void log_on_error() throws NotImplementedException { + @Specification( + number = "1.4.11", + text = "Methods, functions, or operations on the client SHOULD NOT write log messages.") + @Test + void log_on_error() throws NotImplementedException { FeatureProviderTestUtils.setFeatureProvider(new AlwaysBrokenProvider()); Client c = api.getClient(); FlagEvaluationDetails result = c.getBooleanDetails("test", false); assertEquals(Reason.ERROR.toString(), result.getReason()); - Mockito.verify(logger, never()).error( - any(String.class), - any(), - any()); + Mockito.verify(logger, never()).error(any(String.class), any(), any()); } - @Specification(number="1.2.2", text="The client interface MUST define a metadata member or accessor, containing an immutable domain field or accessor of type string, which corresponds to the domain value supplied during client creation. In previous drafts, this property was called name. For backwards compatibility, implementations should consider name an alias to domain.") - @Test void clientMetadata() { + @Specification( + number = "1.2.2", + text = + "The client interface MUST define a metadata member or accessor, containing an immutable domain field or accessor of type string, which corresponds to the domain value supplied during client creation. In previous drafts, this property was called name. For backwards compatibility, implementations should consider name an alias to domain.") + @Test + void clientMetadata() { Client c = _client(); assertNull(c.getMetadata().getName()); assertNull(c.getMetadata().getDomain()); @@ -306,27 +457,36 @@ void getApiInstance() { assertEquals(domainName, c2.getMetadata().getDomain()); } - @Specification(number="1.4.9", text="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() { + @Specification( + number = "1.4.9", + text = + "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()); Client c = api.getClient(); FlagEvaluationDetails result = c.getBooleanDetails("test", false); assertEquals(Reason.ERROR.toString(), result.getReason()); } - @Specification(number="1.4.14", text="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() { + @Specification( + number = "1.4.14", + text = + "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)); Client c = api.getClient(); FlagEvaluationDetails result = c.getBooleanDetails("test", false); assertNotNull(result.getFlagMetadata()); } - @Specification(number="3.2.2.1", text="The API MUST have a method for setting the global evaluation context.") - @Test void api_context() { + @Specification(number = "3.2.2.1", text = "The API MUST have a method for setting the global evaluation context.") + @Test + void api_context() { String contextKey = "some-key"; String contextValue = "some-value"; - DoSomethingProvider provider = spy( new DoSomethingProvider()); + DoSomethingProvider provider = spy(new DoSomethingProvider()); FeatureProviderTestUtils.setFeatureProvider(provider); Map attributes = new HashMap<>(); @@ -339,12 +499,20 @@ void getApiInstance() { client.getBooleanValue("any-flag", false); // assert that the value from the global context was passed to the provider - verify(provider).getBooleanEvaluation(any(), any(), argThat((arg) -> arg.getValue(contextKey).asString().equals(contextValue))); + verify(provider).getBooleanEvaluation(any(), any(), argThat((arg) -> arg.getValue(contextKey) + .asString() + .equals(contextValue))); } - @Specification(number="3.2.1.1", text="The API, Client and invocation MUST have a method for supplying evaluation context.") - @Specification(number="3.2.3", text="Evaluation context MUST be merged in the order: API (global; lowest precedence) -> transaction -> client -> invocation -> before hooks (highest precedence), with duplicate values being overwritten.") - @Test void multi_layer_context_merges_correctly() { + @Specification( + number = "3.2.1.1", + text = "The API, Client and invocation MUST have a method for supplying evaluation context.") + @Specification( + number = "3.2.3", + text = + "Evaluation context MUST be merged in the order: API (global; lowest precedence) -> transaction -> client -> invocation -> before hooks (highest precedence), with duplicate values being overwritten.") + @Test + void multi_layer_context_merges_correctly() { DoSomethingProvider provider = spy(new DoSomethingProvider()); FeatureProviderTestUtils.setFeatureProvider(provider); TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator(); @@ -357,8 +525,10 @@ public Optional before(HookContext ctx, Map ctx, FlagEvaluationDetails details, Map hints) { + public void after( + HookContext ctx, FlagEvaluationDetails details, Map hints) { Hook.super.after(ctx, details, hints); } }); @@ -404,59 +574,133 @@ public void after(HookContext ctx, FlagEvaluationDetails detai invocationAttributes.put("invocation", new Value("4")); EvaluationContext invocationCtx = new ImmutableContext(invocationAttributes); - c.getBooleanValue("key", false, invocationCtx, FlagEvaluationOptions.builder().hook(hook).build()); + c.getBooleanValue( + "key", + false, + invocationCtx, + FlagEvaluationOptions.builder().hook(hook).build()); // assert the correct overrides in before hook - verify(hook).before(argThat((arg) -> { - EvaluationContext evaluationContext = arg.getCtx(); - return evaluationContext.getValue("api").asString().equals("1") && - evaluationContext.getValue("transaction").asString().equals("2") && - evaluationContext.getValue("client").asString().equals("3") && - evaluationContext.getValue("invocation").asString().equals("4") && - evaluationContext.getValue("common1").asString().equals("2") && - evaluationContext.getValue("common2").asString().equals("3") && - evaluationContext.getValue("common3").asString().equals("4") && - evaluationContext.getValue("common4").asString().equals("3") && - evaluationContext.getValue("common5").asString().equals("4") && - evaluationContext.getValue("common6").asString().equals("4"); - }), any()); + verify(hook) + .before( + argThat((arg) -> { + EvaluationContext evaluationContext = arg.getCtx(); + return evaluationContext.getValue("api").asString().equals("1") + && evaluationContext + .getValue("transaction") + .asString() + .equals("2") + && evaluationContext + .getValue("client") + .asString() + .equals("3") + && evaluationContext + .getValue("invocation") + .asString() + .equals("4") + && evaluationContext + .getValue("common1") + .asString() + .equals("2") + && evaluationContext + .getValue("common2") + .asString() + .equals("3") + && evaluationContext + .getValue("common3") + .asString() + .equals("4") + && evaluationContext + .getValue("common4") + .asString() + .equals("3") + && evaluationContext + .getValue("common5") + .asString() + .equals("4") + && evaluationContext + .getValue("common6") + .asString() + .equals("4"); + }), + any()); // assert the correct overrides in evaluation verify(provider).getBooleanEvaluation(any(), any(), argThat((arg) -> { - return arg.getValue("api").asString().equals("1") && - arg.getValue("transaction").asString().equals("2") && - arg.getValue("client").asString().equals("3") && - arg.getValue("invocation").asString().equals("4") && - arg.getValue("before").asString().equals("5") && - arg.getValue("common1").asString().equals("2") && - arg.getValue("common2").asString().equals("3") && - arg.getValue("common3").asString().equals("4") && - arg.getValue("common4").asString().equals("3") && - arg.getValue("common5").asString().equals("4") && - arg.getValue("common6").asString().equals("4") && - arg.getValue("common7").asString().equals("5"); + return arg.getValue("api").asString().equals("1") + && arg.getValue("transaction").asString().equals("2") + && arg.getValue("client").asString().equals("3") + && arg.getValue("invocation").asString().equals("4") + && arg.getValue("before").asString().equals("5") + && arg.getValue("common1").asString().equals("2") + && arg.getValue("common2").asString().equals("3") + && arg.getValue("common3").asString().equals("4") + && arg.getValue("common4").asString().equals("3") + && arg.getValue("common5").asString().equals("4") + && arg.getValue("common6").asString().equals("4") + && arg.getValue("common7").asString().equals("5"); })); // assert the correct overrides in after hook - verify(hook).after(argThat((arg) -> { - EvaluationContext evaluationContext = arg.getCtx(); - return evaluationContext.getValue("api").asString().equals("1") && - evaluationContext.getValue("transaction").asString().equals("2") && - evaluationContext.getValue("client").asString().equals("3") && - evaluationContext.getValue("invocation").asString().equals("4") && - evaluationContext.getValue("before").asString().equals("5") && - evaluationContext.getValue("common1").asString().equals("2") && - evaluationContext.getValue("common2").asString().equals("3") && - evaluationContext.getValue("common3").asString().equals("4") && - evaluationContext.getValue("common4").asString().equals("3") && - evaluationContext.getValue("common5").asString().equals("4") && - evaluationContext.getValue("common6").asString().equals("4") && - evaluationContext.getValue("common7").asString().equals("5"); - }), any(), any()); + verify(hook) + .after( + argThat((arg) -> { + EvaluationContext evaluationContext = arg.getCtx(); + return evaluationContext.getValue("api").asString().equals("1") + && evaluationContext + .getValue("transaction") + .asString() + .equals("2") + && evaluationContext + .getValue("client") + .asString() + .equals("3") + && evaluationContext + .getValue("invocation") + .asString() + .equals("4") + && evaluationContext + .getValue("before") + .asString() + .equals("5") + && evaluationContext + .getValue("common1") + .asString() + .equals("2") + && evaluationContext + .getValue("common2") + .asString() + .equals("3") + && evaluationContext + .getValue("common3") + .asString() + .equals("4") + && evaluationContext + .getValue("common4") + .asString() + .equals("3") + && evaluationContext + .getValue("common5") + .asString() + .equals("4") + && evaluationContext + .getValue("common6") + .asString() + .equals("4") + && evaluationContext + .getValue("common7") + .asString() + .equals("5"); + }), + any(), + any()); } - @Specification(number="3.3.1.1", text="The API SHOULD have a method for setting a transaction context propagator.") - @Test void setting_transaction_context_propagator() { + @Specification( + number = "3.3.1.1", + text = "The API SHOULD have a method for setting a transaction context propagator.") + @Test + void setting_transaction_context_propagator() { DoSomethingProvider provider = new DoSomethingProvider(); FeatureProviderTestUtils.setFeatureProvider(provider); @@ -465,8 +709,12 @@ public void after(HookContext ctx, FlagEvaluationDetails detai assertEquals(transactionContextPropagator, api.getTransactionContextPropagator()); } - @Specification(number="3.3.1.2.1", text="The API MUST have a method for setting the evaluation context of the transaction context propagator for the current transaction.") - @Test void setting_transaction_context() { + @Specification( + number = "3.3.1.2.1", + text = + "The API MUST have a method for setting the evaluation context of the transaction context propagator for the current transaction.") + @Test + void setting_transaction_context() { DoSomethingProvider provider = new DoSomethingProvider(); FeatureProviderTestUtils.setFeatureProvider(provider); @@ -481,9 +729,16 @@ public void after(HookContext ctx, FlagEvaluationDetails detai assertEquals(transactionContext, transactionContextPropagator.getTransactionContext()); } - @Specification(number="3.3.1.2.2", text="A transaction context propagator MUST have a method for setting the evaluation context of the current transaction.") - @Specification(number="3.3.1.2.3", text="A transaction context propagator MUST have a method for getting the evaluation context of the current transaction.") - @Test void transaction_context_propagator_setting_context() { + @Specification( + number = "3.3.1.2.2", + text = + "A transaction context propagator MUST have a method for setting the evaluation context of the current transaction.") + @Specification( + number = "3.3.1.2.3", + text = + "A transaction context propagator MUST have a method for getting the evaluation context of the current transaction.") + @Test + void transaction_context_propagator_setting_context() { TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator(); Map attributes = new HashMap<>(); @@ -494,23 +749,46 @@ public void after(HookContext ctx, FlagEvaluationDetails detai assertEquals(transactionContext, transactionContextPropagator.getTransactionContext()); } - @Specification(number="1.3.4", text="The client SHOULD guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied default value should be returned.") - @Test void type_system_prevents_this() {} - - @Specification(number="1.1.7", text="The client creation function MUST NOT throw, or otherwise abnormally terminate.") - @Test void constructor_does_not_throw() {} - - @Specification(number="1.4.12", text="The client SHOULD provide asynchronous or non-blocking mechanisms for flag evaluation.") - @Test void one_thread_per_request_model() {} - - @Specification(number="1.4.14.1", text="Condition: Flag metadata MUST be immutable.") - @Test void compiler_enforced() {} - - @Specification(number="1.4.2.1", text="The client MUST provide methods for detailed flag value evaluation with parameters flag key (string, required), default value (boolean | number | string | structure, required), and evaluation options (optional), which returns an evaluation details structure.") - @Specification(number="1.3.2.1", text="The client MUST provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters flag key (string, required), default value (boolean | number | string | structure, required), and evaluation options (optional), which returns the flag value.") - @Specification(number="3.2.2.2", text="The Client and invocation MUST NOT have a method for supplying evaluation context.") - @Specification(number="3.2.4.1", text="When the global evaluation context is set, the on context changed handler MUST run.") - @Specification(number="3.3.2.1", text="The API MUST NOT have a method for setting a transaction context propagator.") - @Test void not_applicable_for_dynamic_context() {} - + @Specification( + number = "1.3.4", + text = + "The client SHOULD guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied default value should be returned.") + @Test + void type_system_prevents_this() {} + + @Specification( + number = "1.1.7", + text = "The client creation function MUST NOT throw, or otherwise abnormally terminate.") + @Test + void constructor_does_not_throw() {} + + @Specification( + number = "1.4.12", + text = "The client SHOULD provide asynchronous or non-blocking mechanisms for flag evaluation.") + @Test + void one_thread_per_request_model() {} + + @Specification(number = "1.4.14.1", text = "Condition: Flag metadata MUST be immutable.") + @Test + void compiler_enforced() {} + + @Specification( + number = "1.4.2.1", + text = + "The client MUST provide methods for detailed flag value evaluation with parameters flag key (string, required), default value (boolean | number | string | structure, required), and evaluation options (optional), which returns an evaluation details structure.") + @Specification( + number = "1.3.2.1", + text = + "The client MUST provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters flag key (string, required), default value (boolean | number | string | structure, required), and evaluation options (optional), which returns the flag value.") + @Specification( + number = "3.2.2.2", + text = "The Client and invocation MUST NOT have a method for supplying evaluation context.") + @Specification( + number = "3.2.4.1", + text = "When the global evaluation context is set, the on context changed handler MUST run.") + @Specification( + number = "3.3.2.1", + text = "The API MUST NOT have a method for setting a transaction context propagator.") + @Test + void not_applicable_for_dynamic_context() {} } diff --git a/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java b/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java index c300daa0..f8b9ba58 100644 --- a/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java +++ b/src/test/java/dev/openfeature/sdk/FlagMetadataTest.java @@ -1,10 +1,10 @@ package dev.openfeature.sdk; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class FlagMetadataTest { @Test @@ -44,9 +44,8 @@ public void builder_validation() { @DisplayName("Value type mismatch returns a null") public void value_type_validation() { // given - ImmutableMetadata flagMetadata = ImmutableMetadata.builder() - .addString("string", "string") - .build(); + ImmutableMetadata flagMetadata = + ImmutableMetadata.builder().addString("string", "string").build(); // then assertThat(flagMetadata.getBoolean("string")).isNull(); diff --git a/src/test/java/dev/openfeature/sdk/HookContextTest.java b/src/test/java/dev/openfeature/sdk/HookContextTest.java index 50cc6617..2196b8b1 100644 --- a/src/test/java/dev/openfeature/sdk/HookContextTest.java +++ b/src/test/java/dev/openfeature/sdk/HookContextTest.java @@ -1,30 +1,32 @@ package dev.openfeature.sdk; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; + class HookContextTest { - @Specification(number="4.2.2.2", text="Condition: The client metadata field in the hook context MUST be immutable.") - @Specification(number="4.2.2.3", text="Condition: The provider metadata field in the hook context MUST be immutable.") - @Test void metadata_field_is_type_metadata() { + @Specification( + number = "4.2.2.2", + text = "Condition: The client metadata field in the hook context MUST be immutable.") + @Specification( + number = "4.2.2.3", + text = "Condition: The provider metadata field in the hook context MUST be immutable.") + @Test + void metadata_field_is_type_metadata() { ClientMetadata clientMetadata = mock(ClientMetadata.class); Metadata meta = mock(Metadata.class); - HookContext hc = HookContext.from( - "key", - FlagValueType.BOOLEAN, - clientMetadata, - meta, - new ImmutableContext(), - false - ); + HookContext hc = + HookContext.from("key", FlagValueType.BOOLEAN, clientMetadata, meta, new ImmutableContext(), false); assertTrue(ClientMetadata.class.isAssignableFrom(hc.getClientMetadata().getClass())); assertTrue(Metadata.class.isAssignableFrom(hc.getProviderMetadata().getClass())); } - @Specification(number="4.3.3.1", text="The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters. It has no return value.") - @Test void not_applicable_for_dynamic_context() {} - -} \ No newline at end of file + @Specification( + number = "4.3.3.1", + text = + "The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters. It has no return value.") + @Test + void not_applicable_for_dynamic_context() {} +} diff --git a/src/test/java/dev/openfeature/sdk/HookSpecTest.java b/src/test/java/dev/openfeature/sdk/HookSpecTest.java index 4609c8d5..4a141c61 100644 --- a/src/test/java/dev/openfeature/sdk/HookSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/HookSpecTest.java @@ -1,23 +1,28 @@ package dev.openfeature.sdk; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + 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; +import java.util.Collections; +import java.util.HashMap; +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.Test; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - class HookSpecTest implements HookFixtures { @AfterEach void emptyApiHooks() { @@ -25,7 +30,10 @@ void emptyApiHooks() { OpenFeatureAPI.getInstance().clearHooks(); } - @Specification(number = "4.1.3", text = "The flag key, flag type, and default value properties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties.") + @Specification( + number = "4.1.3", + text = + "The flag key, flag type, and default value properties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties.") @Test void immutableValues() { try { @@ -50,7 +58,10 @@ void immutableValues() { } } - @Specification(number = "4.1.1", text = "Hook context MUST provide: the flag key, flag value type, evaluation context, and the default value.") + @Specification( + number = "4.1.1", + text = + "Hook context MUST provide: the flag key, flag value type, evaluation context, and the default value.") @Test void nullish_properties_on_hookcontext() { // missing ctx @@ -112,10 +123,11 @@ void nullish_properties_on_hookcontext() { } catch (NullPointerException e) { fail("NPE after we provided all relevant info"); } - } - @Specification(number = "4.1.2", text = "The hook context SHOULD provide: access to the client metadata and the provider metadata fields.") + @Specification( + number = "4.1.2", + text = "The hook context SHOULD provide: access to the client metadata and the provider metadata fields.") @Test void optional_properties() { // don't specify @@ -145,7 +157,10 @@ void optional_properties() { .build(); } - @Specification(number = "4.3.2.1", text = "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.") + @Specification( + number = "4.3.2.1", + text = + "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(); @@ -153,7 +168,10 @@ void before_runs_ahead_of_evaluation() { Client client = api.getClient(); Hook evalHook = mockBooleanHook(); - client.getBooleanValue("key", false, new ImmutableContext(), + client.getBooleanValue( + "key", + false, + new ImmutableContext(), FlagEvaluationOptions.builder().hook(evalHook).build()); verify(evalHook, times(1)).before(any(), any()); @@ -161,8 +179,7 @@ void before_runs_ahead_of_evaluation() { @Test void feo_has_hook_list() { - FlagEvaluationOptions feo = FlagEvaluationOptions.builder() - .build(); + FlagEvaluationOptions feo = FlagEvaluationOptions.builder().build(); assertNotNull(feo.getHooks()); } @@ -175,7 +192,6 @@ void error_hook_run_during_non_finally_stage() { verify(h, times(0)).error(any(), any(), any()); } - @Test void error_hook_must_run_if_resolution_details_returns_an_error_code() { @@ -184,18 +200,20 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() { EvaluationContext invocationCtx = new ImmutableContext(); Hook hook = mockBooleanHook(); FeatureProvider provider = mock(FeatureProvider.class); - when(provider.getBooleanEvaluation(any(), any(), any())).thenReturn(ProviderEvaluation.builder() - .errorCode(ErrorCode.FLAG_NOT_FOUND) - .errorMessage(errorMessage) - .build()); + when(provider.getBooleanEvaluation(any(), any(), any())) + .thenReturn(ProviderEvaluation.builder() + .errorCode(ErrorCode.FLAG_NOT_FOUND) + .errorMessage(errorMessage) + .build()); OpenFeatureAPI api = OpenFeatureAPI.getInstance(); FeatureProviderTestUtils.setFeatureProvider("errorHookMustRun", provider); Client client = api.getClient("errorHookMustRun"); - client.getBooleanValue("key", false, invocationCtx, - FlagEvaluationOptions.builder() - .hook(hook) - .build()); + client.getBooleanValue( + "key", + false, + invocationCtx, + FlagEvaluationOptions.builder().hook(hook).build()); ArgumentCaptor captor = ArgumentCaptor.forClass(Exception.class); @@ -209,12 +227,25 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() { assertInstanceOf(FlagNotFoundError.class, exception); } - - @Specification(number = "4.3.6", text = "The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.") - @Specification(number = "4.3.7", text = "The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.") - @Specification(number = "4.3.8", text = "The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.") - @Specification(number = "4.4.1", text = "The API, Client, Provider, and invocation MUST have a method for registering hooks.") - @Specification(number = "4.4.2", text = "Hooks MUST be evaluated in the following order: - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API") + @Specification( + number = "4.3.6", + text = + "The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.") + @Specification( + number = "4.3.7", + text = + "The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.") + @Specification( + number = "4.3.8", + text = + "The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.") + @Specification( + number = "4.4.1", + text = "The API, Client, Provider, and invocation MUST have a method for registering hooks.") + @Specification( + number = "4.4.2", + text = + "Hooks MUST be evaluated in the following order: - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API") @Test void hook_eval_order() { List evalOrder = new ArrayList<>(); @@ -230,8 +261,10 @@ public Optional before(HookContext ctx, Map ctx, FlagEvaluationDetails details, Map hints) { + public void after( + HookContext ctx, + FlagEvaluationDetails details, + Map hints) { evalOrder.add("provider after"); } @@ -255,7 +288,8 @@ public Optional before(HookContext ctx, Map ctx, FlagEvaluationDetails details, Map hints) { + public void after( + HookContext ctx, FlagEvaluationDetails details, Map hints) { evalOrder.add("api after"); throw new RuntimeException(); // trigger error flows. } @@ -280,7 +314,8 @@ public Optional before(HookContext ctx, Map ctx, FlagEvaluationDetails details, Map hints) { + public void after( + HookContext ctx, FlagEvaluationDetails details, Map hints) { evalOrder.add("client after"); } @@ -295,41 +330,63 @@ public void finallyAfter(HookContext ctx, Map hints) { } }); - c.getBooleanValue("key", false, null, FlagEvaluationOptions - .builder() - .hook(new BooleanHook() { - @Override - public Optional before(HookContext ctx, Map hints) { - evalOrder.add("invocation before"); - return null; - } - - @Override - public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) { - evalOrder.add("invocation after"); - } - - @Override - public void error(HookContext ctx, Exception error, Map hints) { - evalOrder.add("invocation error"); - } - - @Override - public void finallyAfter(HookContext ctx, Map hints) { - evalOrder.add("invocation finally"); - } - }) - .build()); + c.getBooleanValue( + "key", + false, + null, + FlagEvaluationOptions.builder() + .hook(new BooleanHook() { + @Override + public Optional before( + HookContext ctx, Map hints) { + evalOrder.add("invocation before"); + return null; + } + + @Override + public void after( + HookContext ctx, + FlagEvaluationDetails details, + Map hints) { + evalOrder.add("invocation after"); + } + + @Override + public void error(HookContext ctx, Exception error, Map hints) { + evalOrder.add("invocation error"); + } + + @Override + public void finallyAfter(HookContext ctx, Map hints) { + evalOrder.add("invocation finally"); + } + }) + .build()); List expectedOrder = Arrays.asList( - "api before", "client before", "invocation before", "provider before", - "provider after", "invocation after", "client after", "api after", - "provider error", "invocation error", "client error", "api error", - "provider finally", "invocation finally", "client finally", "api finally"); + "api before", + "client before", + "invocation before", + "provider before", + "provider after", + "invocation after", + "client after", + "api after", + "provider error", + "invocation error", + "client error", + "api error", + "provider finally", + "invocation finally", + "client finally", + "api finally"); assertEquals(expectedOrder, evalOrder); } - @Specification(number = "4.4.6", text = "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.") + @Specification( + number = "4.4.6", + text = + "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.") @Test void error_stops_before() { Hook h = mockBooleanHook(); @@ -340,15 +397,19 @@ void error_stops_before() { api.setProviderAndWait(new AlwaysBrokenProvider()); Client c = api.getClient(); - c.getBooleanDetails("key", false, null, FlagEvaluationOptions.builder() - .hook(h2) - .hook(h) - .build()); - verify(h, times(1)).before(any(), any()); - verify(h2, times(0)).before(any(), any()); + c.getBooleanDetails( + "key", + false, + null, + FlagEvaluationOptions.builder().hook(h2).hook(h).build()); + verify(h, times(1)).before(any(), any()); + verify(h2, times(0)).before(any(), any()); } - @Specification(number = "4.4.6", text = "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.") + @Specification( + number = "4.4.6", + text = + "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked.") @SneakyThrows @Test void error_stops_after() { @@ -358,15 +419,19 @@ void error_stops_after() { Client c = getClient(TestEventsProvider.newInitializedTestEventsProvider()); - c.getBooleanDetails("key", false, null, FlagEvaluationOptions.builder() - .hook(h) - .hook(h2) - .build()); + c.getBooleanDetails( + "key", + false, + null, + FlagEvaluationOptions.builder().hook(h).hook(h2).build()); verify(h, times(1)).after(any(), any(), any()); verify(h2, times(0)).after(any(), any(), any()); } - @Specification(number = "4.2.1", text = "hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure..") + @Specification( + number = "4.2.1", + text = + "hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure..") @Specification(number = "4.5.2", text = "hook hints MUST be passed to each hook.") @Specification(number = "4.2.2.1", text = "Condition: Hook hints MUST be immutable.") @Specification(number = "4.5.3", text = "The hook MUST NOT alter the hook hints structure.") @@ -378,23 +443,28 @@ void hook_hints() { Hook mutatingHook = new BooleanHook() { @Override public Optional before(HookContext ctx, Map hints) { - assertThatCode(() -> hints.put(hintKey, "changed value")).isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> hints.put(hintKey, "changed value")) + .isInstanceOf(UnsupportedOperationException.class); return Optional.empty(); } @Override - public void after(HookContext ctx, FlagEvaluationDetails details, Map hints) { - assertThatCode(() -> hints.put(hintKey, "changed value")).isInstanceOf(UnsupportedOperationException.class); + public void after( + HookContext ctx, FlagEvaluationDetails details, Map hints) { + assertThatCode(() -> hints.put(hintKey, "changed value")) + .isInstanceOf(UnsupportedOperationException.class); } @Override public void error(HookContext ctx, Exception error, Map hints) { - assertThatCode(() -> hints.put(hintKey, "changed value")).isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> hints.put(hintKey, "changed value")) + .isInstanceOf(UnsupportedOperationException.class); } @Override public void finallyAfter(HookContext ctx, Map hints) { - assertThatCode(() -> hints.put(hintKey, "changed value")).isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> hints.put(hintKey, "changed value")) + .isInstanceOf(UnsupportedOperationException.class); } }; @@ -402,13 +472,16 @@ public void finallyAfter(HookContext ctx, Map hints) { hh.put(hintKey, "My hint value"); hh = Collections.unmodifiableMap(hh); - client.getBooleanValue("key", false, new ImmutableContext(), FlagEvaluationOptions.builder() - .hook(mutatingHook) - .hookHints(hh) - .build()); + client.getBooleanValue( + "key", + false, + new ImmutableContext(), + FlagEvaluationOptions.builder().hook(mutatingHook).hookHints(hh).build()); } - @Specification(number = "4.5.1", text = "Flag evaluation options MAY contain hook hints, a map of data to be provided to hook invocations.") + @Specification( + number = "4.5.1", + text = "Flag evaluation options MAY contain hook hints, a map of data to be provided to hook invocations.") @Test void missing_hook_hints() { FlagEvaluationOptions feo = FlagEvaluationOptions.builder().build(); @@ -421,15 +494,16 @@ void flag_eval_hook_order() { Hook hook = mockBooleanHook(); FeatureProvider provider = mock(FeatureProvider.class); when(provider.getBooleanEvaluation(any(), any(), any())) - .thenReturn(ProviderEvaluation.builder() - .value(true) - .build()); + .thenReturn(ProviderEvaluation.builder().value(true).build()); InOrder order = inOrder(hook, provider); OpenFeatureAPI api = OpenFeatureAPI.getInstance(); FeatureProviderTestUtils.setFeatureProvider(provider); Client client = api.getClient(); - client.getBooleanValue("key", false, new ImmutableContext(), + client.getBooleanValue( + "key", + false, + new ImmutableContext(), FlagEvaluationOptions.builder().hook(hook).build()); order.verify(hook).before(any(), any()); @@ -438,27 +512,39 @@ void flag_eval_hook_order() { order.verify(hook).finallyAfter(any(), any()); } - @Specification(number = "4.4.5", text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked.") - @Specification(number = "4.4.7", text = "If an error occurs in the before hooks, the default value MUST be returned.") + @Specification( + number = "4.4.5", + text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked.") + @Specification( + number = "4.4.7", + text = "If an error occurs in the before hooks, the default value MUST be returned.") @Test void error_hooks__before() { Hook hook = mockBooleanHook(); doThrow(RuntimeException.class).when(hook).before(any(), any()); Client client = getClient(TestEventsProvider.newInitializedTestEventsProvider()); - Boolean value = client.getBooleanValue("key", false, new ImmutableContext(), + Boolean value = client.getBooleanValue( + "key", + false, + new ImmutableContext(), FlagEvaluationOptions.builder().hook(hook).build()); verify(hook, times(1)).before(any(), any()); verify(hook, times(1)).error(any(), any(), any()); assertEquals(false, value, "Falls through to the default."); } - @Specification(number = "4.4.5", text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked.") + @Specification( + number = "4.4.5", + text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked.") @Test void error_hooks__after() { Hook hook = mockBooleanHook(); doThrow(RuntimeException.class).when(hook).after(any(), any(), any()); Client client = getClient(TestEventsProvider.newInitializedTestEventsProvider()); - client.getBooleanValue("key", false, new ImmutableContext(), + client.getBooleanValue( + "key", + false, + new ImmutableContext(), FlagEvaluationOptions.builder().hook(hook).build()); verify(hook, times(1)).after(any(), any(), any()); verify(hook, times(1)).error(any(), any(), any()); @@ -472,11 +558,11 @@ void multi_hooks_early_out__before() { Client client = getClient(null); - client.getBooleanValue("key", false, new ImmutableContext(), - FlagEvaluationOptions.builder() - .hook(hook2) - .hook(hook) - .build()); + client.getBooleanValue( + "key", + false, + new ImmutableContext(), + FlagEvaluationOptions.builder().hook(hook2).hook(hook).build()); verify(hook, times(1)).before(any(), any()); verify(hook2, times(0)).before(any(), any()); @@ -486,7 +572,10 @@ void multi_hooks_early_out__before() { } @Specification(number = "4.1.4", text = "The evaluation context MUST be mutable only within the before hook.") - @Specification(number = "4.3.4", text = "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).") + @Specification( + number = "4.3.4", + text = + "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).") @Test void beforeContextUpdated() { String targetingKey = "test-key"; @@ -498,11 +587,11 @@ void beforeContextUpdated() { InOrder order = inOrder(hook, hook2); Client client = getClient(null); - client.getBooleanValue("key", false, ctx, - FlagEvaluationOptions.builder() - .hook(hook2) - .hook(hook) - .build()); + client.getBooleanValue( + "key", + false, + ctx, + FlagEvaluationOptions.builder().hook(hook2).hook(hook).build()); order.verify(hook).before(any(), any()); ArgumentCaptor> captor = ArgumentCaptor.forClass(HookContext.class); @@ -510,10 +599,12 @@ void beforeContextUpdated() { HookContext hc = captor.getValue(); assertEquals(hc.getCtx().getTargetingKey(), targetingKey); - } - @Specification(number = "4.3.5", text = "When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context.") + @Specification( + number = "4.3.5", + text = + "When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context.") @Test void mergeHappensCorrectly() { Map attributes = new HashMap<>(); @@ -521,7 +612,6 @@ void mergeHappensCorrectly() { attributes.put("another", new Value("exists")); EvaluationContext hookCtx = new ImmutableContext(attributes); - Map attributes1 = new HashMap<>(); attributes1.put("something", new Value("here")); attributes1.put("test", new Value("broken")); @@ -531,17 +621,17 @@ void mergeHappensCorrectly() { when(hook.before(any(), any())).thenReturn(Optional.of(hookCtx)); FeatureProvider provider = mock(FeatureProvider.class); - when(provider.getBooleanEvaluation(any(), any(), any())).thenReturn(ProviderEvaluation.builder() - .value(true) - .build()); + when(provider.getBooleanEvaluation(any(), any(), any())) + .thenReturn(ProviderEvaluation.builder().value(true).build()); OpenFeatureAPI api = OpenFeatureAPI.getInstance(); FeatureProviderTestUtils.setFeatureProvider(provider); Client client = api.getClient(); - client.getBooleanValue("key", false, invocationCtx, - FlagEvaluationOptions.builder() - .hook(hook) - .build()); + client.getBooleanValue( + "key", + false, + invocationCtx, + FlagEvaluationOptions.builder().hook(hook).build()); ArgumentCaptor captor = ArgumentCaptor.forClass(ImmutableContext.class); verify(provider).getBooleanEvaluation(any(), any(), captor.capture()); @@ -551,7 +641,10 @@ void mergeHappensCorrectly() { assertEquals("here", ec.getValue("something").asString()); } - @Specification(number = "4.4.3", text = "If a finally hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining finally hooks.") + @Specification( + number = "4.4.3", + text = + "If a finally hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining finally hooks.") @Test void first_finally_broken() { Hook hook = mockBooleanHook(); @@ -561,18 +654,21 @@ void first_finally_broken() { InOrder order = inOrder(hook, hook2); Client client = getClient(null); - client.getBooleanValue("key", false, new ImmutableContext(), - FlagEvaluationOptions.builder() - .hook(hook2) - .hook(hook) - .build()); + client.getBooleanValue( + "key", + false, + new ImmutableContext(), + FlagEvaluationOptions.builder().hook(hook2).hook(hook).build()); order.verify(hook).before(any(), any()); order.verify(hook2).finallyAfter(any(), any()); order.verify(hook).finallyAfter(any(), any()); } - @Specification(number = "4.4.4", text = "If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks.") + @Specification( + number = "4.4.4", + text = + "If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks.") @Test void first_error_broken() { Hook hook = mockBooleanHook(); @@ -582,11 +678,11 @@ void first_error_broken() { InOrder order = inOrder(hook, hook2); Client client = getClient(null); - client.getBooleanValue("key", false, new ImmutableContext(), - FlagEvaluationOptions.builder() - .hook(hook2) - .hook(hook) - .build()); + client.getBooleanValue( + "key", + false, + new ImmutableContext(), + FlagEvaluationOptions.builder().hook(hook2).hook(hook).build()); order.verify(hook).before(any(), any()); order.verify(hook2).error(any(), any(), any()); @@ -605,8 +701,7 @@ private Client getClient(FeatureProvider provider) { @Specification(number = "4.3.1", text = "Hooks MUST specify at least one stage.") @Test - void default_methods_so_impossible() { - } + void default_methods_so_impossible() {} @Specification(number = "4.3.9.1", text = "Instead of finally, finallyAfter SHOULD be used.") @SneakyThrows @@ -619,5 +714,4 @@ void doesnt_use_finally() { assertThatCode(() -> Hook.class.getMethod("finallyAfter", HookContext.class, Map.class)) .doesNotThrowAnyException(); } - } diff --git a/src/test/java/dev/openfeature/sdk/HookSupportTest.java b/src/test/java/dev/openfeature/sdk/HookSupportTest.java index bf6501dd..73256ab5 100644 --- a/src/test/java/dev/openfeature/sdk/HookSupportTest.java +++ b/src/test/java/dev/openfeature/sdk/HookSupportTest.java @@ -5,19 +5,17 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import dev.openfeature.sdk.fixtures.HookFixtures; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import dev.openfeature.sdk.fixtures.HookFixtures; - class HookSupportTest implements HookFixtures { @Test @DisplayName("should merge EvaluationContexts on before hooks correctly") @@ -25,14 +23,16 @@ void shouldMergeEvaluationContextsOnBeforeHooksCorrectly() { Map attributes = new HashMap<>(); attributes.put("baseKey", new Value("baseValue")); EvaluationContext baseContext = new ImmutableContext(attributes); - HookContext hookContext = new HookContext<>("flagKey", FlagValueType.STRING, "defaultValue", baseContext, () -> "client", () -> "provider"); + HookContext hookContext = new HookContext<>( + "flagKey", FlagValueType.STRING, "defaultValue", baseContext, () -> "client", () -> "provider"); Hook hook1 = mockStringHook(); Hook hook2 = mockStringHook(); when(hook1.before(any(), any())).thenReturn(Optional.of(evaluationContextWithValue("bla", "blubber"))); when(hook2.before(any(), any())).thenReturn(Optional.of(evaluationContextWithValue("foo", "bar"))); HookSupport hookSupport = new HookSupport(); - EvaluationContext result = hookSupport.beforeHooks(FlagValueType.STRING, hookContext, Arrays.asList(hook1, hook2), Collections.emptyMap()); + EvaluationContext result = hookSupport.beforeHooks( + FlagValueType.STRING, hookContext, Arrays.asList(hook1, hook2), Collections.emptyMap()); assertThat(result.getValue("bla").asString()).isEqualTo("blubber"); assertThat(result.getValue("foo").asString()).isEqualTo("bar"); @@ -47,12 +47,30 @@ void shouldAlwaysCallGenericHook(FlagValueType flagValueType) { HookSupport hookSupport = new HookSupport(); EvaluationContext baseContext = new ImmutableContext(); IllegalStateException expectedException = new IllegalStateException("All fine, just a test"); - HookContext hookContext = new HookContext<>("flagKey", flagValueType, createDefaultValue(flagValueType), baseContext, () -> "client", () -> "provider"); + HookContext hookContext = new HookContext<>( + "flagKey", + flagValueType, + createDefaultValue(flagValueType), + baseContext, + () -> "client", + () -> "provider"); - hookSupport.beforeHooks(flagValueType, hookContext, Collections.singletonList(genericHook), Collections.emptyMap()); - hookSupport.afterHooks(flagValueType, hookContext, FlagEvaluationDetails.builder().build(), Collections.singletonList(genericHook), Collections.emptyMap()); - hookSupport.afterAllHooks(flagValueType, hookContext, Collections.singletonList(genericHook), Collections.emptyMap()); - hookSupport.errorHooks(flagValueType, hookContext, expectedException, Collections.singletonList(genericHook), Collections.emptyMap()); + hookSupport.beforeHooks( + flagValueType, hookContext, Collections.singletonList(genericHook), Collections.emptyMap()); + hookSupport.afterHooks( + flagValueType, + hookContext, + FlagEvaluationDetails.builder().build(), + Collections.singletonList(genericHook), + Collections.emptyMap()); + hookSupport.afterAllHooks( + flagValueType, hookContext, Collections.singletonList(genericHook), Collections.emptyMap()); + hookSupport.errorHooks( + flagValueType, + hookContext, + expectedException, + Collections.singletonList(genericHook), + Collections.emptyMap()); verify(genericHook).before(any(), any()); verify(genericHook).after(any(), any(), any()); @@ -83,5 +101,4 @@ private EvaluationContext evaluationContextWithValue(String key, String value) { EvaluationContext baseContext = new ImmutableContext(attributes); return baseContext; } - } diff --git a/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java b/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java index 44a6f479..e69a974b 100644 --- a/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java +++ b/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java @@ -1,18 +1,16 @@ package dev.openfeature.sdk; -import java.util.Collections; -import java.util.Map; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; - 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + class ImmutableContextTest { @DisplayName("attributes unable to allow mutation should not affect the immutable context") @Test @@ -23,7 +21,8 @@ void shouldNotAttemptToModifyAttributesForImmutableContext() { // should check the usage of Map.of() which is a more likely use case, but that API isn't available in Java 8 EvaluationContext ctx = new ImmutableContext("targeting key", Collections.unmodifiableMap(attributes)); attributes.put("key3", new Value("val3")); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); } @DisplayName("attributes mutation should not affect the immutable context") @@ -34,7 +33,8 @@ void shouldCreateCopyOfAttributesForImmutableContext() { attributes.put("key2", new Value("val2")); EvaluationContext ctx = new ImmutableContext("targeting key", attributes); attributes.put("key3", new Value("val3")); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); } @DisplayName("targeting key should be changed from the overriding context") @@ -60,6 +60,7 @@ void shouldRetainTargetingKeyWhenOverridingContextTargetingKeyValueIsEmpty() { EvaluationContext merge = ctx.merge(overriding); assertEquals("targeting_key", merge.getTargetingKey()); } + @DisplayName("missing targeting key should return null") @Test void missingTargetingKeyShould() { @@ -76,10 +77,12 @@ void mergeShouldReturnAllTheValuesFromTheContextWhenOverridingContextIsNull() { EvaluationContext ctx = new ImmutableContext("targeting_key", attributes); EvaluationContext merge = ctx.merge(null); assertEquals("targeting_key", merge.getTargetingKey()); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); } - @DisplayName("Merge should retain subkeys from the existing context when the overriding context has the same targeting key") + @DisplayName( + "Merge should retain subkeys from the existing context when the overriding context has the same targeting key") @Test void mergeShouldRetainItsSubkeysWhenOverridingContextHasTheSameKey() { HashMap attributes = new HashMap<>(); @@ -92,21 +95,24 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasTheSameKey() { attributes.put("key2", new Value("val2")); ovKey1Attributes.put("overriding_key1_1", new Value("overriding_val_1_1")); overridingAttributes.put("key1", new Value(new ImmutableStructure(ovKey1Attributes))); - + EvaluationContext ctx = new ImmutableContext("targeting_key", attributes); EvaluationContext overriding = new ImmutableContext("targeting_key", overridingAttributes); EvaluationContext merge = ctx.merge(overriding); assertEquals("targeting_key", merge.getTargetingKey()); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); - + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); + Value key1 = merge.getValue("key1"); assertTrue(key1.isStructure()); - + Structure value = key1.asStructure(); - assertArrayEquals(new Object[]{"key1_1","overriding_key1_1"}, value.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1_1", "overriding_key1_1"}, value.keySet().toArray()); } - - @DisplayName("Merge should retain subkeys from the existing context when the overriding context doesn't have targeting key") + + @DisplayName( + "Merge should retain subkeys from the existing context when the overriding context doesn't have targeting key") @Test void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() { HashMap attributes = new HashMap<>(); @@ -115,16 +121,16 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() { key1Attributes.put("key1_1", new Value("val1_1")); attributes.put("key1", new Value(new ImmutableStructure(key1Attributes))); attributes.put("key2", new Value("val2")); - + EvaluationContext ctx = new ImmutableContext(attributes); EvaluationContext overriding = new ImmutableContext(); EvaluationContext merge = ctx.merge(overriding); - assertArrayEquals(new Object[]{"key1", "key2"}, merge.keySet().toArray()); - + assertArrayEquals(new Object[] {"key1", "key2"}, merge.keySet().toArray()); + Value key1 = merge.getValue("key1"); assertTrue(key1.isStructure()); - + Structure value = key1.asStructure(); - assertArrayEquals(new Object[]{"key1_1"}, value.keySet().toArray()); + assertArrayEquals(new Object[] {"key1_1"}, value.keySet().toArray()); } } diff --git a/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java b/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java index 491b5069..dff95adc 100644 --- a/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java +++ b/src/test/java/dev/openfeature/sdk/ImmutableStructureTest.java @@ -1,6 +1,6 @@ package dev.openfeature.sdk; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -9,16 +9,17 @@ import java.util.List; import java.util.Map; import java.util.Set; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; class ImmutableStructureTest { - @Test void noArgShouldContainEmptyAttributes() { + @Test + void noArgShouldContainEmptyAttributes() { ImmutableStructure structure = new ImmutableStructure(); assertEquals(0, structure.asMap().keySet().size()); } - @Test void mapArgShouldContainNewMap() { + @Test + void mapArgShouldContainNewMap() { String KEY = "key"; Map map = new HashMap() { { @@ -30,7 +31,8 @@ class ImmutableStructureTest { assertNotSame(structure.asMap(), map); // should be a copy } - @Test void MutatingGetValueShouldNotChangeOriginalValue() { + @Test + void MutatingGetValueShouldNotChangeOriginalValue() { String KEY = "key"; List lists = new ArrayList<>(); lists.add(new Value(KEY)); @@ -47,7 +49,8 @@ class ImmutableStructureTest { assertNotSame(structure.asMap(), map); // should be a copy } - @Test void MutatingGetInstantValueShouldNotChangeOriginalValue() { + @Test + void MutatingGetInstantValueShouldNotChangeOriginalValue() { String KEY = "key"; Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS); Map map = new HashMap() { @@ -56,39 +59,60 @@ class ImmutableStructureTest { } }; ImmutableStructure structure = new ImmutableStructure(map); - //mutate the original value + // mutate the original value Instant tomorrow = now.plus(1, ChronoUnit.DAYS); - //mutate the getValue + // mutate the getValue structure.getValue(KEY).asInstant().plus(1, ChronoUnit.DAYS); assertNotEquals(tomorrow, structure.getValue(KEY).asInstant()); assertEquals(now, structure.getValue(KEY).asInstant()); } - @Test void MutatingGetStructureValueShouldNotChangeOriginalValue() { + @Test + void MutatingGetStructureValueShouldNotChangeOriginalValue() { String KEY = "key"; List lists = new ArrayList<>(); lists.add(new Value("dummy_list_1")); - MutableStructure mutableStructure = new MutableStructure().add("key1","val1").add("list", lists); + MutableStructure mutableStructure = + new MutableStructure().add("key1", "val1").add("list", lists); Map map = new HashMap() { { put(KEY, new Value(mutableStructure)); } }; ImmutableStructure structure = new ImmutableStructure(map); - //mutate the original structure + // mutate the original structure mutableStructure.add("key2", "val2"); - //mutate the return value + // mutate the return value structure.getValue(KEY).asStructure().asMap().put("key3", new Value("val3")); assertEquals(2, structure.getValue(KEY).asStructure().asMap().size()); - assertArrayEquals(new Object[]{"key1", "list"}, structure.getValue(KEY).asStructure().keySet().toArray()); + assertArrayEquals( + new Object[] {"key1", "list"}, + structure.getValue(KEY).asStructure().keySet().toArray()); assertTrue(structure.getValue(KEY).asStructure() instanceof ImmutableStructure); - //mutate list value + // mutate list value lists.add(new Value("dummy_list_2")); - //mutate the return list value + // mutate the return list value structure.getValue(KEY).asStructure().asMap().get("list").asList().add(new Value("dummy_list_3")); - assertEquals(1, structure.getValue(KEY).asStructure().asMap().get("list").asList().size()); - assertEquals("dummy_list_1", structure.getValue(KEY).asStructure().asMap().get("list").asList().get(0).asString()); + assertEquals( + 1, + structure + .getValue(KEY) + .asStructure() + .asMap() + .get("list") + .asList() + .size()); + assertEquals( + "dummy_list_1", + structure + .getValue(KEY) + .asStructure() + .asMap() + .get("list") + .asList() + .get(0) + .asString()); } @Test @@ -112,7 +136,8 @@ void GettingAMissingValueShouldReturnNull() { assertNull(value); } - @Test void objectMapTest() { + @Test + void objectMapTest() { Map attrs = new HashMap<>(); attrs.put("test", new Value(45)); ImmutableStructure structure = new ImmutableStructure(attrs); diff --git a/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java b/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java index 4d0599a7..3353f564 100644 --- a/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/InitializeBehaviorSpecTest.java @@ -1,10 +1,18 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.testutils.exception.TestException; -import org.junit.jupiter.api.*; - import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import dev.openfeature.sdk.testutils.exception.TestException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; class InitializeBehaviorSpecTest { @@ -18,11 +26,13 @@ void setupTest() { @Nested class DefaultProvider { - @Specification(number = "1.1.2.2", text = "The `provider mutator` function MUST invoke the `initialize` " - + "function on the newly registered provider before using it to resolve flag values.") + @Specification( + number = "1.1.2.2", + text = "The `provider mutator` function MUST invoke the `initialize` " + + "function on the newly registered provider before using it to resolve flag values.") @Test @DisplayName("must call initialize function of the newly registered provider before using it for " - + "flag evaluation") + + "flag evaluation") void mustCallInitializeFunctionOfTheNewlyRegisteredProviderBeforeUsingItForFlagEvaluation() throws Exception { FeatureProvider featureProvider = mock(FeatureProvider.class); doReturn(ProviderState.NOT_READY).when(featureProvider).getState(); @@ -32,10 +42,12 @@ void mustCallInitializeFunctionOfTheNewlyRegisteredProviderBeforeUsingItForFlagE verify(featureProvider, timeout(1000)).initialize(any()); } - @Specification(number = "1.4.10", text = "Methods, functions, or operations on the client MUST NOT throw " - + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " - + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " - + "the purposes for configuration or setup.") + @Specification( + number = "1.4.10", + text = "Methods, functions, or operations on the client MUST NOT throw " + + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " + + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " + + "the purposes for configuration or setup.") @Test @DisplayName("should catch exception thrown by the provider on initialization") void shouldCatchExceptionThrownByTheProviderOnInitialization() throws Exception { @@ -44,7 +56,7 @@ void shouldCatchExceptionThrownByTheProviderOnInitialization() throws Exception doThrow(TestException.class).when(featureProvider).initialize(any()); assertThatCode(() -> OpenFeatureAPI.getInstance().setProvider(featureProvider)) - .doesNotThrowAnyException(); + .doesNotThrowAnyException(); verify(featureProvider, timeout(1000)).initialize(any()); } @@ -53,12 +65,15 @@ void shouldCatchExceptionThrownByTheProviderOnInitialization() throws Exception @Nested class ProviderForNamedClient { - @Specification(number = "1.1.2.2", text = "The `provider mutator` function MUST invoke the `initialize`" - + " function on the newly registered provider before using it to resolve flag values.") + @Specification( + number = "1.1.2.2", + text = "The `provider mutator` function MUST invoke the `initialize`" + + " function on the newly registered provider before using it to resolve flag values.") @Test @DisplayName("must call initialize function of the newly registered named provider before using it " - + "for flag evaluation") - void mustCallInitializeFunctionOfTheNewlyRegisteredNamedProviderBeforeUsingItForFlagEvaluation() throws Exception { + + "for flag evaluation") + void mustCallInitializeFunctionOfTheNewlyRegisteredNamedProviderBeforeUsingItForFlagEvaluation() + throws Exception { FeatureProvider featureProvider = mock(FeatureProvider.class); doReturn(ProviderState.NOT_READY).when(featureProvider).getState(); @@ -67,10 +82,12 @@ void mustCallInitializeFunctionOfTheNewlyRegisteredNamedProviderBeforeUsingItFor verify(featureProvider, timeout(1000)).initialize(any()); } - @Specification(number = "1.4.10", text = "Methods, functions, or operations on the client MUST NOT throw " - + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " - + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " - + "the purposes for configuration or setup.") + @Specification( + number = "1.4.10", + text = "Methods, functions, or operations on the client MUST NOT throw " + + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " + + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " + + "the purposes for configuration or setup.") @Test @DisplayName("should catch exception thrown by the named client provider on initialization") void shouldCatchExceptionThrownByTheNamedClientProviderOnInitialization() throws Exception { @@ -79,7 +96,7 @@ void shouldCatchExceptionThrownByTheNamedClientProviderOnInitialization() throws doThrow(TestException.class).when(featureProvider).initialize(any()); assertThatCode(() -> OpenFeatureAPI.getInstance().setProvider(DOMAIN_NAME, featureProvider)) - .doesNotThrowAnyException(); + .doesNotThrowAnyException(); verify(featureProvider, timeout(1000)).initialize(any()); } diff --git a/src/test/java/dev/openfeature/sdk/LockingTest.java b/src/test/java/dev/openfeature/sdk/LockingTest.java index ddfa9c07..4b7af553 100644 --- a/src/test/java/dev/openfeature/sdk/LockingTest.java +++ b/src/test/java/dev/openfeature/sdk/LockingTest.java @@ -5,26 +5,24 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; -import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock; - @Isolated() class LockingTest { - + private static OpenFeatureAPI api; private OpenFeatureClient client; private AutoCloseableReentrantReadWriteLock apiLock; private AutoCloseableReentrantReadWriteLock clientContextLock; private AutoCloseableReentrantReadWriteLock clientHooksLock; - + @BeforeAll static void beforeAll() { api = OpenFeatureAPI.getInstance(); @@ -34,7 +32,7 @@ static void beforeAll() { @BeforeEach void beforeEach() { client = (OpenFeatureClient) api.getClient("LockingTest"); - + apiLock = setupLock(apiLock, mockInnerReadLock(), mockInnerWriteLock()); OpenFeatureAPI.lock = apiLock; @@ -93,8 +91,9 @@ void onProviderErrorShouldWriteLockAndUnlock() { @Nested class Client { - - // Note that the API lock is used for adding client handlers, they are all added (indirectly) on the API object. + + // Note that the API lock is used for adding client handlers, they are all added (indirectly) on the API + // object. @Test void onShouldApiWriteLockAndUnlock() { @@ -138,16 +137,13 @@ void onProviderErrorProviderReadyShouldApiWriteLockAndUnlock() { } } - @Test void addHooksShouldWriteLockAndUnlock() { - client.addHooks(new Hook() { - }); + client.addHooks(new Hook() {}); verify(clientHooksLock.writeLock()).lock(); verify(clientHooksLock.writeLock()).unlock(); - api.addHooks(new Hook() { - }); + api.addHooks(new Hook() {}); verify(apiLock.writeLock()).lock(); verify(apiLock.writeLock()).unlock(); } @@ -199,7 +195,6 @@ void getTransactionalContextPropagatorShouldReadLockAndUnlock() { verify(apiLock.readLock()).unlock(); } - @Test void clearHooksShouldWriteLockAndUnlock() { api.clearHooks(); @@ -221,7 +216,8 @@ private static ReentrantReadWriteLock.WriteLock mockInnerWriteLock() { return writeLockMock; } - private AutoCloseableReentrantReadWriteLock setupLock(AutoCloseableReentrantReadWriteLock lock, + private AutoCloseableReentrantReadWriteLock setupLock( + AutoCloseableReentrantReadWriteLock lock, AutoCloseableReentrantReadWriteLock.ReadLock readlock, AutoCloseableReentrantReadWriteLock.WriteLock writeLock) { lock = mock(AutoCloseableReentrantReadWriteLock.class); @@ -231,4 +227,4 @@ private AutoCloseableReentrantReadWriteLock setupLock(AutoCloseableReentrantRead when(lock.writeLock()).thenReturn(writeLock); return lock; } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/MetadataTest.java b/src/test/java/dev/openfeature/sdk/MetadataTest.java index 944f45e3..f8ee0ceb 100644 --- a/src/test/java/dev/openfeature/sdk/MetadataTest.java +++ b/src/test/java/dev/openfeature/sdk/MetadataTest.java @@ -1,12 +1,16 @@ package dev.openfeature.sdk; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + class MetadataTest { - @Specification(number="4.2.2.2", text="Condition: The client metadata field in the hook context MUST be immutable.") - @Specification(number="4.2.2.3", text="Condition: The provider metadata field in the hook context MUST be immutable.") + @Specification( + number = "4.2.2.2", + text = "Condition: The client metadata field in the hook context MUST be immutable.") + @Specification( + number = "4.2.2.3", + text = "Condition: The provider metadata field in the hook context MUST be immutable.") @Test void metadata_is_immutable() { try { @@ -16,4 +20,4 @@ void metadata_is_immutable() { // Pass } } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/MutableContextTest.java b/src/test/java/dev/openfeature/sdk/MutableContextTest.java index df21e6ec..953e3f63 100644 --- a/src/test/java/dev/openfeature/sdk/MutableContextTest.java +++ b/src/test/java/dev/openfeature/sdk/MutableContextTest.java @@ -1,17 +1,16 @@ package dev.openfeature.sdk; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - 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.assertTrue; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + class MutableContextTest { @DisplayName("attributes unable to allow mutation should not affect the Mutable context") @@ -23,7 +22,8 @@ void shouldNotAttemptToModifyAttributesForMutableContext() { // should check the usage of Map.of() which is a more likely use case, but that API isn't available in Java 8 EvaluationContext ctx = new MutableContext("targeting key", Collections.unmodifiableMap(attributes)); attributes.put("key3", new Value("val3")); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); } @DisplayName("targeting key should be changed from the overriding context") @@ -49,6 +49,7 @@ void shouldRetainTargetingKeyWhenOverridingContextTargetingKeyValueIsEmpty() { EvaluationContext merge = ctx.merge(overriding); assertEquals("targeting_key", merge.getTargetingKey()); } + @DisplayName("missing targeting key should return null") @Test void missingTargetingKeyShould() { @@ -65,10 +66,12 @@ void mergeShouldReturnAllTheValuesFromTheContextWhenOverridingContextIsNull() { EvaluationContext ctx = new MutableContext("targeting_key", attributes); EvaluationContext merge = ctx.merge(null); assertEquals("targeting_key", merge.getTargetingKey()); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); } - @DisplayName("Merge should retain subkeys from the existing context when the overriding context has the same targeting key") + @DisplayName( + "Merge should retain subkeys from the existing context when the overriding context has the same targeting key") @Test void mergeShouldRetainItsSubkeysWhenOverridingContextHasTheSameKey() { HashMap attributes = new HashMap<>(); @@ -81,21 +84,24 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasTheSameKey() { attributes.put("key2", new Value("val2")); ovKey1Attributes.put("overriding_key1_1", new Value("overriding_val_1_1")); overridingAttributes.put("key1", new Value(new ImmutableStructure(ovKey1Attributes))); - + EvaluationContext ctx = new MutableContext("targeting_key", attributes); EvaluationContext overriding = new MutableContext("targeting_key", overridingAttributes); EvaluationContext merge = ctx.merge(overriding); assertEquals("targeting_key", merge.getTargetingKey()); - assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); - + assertArrayEquals( + new Object[] {"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); + Value key1 = merge.getValue("key1"); assertTrue(key1.isStructure()); - + Structure value = key1.asStructure(); - assertArrayEquals(new Object[]{"key1_1","overriding_key1_1"}, value.keySet().toArray()); + assertArrayEquals( + new Object[] {"key1_1", "overriding_key1_1"}, value.keySet().toArray()); } - - @DisplayName("Merge should retain subkeys from the existing context when the overriding context doesn't have targeting key") + + @DisplayName( + "Merge should retain subkeys from the existing context when the overriding context doesn't have targeting key") @Test void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() { HashMap attributes = new HashMap<>(); @@ -104,17 +110,17 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() { key1Attributes.put("key1_1", new Value("val1_1")); attributes.put("key1", new Value(new ImmutableStructure(key1Attributes))); attributes.put("key2", new Value("val2")); - + EvaluationContext ctx = new MutableContext(attributes); EvaluationContext overriding = new MutableContext(); EvaluationContext merge = ctx.merge(overriding); - assertArrayEquals(new Object[]{"key1", "key2"}, merge.keySet().toArray()); - + assertArrayEquals(new Object[] {"key1", "key2"}, merge.keySet().toArray()); + Value key1 = merge.getValue("key1"); assertTrue(key1.isStructure()); - + Structure value = key1.asStructure(); - assertArrayEquals(new Object[]{"key1_1"}, value.keySet().toArray()); + assertArrayEquals(new Object[] {"key1_1"}, value.keySet().toArray()); } @DisplayName("Ensure mutations are chainable") @@ -129,6 +135,6 @@ void shouldAllowChainingOfMutations() { assertEquals("TARGETING_KEY", context.getTargetingKey()); assertEquals("val1", context.getValue("key1").asString()); assertEquals(2, context.getValue("key2").asInteger()); - assertEquals(3.0, context.getValue("key3").asDouble()); + assertEquals(3.0, context.getValue("key3").asDouble()); } } diff --git a/src/test/java/dev/openfeature/sdk/MutableTrackingEventDetailsTest.java b/src/test/java/dev/openfeature/sdk/MutableTrackingEventDetailsTest.java index a169107f..04fe12ad 100644 --- a/src/test/java/dev/openfeature/sdk/MutableTrackingEventDetailsTest.java +++ b/src/test/java/dev/openfeature/sdk/MutableTrackingEventDetailsTest.java @@ -1,17 +1,15 @@ package dev.openfeature.sdk; -import com.google.common.collect.Lists; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -class MutableTrackingEventDetailsTest { +import com.google.common.collect.Lists; +import java.time.Instant; +import org.junit.jupiter.api.Test; +class MutableTrackingEventDetailsTest { @Test void hasDefaultValue() { @@ -46,6 +44,8 @@ void shouldStoreAttributes() { assertEquals(new Value(Instant.parse("2023-12-03T10:15:30Z")), track.getValue("key5")); assertEquals(new Value(new MutableContext()), track.getValue("key6")); assertEquals(new Value(7), track.getValue("key7")); - assertArrayEquals(new Object[]{new Value(8), new Value(9)}, track.getValue("key8").asList().toArray()); + assertArrayEquals( + new Object[] {new Value(8), new Value(9)}, + track.getValue("key8").asList().toArray()); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/NoOpProviderTest.java b/src/test/java/dev/openfeature/sdk/NoOpProviderTest.java index 2f34cd7d..d0c7c601 100644 --- a/src/test/java/dev/openfeature/sdk/NoOpProviderTest.java +++ b/src/test/java/dev/openfeature/sdk/NoOpProviderTest.java @@ -5,32 +5,37 @@ import org.junit.jupiter.api.Test; public class NoOpProviderTest { - @Test void bool() { + @Test + void bool() { NoOpProvider p = new NoOpProvider(); ProviderEvaluation eval = p.getBooleanEvaluation("key", true, null); assertEquals(true, eval.getValue()); } - @Test void str() { + @Test + void str() { NoOpProvider p = new NoOpProvider(); ProviderEvaluation eval = p.getStringEvaluation("key", "works", null); assertEquals("works", eval.getValue()); } - @Test void integer() { + @Test + void integer() { NoOpProvider p = new NoOpProvider(); ProviderEvaluation eval = p.getIntegerEvaluation("key", 4, null); assertEquals(4, eval.getValue()); } - @Test void noOpdouble() { + @Test + void noOpdouble() { NoOpProvider p = new NoOpProvider(); ProviderEvaluation eval = p.getDoubleEvaluation("key", 0.4, null); assertEquals(0.4, eval.getValue()); } - @Test void value() { + @Test + void value() { NoOpProvider p = new NoOpProvider(); Value s = new Value(); ProviderEvaluation eval = p.getObjectEvaluation("key", s, null); diff --git a/src/test/java/dev/openfeature/sdk/NoOpTransactionContextPropagatorTest.java b/src/test/java/dev/openfeature/sdk/NoOpTransactionContextPropagatorTest.java index 06b7e93c..d824a5a1 100644 --- a/src/test/java/dev/openfeature/sdk/NoOpTransactionContextPropagatorTest.java +++ b/src/test/java/dev/openfeature/sdk/NoOpTransactionContextPropagatorTest.java @@ -1,11 +1,10 @@ package dev.openfeature.sdk; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.util.HashMap; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; class NoOpTransactionContextPropagatorTest { @@ -26,4 +25,4 @@ public void setTransactionContext() { EvaluationContext result = contextPropagator.getTransactionContext(); assertTrue(result.asMap().isEmpty()); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/NotImplementedException.java b/src/test/java/dev/openfeature/sdk/NotImplementedException.java index 09d7bcbb..780c167b 100644 --- a/src/test/java/dev/openfeature/sdk/NotImplementedException.java +++ b/src/test/java/dev/openfeature/sdk/NotImplementedException.java @@ -4,7 +4,7 @@ public class NotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; - public NotImplementedException(String message){ + public NotImplementedException(String message) { super(message); } } diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java index 026170a7..63145ecb 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java @@ -1,14 +1,5 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.providers.memory.InMemoryProvider; -import dev.openfeature.sdk.testutils.FeatureProviderTestUtils; -import dev.openfeature.sdk.testutils.TestEventsProvider; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.HashMap; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,6 +7,14 @@ import static org.mockito.Mockito.mock; 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; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class OpenFeatureAPITest { private static final String DOMAIN_NAME = "my domain"; @@ -36,7 +35,10 @@ void namedProviderTest() { .isEqualTo(api.getProviderMetadata("namedProviderTest").getName()); } - @Specification(number = "1.1.3", text = "The API MUST provide a function to bind a given provider to one or more clients using a domain. If the domain already has a bound provider, it is overwritten with the new mapping.") + @Specification( + number = "1.1.3", + text = + "The API MUST provide a function to bind a given provider to one or more clients using a domain. If the domain already has a bound provider, it is overwritten with the new mapping.") @Test void namedProviderOverwrittenTest() { String domain = "namedProviderOverwrittenTest"; @@ -45,7 +47,10 @@ void namedProviderOverwrittenTest() { FeatureProviderTestUtils.setFeatureProvider(domain, provider1); FeatureProviderTestUtils.setFeatureProvider(domain, provider2); - assertThat(OpenFeatureAPI.getInstance().getProvider(domain).getMetadata().getName()) + assertThat(OpenFeatureAPI.getInstance() + .getProvider(domain) + .getMetadata() + .getName()) .isEqualTo(DoSomethingProvider.name); } @@ -105,7 +110,6 @@ void getStateReturnsTheStateOfTheAppropriateProvider() throws Exception { .isEqualTo(ProviderState.READY); } - @Test void featureProviderTrackIsCalled() throws Exception { FeatureProvider featureProvider = mock(FeatureProvider.class); @@ -113,9 +117,7 @@ void featureProviderTrackIsCalled() throws Exception { OpenFeatureAPI.getInstance() .getClient() - .track("track-event", - new ImmutableContext(), - new MutableTrackingEventDetails(22.2f)); + .track("track-event", new ImmutableContext(), new MutableTrackingEventDetails(22.2f)); verify(featureProvider).initialize(any()); verify(featureProvider).getMetadata(); diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java index 3c82fd65..50b5254c 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java @@ -7,9 +7,11 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; +import dev.openfeature.sdk.exceptions.FatalError; +import dev.openfeature.sdk.fixtures.HookFixtures; +import dev.openfeature.sdk.testutils.TestEventsProvider; import java.util.HashMap; import java.util.concurrent.atomic.AtomicBoolean; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -18,10 +20,6 @@ import org.simplify4u.slf4jmock.LoggerMock; import org.slf4j.Logger; -import dev.openfeature.sdk.exceptions.FatalError; -import dev.openfeature.sdk.fixtures.HookFixtures; -import dev.openfeature.sdk.testutils.TestEventsProvider; - class OpenFeatureClientTest implements HookFixtures { private Logger logger; @@ -41,13 +39,15 @@ void reset_logs() { @DisplayName("should not throw exception if hook has different type argument than hookContext") void shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext() { OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - api.setProviderAndWait("shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext", new DoSomethingProvider()); + api.setProviderAndWait( + "shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext", new DoSomethingProvider()); Client client = api.getClient("shouldNotThrowExceptionIfHookHasDifferentTypeArgumentThanHookContext"); client.addHooks(mockBooleanHook(), mockStringHook()); FlagEvaluationDetails actual = client.getBooleanDetails("feature key", Boolean.FALSE); assertThat(actual.getValue()).isTrue(); - // I dislike this, but given the mocking tools available, there's no way that I know of to say "no errors were logged" + // I dislike this, but given the mocking tools available, there's no way that I know of to say "no errors were + // logged" Mockito.verify(logger, never()).error(any()); Mockito.verify(logger, never()).error(anyString(), any(Throwable.class)); Mockito.verify(logger, never()).error(anyString(), any(Object.class)); @@ -78,7 +78,6 @@ void setEvaluationContextShouldAllowChaining() { assertEquals(client, result); } - @Test @DisplayName("Should not call evaluation methods when the provider has state FATAL") void shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState() { @@ -86,7 +85,10 @@ void shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState() { OpenFeatureAPI api = OpenFeatureAPI.getInstance(); Client client = api.getClient("shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState"); - assertThrows(FatalError.class, () -> api.setProviderAndWait("shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState", provider)); + assertThrows( + FatalError.class, + () -> api.setProviderAndWait( + "shouldNotCallEvaluationMethodsWhenProviderIsInFatalErrorState", provider)); FlagEvaluationDetails details = client.getBooleanDetails("key", true); assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_FATAL); } @@ -126,7 +128,8 @@ public Metadata getMetadata() { } @Override - public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + public ProviderEvaluation getBooleanEvaluation( + String key, Boolean defaultValue, EvaluationContext ctx) { evaluationCalled.set(true); return null; } @@ -138,7 +141,8 @@ public ProviderEvaluation getStringEvaluation(String key, String default } @Override - public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { + public ProviderEvaluation getIntegerEvaluation( + String key, Integer defaultValue, EvaluationContext ctx) { evaluationCalled.set(true); return null; } diff --git a/src/test/java/dev/openfeature/sdk/ProviderEvaluationTest.java b/src/test/java/dev/openfeature/sdk/ProviderEvaluationTest.java index 16215dc1..24762431 100644 --- a/src/test/java/dev/openfeature/sdk/ProviderEvaluationTest.java +++ b/src/test/java/dev/openfeature/sdk/ProviderEvaluationTest.java @@ -27,13 +27,8 @@ public void sixArgConstructor() { String errorMessage = "message"; ImmutableMetadata metadata = ImmutableMetadata.builder().build(); - ProviderEvaluation details = new ProviderEvaluation<>( - value, - variant, - reason.toString(), - errorCode, - errorMessage, - metadata); + ProviderEvaluation details = + new ProviderEvaluation<>(value, variant, reason.toString(), errorCode, errorMessage, metadata); assertEquals(value, details.getValue()); assertEquals(variant, details.getVariant()); diff --git a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java index 26a04d53..98652635 100644 --- a/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java +++ b/src/test/java/dev/openfeature/sdk/ProviderRepositoryTest.java @@ -1,12 +1,16 @@ package dev.openfeature.sdk; +import static dev.openfeature.sdk.fixtures.ProviderFixture.*; +import static dev.openfeature.sdk.testutils.stubbing.ConditionStubber.doDelayResponse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + import dev.openfeature.sdk.exceptions.OpenFeatureError; import dev.openfeature.sdk.testutils.exception.TestException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -14,15 +18,10 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; - -import static dev.openfeature.sdk.fixtures.ProviderFixture.*; -import static dev.openfeature.sdk.testutils.stubbing.ConditionStubber.doDelayResponse; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.awaitility.Awaitility.await; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; class ProviderRepositoryTest { @@ -48,8 +47,9 @@ class DefaultProvider { @Test @DisplayName("should reject null as default provider") void shouldRejectNullAsDefaultProvider() { - assertThatCode(() -> providerRepository.setProvider(null, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError(), false)).isInstanceOf(IllegalArgumentException.class); + assertThatCode(() -> providerRepository.setProvider( + null, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), mockAfterError(), false)) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -64,13 +64,17 @@ void shouldImmediatelyReturnWhenCallingTheProviderMutator() throws Exception { FeatureProvider featureProvider = createMockedProvider(); doDelayResponse(Duration.ofSeconds(10)).when(featureProvider).initialize(new ImmutableContext()); - await() - .alias("wait for provider mutator to return") + await().alias("wait for provider mutator to return") .pollDelay(Duration.ofMillis(1)) .atMost(Duration.ofSeconds(1)) .until(() -> { - providerRepository.setProvider(featureProvider, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError(), false); + providerRepository.setProvider( + featureProvider, + mockAfterSet(), + mockAfterInit(), + mockAfterShutdown(), + mockAfterError(), + false); verify(featureProvider, timeout(TIMEOUT)).initialize(any()); return true; }); @@ -85,8 +89,14 @@ class NamedProvider { @Test @DisplayName("should reject null as named provider") void shouldRejectNullAsNamedProvider() { - assertThatCode(() -> providerRepository.setProvider(DOMAIN_NAME, null, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError(), false)) + assertThatCode(() -> providerRepository.setProvider( + DOMAIN_NAME, + null, + mockAfterSet(), + mockAfterInit(), + mockAfterShutdown(), + mockAfterError(), + false)) .isInstanceOf(IllegalArgumentException.class); } @@ -94,8 +104,14 @@ void shouldRejectNullAsNamedProvider() { @DisplayName("should reject null as domain name") void shouldRejectNullAsDefaultProvider() { NoOpProvider provider = new NoOpProvider(); - assertThatCode(() -> providerRepository.setProvider(null, provider, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError(), false)) + assertThatCode(() -> providerRepository.setProvider( + null, + provider, + mockAfterSet(), + mockAfterInit(), + mockAfterShutdown(), + mockAfterError(), + false)) .isInstanceOf(IllegalArgumentException.class); } @@ -105,13 +121,18 @@ void shouldImmediatelyReturnWhenCallingTheDomainProviderMutator() throws Excepti FeatureProvider featureProvider = createMockedProvider(); doDelayResponse(Duration.ofSeconds(10)).when(featureProvider).initialize(any()); - await() - .alias("wait for provider mutator to return") + await().alias("wait for provider mutator to return") .pollDelay(Duration.ofMillis(1)) .atMost(Duration.ofSeconds(1)) .until(() -> { - providerRepository.setProvider("a domain", featureProvider, mockAfterSet(), - mockAfterInit(), mockAfterShutdown(), mockAfterError(), false); + providerRepository.setProvider( + "a domain", + featureProvider, + mockAfterSet(), + mockAfterInit(), + mockAfterShutdown(), + mockAfterError(), + false); verify(featureProvider, timeout(TIMEOUT)).initialize(any()); return true; }); @@ -131,13 +152,17 @@ void shouldImmediatelyReturnWhenCallingTheProviderMutator() throws Exception { FeatureProvider newProvider = createMockedProvider(); doDelayResponse(Duration.ofSeconds(10)).when(newProvider).initialize(any()); - await() - .alias("wait for provider mutator to return") + await().alias("wait for provider mutator to return") .pollDelay(Duration.ofMillis(1)) .atMost(Duration.ofSeconds(1)) .until(() -> { - providerRepository.setProvider(newProvider, mockAfterSet(), mockAfterInit(), - mockAfterShutdown(), mockAfterError(), false); + providerRepository.setProvider( + newProvider, + mockAfterSet(), + mockAfterInit(), + mockAfterShutdown(), + mockAfterError(), + false); verify(newProvider, timeout(TIMEOUT)).initialize(any()); return true; }); @@ -168,12 +193,16 @@ void shouldImmediatelyReturnWhenCallingTheProviderMutator() throws Exception { FeatureProvider newProvider = createMockedProvider(); doDelayResponse(Duration.ofSeconds(10)).when(newProvider).initialize(any()); - Future providerMutation = executorService - .submit(() -> providerRepository.setProvider(DOMAIN_NAME, newProvider, mockAfterSet(), - mockAfterInit(), mockAfterShutdown(), mockAfterError(), false)); + Future providerMutation = executorService.submit(() -> providerRepository.setProvider( + DOMAIN_NAME, + newProvider, + mockAfterSet(), + mockAfterInit(), + mockAfterShutdown(), + mockAfterError(), + false)); - await() - .alias("wait for provider mutator to return") + await().alias("wait for provider mutator to return") .pollDelay(Duration.ofMillis(1)) .atMost(Duration.ofSeconds(1)) .until(providerMutation::isDone); @@ -278,55 +307,47 @@ void shouldShutdownAllFeatureProvidersOnShutdown() { } private void setFeatureProvider(FeatureProvider provider) { - providerRepository.setProvider(provider, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), - mockAfterError(), false); + providerRepository.setProvider( + provider, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), mockAfterError(), false); waitForSettingProviderHasBeenCompleted(ProviderRepository::getProvider, provider); } - - private void setFeatureProvider(FeatureProvider provider, Consumer afterSet, - Consumer afterInit, Consumer afterShutdown, - BiConsumer afterError) { - providerRepository.setProvider(provider, afterSet, afterInit, afterShutdown, - afterError, false); + private void setFeatureProvider( + FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError) { + providerRepository.setProvider(provider, afterSet, afterInit, afterShutdown, afterError, false); waitForSettingProviderHasBeenCompleted(ProviderRepository::getProvider, provider); } private void setFeatureProvider(String namedProvider, FeatureProvider provider) { - providerRepository.setProvider(namedProvider, provider, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), - mockAfterError(), false); + providerRepository.setProvider( + namedProvider, provider, mockAfterSet(), mockAfterInit(), mockAfterShutdown(), mockAfterError(), false); waitForSettingProviderHasBeenCompleted(repository -> repository.getProvider(namedProvider), provider); } private void waitForSettingProviderHasBeenCompleted( - Function extractor, - FeatureProvider provider) { - await() - .pollDelay(Duration.ofMillis(1)) - .atMost(Duration.ofSeconds(5)) - .until(() -> { - return extractor.apply(providerRepository).equals(provider); - }); + Function extractor, FeatureProvider provider) { + await().pollDelay(Duration.ofMillis(1)).atMost(Duration.ofSeconds(5)).until(() -> { + return extractor.apply(providerRepository).equals(provider); + }); } private Consumer mockAfterSet() { - return fp -> { - }; + return fp -> {}; } private Consumer mockAfterInit() { - return fp -> { - }; + return fp -> {}; } private Consumer mockAfterShutdown() { - return fp -> { - }; + return fp -> {}; } private BiConsumer mockAfterError() { - return (fp, ex) -> { - }; + return (fp, ex) -> {}; } - } diff --git a/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java b/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java index a87cc517..ec87acd7 100644 --- a/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/ProviderSpecTest.java @@ -10,17 +10,31 @@ public class ProviderSpecTest { NoOpProvider p = new NoOpProvider(); - @Specification(number = "2.1.1", text = "The provider interface MUST define a metadata member or accessor, containing a name field or accessor of type string, which identifies the provider implementation.") + @Specification( + number = "2.1.1", + text = + "The provider interface MUST define a metadata member or accessor, containing a name field or accessor of type string, which identifies the provider implementation.") @Test void name_accessor() { assertNotNull(p.getName()); } - @Specification(number = "2.2.2.1", text = "The feature provider interface MUST define methods for typed " + - "flag resolution, including boolean, numeric, string, and structure.") - @Specification(number = "2.2.3", text = "In cases of normal execution, the `provider` MUST populate the `resolution details` structure's `value` field with the resolved flag value.") - @Specification(number = "2.2.1", text = "The `feature provider` interface MUST define methods to resolve flag values, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required) and `evaluation context` (optional), which returns a `resolution details` structure.") - @Specification(number = "2.2.8.1", text = "The `resolution details` structure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field.") + @Specification( + number = "2.2.2.1", + text = "The feature provider interface MUST define methods for typed " + + "flag resolution, including boolean, numeric, string, and structure.") + @Specification( + number = "2.2.3", + text = + "In cases of normal execution, the `provider` MUST populate the `resolution details` structure's `value` field with the resolved flag value.") + @Specification( + number = "2.2.1", + text = + "The `feature provider` interface MUST define methods to resolve flag values, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required) and `evaluation context` (optional), which returns a `resolution details` structure.") + @Specification( + number = "2.2.8.1", + text = + "The `resolution details` structure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field.") @Test void flag_value_set() { ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new ImmutableContext()); @@ -37,31 +51,47 @@ void flag_value_set() { ProviderEvaluation object_result = p.getObjectEvaluation("key", new Value(), new ImmutableContext()); assertNotNull(object_result.getValue()); - } - @Specification(number = "2.2.5", text = "The `provider` SHOULD populate the `resolution details` structure's `reason` field with `\"STATIC\"`, `\"DEFAULT\",` `\"TARGETING_MATCH\"`, `\"SPLIT\"`, `\"CACHED\"`, `\"DISABLED\"`, `\"UNKNOWN\"`, `\"STALE\"`, `\"ERROR\"` or some other string indicating the semantic reason for the returned flag value.") + @Specification( + number = "2.2.5", + text = + "The `provider` SHOULD populate the `resolution details` structure's `reason` field with `\"STATIC\"`, `\"DEFAULT\",` `\"TARGETING_MATCH\"`, `\"SPLIT\"`, `\"CACHED\"`, `\"DISABLED\"`, `\"UNKNOWN\"`, `\"STALE\"`, `\"ERROR\"` or some other string indicating the semantic reason for the returned flag value.") @Test void has_reason() { ProviderEvaluation result = p.getBooleanEvaluation("key", false, new ImmutableContext()); assertEquals(Reason.DEFAULT.toString(), result.getReason()); } - @Specification(number = "2.2.6", text = "In cases of normal execution, the `provider` MUST NOT populate the `resolution details` structure's `error code` field, or otherwise must populate it with a null or falsy value.") + @Specification( + number = "2.2.6", + text = + "In cases of normal execution, the `provider` MUST NOT populate the `resolution details` structure's `error code` field, or otherwise must populate it with a null or falsy value.") @Test void no_error_code_by_default() { ProviderEvaluation result = p.getBooleanEvaluation("key", false, new ImmutableContext()); assertNull(result.getErrorCode()); } - @Specification(number = "2.2.7", text = "In cases of abnormal execution, the `provider` **MUST** indicate an error using the idioms of the implementation language, with an associated `error code` and optional associated `error message`.") - @Specification(number = "2.3.2", text = "In cases of normal execution, the `provider` MUST NOT populate the `resolution details` structure's `error message` field, or otherwise must populate it with a null or falsy value.") - @Specification(number = "2.3.3", text = "In cases of abnormal execution, the `resolution details` structure's `error message` field MAY contain a string containing additional detail about the nature of the error.") + @Specification( + number = "2.2.7", + text = + "In cases of abnormal execution, the `provider` **MUST** indicate an error using the idioms of the implementation language, with an associated `error code` and optional associated `error message`.") + @Specification( + number = "2.3.2", + text = + "In cases of normal execution, the `provider` MUST NOT populate the `resolution details` structure's `error message` field, or otherwise must populate it with a null or falsy value.") + @Specification( + number = "2.3.3", + text = + "In cases of abnormal execution, the `resolution details` structure's `error message` field MAY contain a string containing additional detail about the nature of the error.") @Test - void up_to_provider_implementation() { - } + void up_to_provider_implementation() {} - @Specification(number = "2.2.4", text = "In cases of normal execution, the `provider` SHOULD populate the `resolution details` structure's `variant` field with a string identifier corresponding to the returned flag value.") + @Specification( + number = "2.2.4", + text = + "In cases of normal execution, the `provider` SHOULD populate the `resolution details` structure's `variant` field with a string identifier corresponding to the returned flag value.") @Test void variant_set() { ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new ImmutableContext()); @@ -77,7 +107,10 @@ void variant_set() { assertNotNull(boolean_result.getReason()); } - @Specification(number = "2.2.10", text = "`flag metadata` MUST be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number`.") + @Specification( + number = "2.2.10", + text = + "`flag metadata` MUST be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number`.") @Test void flag_metadata_structure() { ImmutableMetadata metadata = ImmutableMetadata.builder() @@ -97,30 +130,51 @@ void flag_metadata_structure() { assertEquals("str", metadata.getString("string")); } - @Specification(number = "2.3.1", text = "The provider interface MUST define a provider hook mechanism which can be optionally implemented in order to add hook instances to the evaluation life-cycle.") - @Specification(number = "4.4.1", text = "The API, Client, Provider, and invocation MUST have a method for registering hooks.") + @Specification( + number = "2.3.1", + text = + "The provider interface MUST define a provider hook mechanism which can be optionally implemented in order to add hook instances to the evaluation life-cycle.") + @Specification( + number = "4.4.1", + text = "The API, Client, Provider, and invocation MUST have a method for registering hooks.") @Test void provider_hooks() { assertEquals(0, p.getProviderHooks().size()); } - @Specification(number = "2.4.2", text = "The provider MAY define a status field/accessor which indicates the readiness of the provider, with possible values NOT_READY, READY, or ERROR.") + @Specification( + number = "2.4.2", + text = + "The provider MAY define a status field/accessor which indicates the readiness of the provider, with possible values NOT_READY, READY, or ERROR.") @Test void defines_status() { assertTrue(p.getState() instanceof ProviderState); } - @Specification(number = "2.4.3", text = "The provider MUST set its status field/accessor to READY if its initialize function terminates normally.") - @Specification(number = "2.4.4", text = "The provider MUST set its status field to ERROR if its initialize function terminates abnormally.") - @Specification(number = "2.2.9", text = "The provider SHOULD populate the resolution details structure's flag metadata field.") - @Specification(number = "2.4.1", text = "The provider MAY define an initialize function which accepts the global evaluation context as an argument and performs initialization logic relevant to the provider.") - @Specification(number = "2.5.1", text = "The provider MAY define a mechanism to gracefully shutdown and dispose of resources.") + @Specification( + number = "2.4.3", + text = + "The provider MUST set its status field/accessor to READY if its initialize function terminates normally.") + @Specification( + number = "2.4.4", + text = "The provider MUST set its status field to ERROR if its initialize function terminates abnormally.") + @Specification( + number = "2.2.9", + text = "The provider SHOULD populate the resolution details structure's flag metadata field.") + @Specification( + number = "2.4.1", + text = + "The provider MAY define an initialize function which accepts the global evaluation context as an argument and performs initialization logic relevant to the provider.") + @Specification( + number = "2.5.1", + text = "The provider MAY define a mechanism to gracefully shutdown and dispose of resources.") @Test - void provider_responsibility() { - } + void provider_responsibility() {} - @Specification(number = "2.6.1", text = "The provider MAY define an on context changed handler, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.") + @Specification( + number = "2.6.1", + text = + "The provider MAY define an on context changed handler, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.") @Test - void not_applicable_for_dynamic_context() { - } + void not_applicable_for_dynamic_context() {} } diff --git a/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java b/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java index bc2dc0ea..e7caf927 100644 --- a/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java @@ -1,18 +1,17 @@ package dev.openfeature.sdk; +import static dev.openfeature.sdk.testutils.FeatureProviderTestUtils.setFeatureProvider; +import static org.mockito.Mockito.*; + import dev.openfeature.sdk.fixtures.ProviderFixture; import dev.openfeature.sdk.testutils.exception.TestException; +import java.time.Duration; import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.time.Duration; - -import static dev.openfeature.sdk.testutils.FeatureProviderTestUtils.setFeatureProvider; -import static org.mockito.Mockito.*; - class ShutdownBehaviorSpecTest { private String DOMAIN = "myDomain"; @@ -25,9 +24,13 @@ void resetFeatureProvider() { @Nested class DefaultProvider { - @Specification(number = "1.1.2.3", text = "The `provider mutator` function MUST invoke the `shutdown` function on the previously registered provider once it's no longer being used to resolve flag values.") + @Specification( + number = "1.1.2.3", + text = + "The `provider mutator` function MUST invoke the `shutdown` function on the previously registered provider once it's no longer being used to resolve flag values.") @Test - @DisplayName("must invoke shutdown method on previously registered provider once it should not be used for flag evaluation anymore") + @DisplayName( + "must invoke shutdown method on previously registered provider once it should not be used for flag evaluation anymore") void mustInvokeShutdownMethodOnPreviouslyRegisteredProviderOnceItShouldNotBeUsedForFlagEvaluationAnymore() { FeatureProvider featureProvider = ProviderFixture.createMockedProvider(); @@ -37,10 +40,12 @@ void mustInvokeShutdownMethodOnPreviouslyRegisteredProviderOnceItShouldNotBeUsed verify(featureProvider, timeout(1000)).shutdown(); } - @Specification(number = "1.4.10", text = "Methods, functions, or operations on the client MUST NOT throw " - + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " - + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " - + "the purposes for configuration or setup.") + @Specification( + number = "1.4.10", + text = "Methods, functions, or operations on the client MUST NOT throw " + + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " + + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " + + "the purposes for configuration or setup.") @Test @DisplayName("should catch exception thrown by the provider on shutdown") void shouldCatchExceptionThrownByTheProviderOnShutdown() { @@ -57,9 +62,13 @@ void shouldCatchExceptionThrownByTheProviderOnShutdown() { @Nested class NamedProvider { - @Specification(number = "1.1.2.3", text = "The `provider mutator` function MUST invoke the `shutdown` function on the previously registered provider once it's no longer being used to resolve flag values.") + @Specification( + number = "1.1.2.3", + text = + "The `provider mutator` function MUST invoke the `shutdown` function on the previously registered provider once it's no longer being used to resolve flag values.") @Test - @DisplayName("must invoke shutdown method on previously registered provider once it should not be used for flag evaluation anymore") + @DisplayName( + "must invoke shutdown method on previously registered provider once it should not be used for flag evaluation anymore") void mustInvokeShutdownMethodOnPreviouslyRegisteredProviderOnceItShouldNotBeUsedForFlagEvaluationAnymore() { FeatureProvider featureProvider = ProviderFixture.createMockedProvider(); @@ -69,10 +78,12 @@ void mustInvokeShutdownMethodOnPreviouslyRegisteredProviderOnceItShouldNotBeUsed verify(featureProvider, timeout(1000)).shutdown(); } - @Specification(number = "1.4.10", text = "Methods, functions, or operations on the client MUST NOT throw " - + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " - + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " - + "the purposes for configuration or setup.") + @Specification( + number = "1.4.10", + text = "Methods, functions, or operations on the client MUST NOT throw " + + "exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the " + + "`default value` in the event of abnormal execution. Exceptions include functions or methods for " + + "the purposes for configuration or setup.") @Test @DisplayName("should catch exception thrown by the named client provider on shutdown") void shouldCatchExceptionThrownByTheNamedClientProviderOnShutdown() { @@ -89,7 +100,9 @@ void shouldCatchExceptionThrownByTheNamedClientProviderOnShutdown() { @Nested class General { - @Specification(number = "1.6.1", text = "The API MUST define a mechanism to propagate a shutdown request to active providers.") + @Specification( + number = "1.6.1", + text = "The API MUST define a mechanism to propagate a shutdown request to active providers.") @Test @DisplayName("must shutdown all providers on shutting down api") void mustShutdownAllProvidersOnShuttingDownApi() { @@ -102,17 +115,13 @@ void mustShutdownAllProvidersOnShuttingDownApi() { synchronized (OpenFeatureAPI.class) { api.shutdown(); - Awaitility - .await() - .atMost(Duration.ofSeconds(1)) - .untilAsserted(() -> { - verify(defaultProvider).shutdown(); - verify(namedProvider).shutdown(); - }); + Awaitility.await().atMost(Duration.ofSeconds(1)).untilAsserted(() -> { + verify(defaultProvider).shutdown(); + verify(namedProvider).shutdown(); + }); } } - @Test @DisplayName("once shutdown is complete, api must be ready to use again") void apiIsReadyToUseAfterShutdown() { diff --git a/src/test/java/dev/openfeature/sdk/Specification.java b/src/test/java/dev/openfeature/sdk/Specification.java index 061e45ec..c75e179c 100644 --- a/src/test/java/dev/openfeature/sdk/Specification.java +++ b/src/test/java/dev/openfeature/sdk/Specification.java @@ -5,5 +5,6 @@ @Repeatable(Specifications.class) public @interface Specification { String number(); + String text(); } diff --git a/src/test/java/dev/openfeature/sdk/StructureTest.java b/src/test/java/dev/openfeature/sdk/StructureTest.java index 16747ee0..2a2406a5 100644 --- a/src/test/java/dev/openfeature/sdk/StructureTest.java +++ b/src/test/java/dev/openfeature/sdk/StructureTest.java @@ -1,7 +1,10 @@ package dev.openfeature.sdk; -import lombok.SneakyThrows; -import org.junit.jupiter.api.Test; +import static dev.openfeature.sdk.Structure.mapToStructure; +import static org.junit.jupiter.api.Assertions.assertEquals; +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.util.ArrayList; @@ -9,20 +12,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - -import static dev.openfeature.sdk.Structure.mapToStructure; -import static org.junit.jupiter.api.Assertions.assertEquals; -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 lombok.SneakyThrows; +import org.junit.jupiter.api.Test; public class StructureTest { - @Test public void noArgShouldContainEmptyAttributes() { + @Test + public void noArgShouldContainEmptyAttributes() { MutableStructure structure = new MutableStructure(); assertEquals(0, structure.asMap().keySet().size()); } - @Test public void mapArgShouldContainNewMap() { + @Test + public void mapArgShouldContainNewMap() { String KEY = "key"; Map map = new HashMap() { { @@ -34,7 +35,8 @@ public class StructureTest { assertNotSame(structure.asMap(), map); // should be a copy } - @Test public void addAndGetAddAndReturnValues() { + @Test + public void addAndGetAddAndReturnValues() { String BOOL_KEY = "bool"; String STRING_KEY = "string"; String INT_KEY = "int"; @@ -104,7 +106,7 @@ void mapToStructureTest() { @Test void asObjectHandlesNullValue() { Map map = new HashMap<>(); - map.put("null", new Value((String)null)); + map.put("null", new Value((String) null)); ImmutableStructure structure = new ImmutableStructure(map); assertNull(structure.asObjectMap().get("null")); } @@ -112,6 +114,6 @@ void asObjectHandlesNullValue() { @Test void convertValueHandlesNullValue() { ImmutableStructure structure = new ImmutableStructure(); - assertNull(structure.convertValue(new Value((String)null))); + assertNull(structure.convertValue(new Value((String) null))); } } diff --git a/src/test/java/dev/openfeature/sdk/ThreadLocalTransactionContextPropagatorTest.java b/src/test/java/dev/openfeature/sdk/ThreadLocalTransactionContextPropagatorTest.java index 531205c1..2993f880 100644 --- a/src/test/java/dev/openfeature/sdk/ThreadLocalTransactionContextPropagatorTest.java +++ b/src/test/java/dev/openfeature/sdk/ThreadLocalTransactionContextPropagatorTest.java @@ -1,12 +1,11 @@ package dev.openfeature.sdk; -import lombok.SneakyThrows; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; - -import static org.junit.jupiter.api.Assertions.*; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; public class ThreadLocalTransactionContextPropagatorTest { @@ -54,4 +53,4 @@ public void setTransactionContextTwoThreads() { assertSame(secondContext, secondThreadContext); assertSame(firstContext, contextPropagator.getTransactionContext()); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java b/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java index 6d195607..a8f6e30f 100644 --- a/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/TrackingSpecTest.java @@ -1,16 +1,5 @@ package dev.openfeature.sdk; -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 lombok.SneakyThrows; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,6 +12,16 @@ import static org.mockito.Mockito.spy; 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; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class TrackingSpecTest { private OpenFeatureAPI api; @@ -34,13 +33,16 @@ void getApiInstance() { client = api.getClient(); } - - @Specification(number = "6.1.1.1", text = "The `client` MUST define a function for tracking the occurrence of " + - "a particular action or application state, with parameters `tracking event name` (string, required), " + - "`evaluation context` (optional) and `tracking event details` (optional), which returns nothing.") - @Specification(number = "6.1.2.1", text = "The `client` MUST define a function for tracking the occurrence of a " + - "particular action or application state, with parameters `tracking event name` (string, required) and " + - "`tracking event details` (optional), which returns nothing.") + @Specification( + number = "6.1.1.1", + text = "The `client` MUST define a function for tracking the occurrence of " + + "a particular action or application state, with parameters `tracking event name` (string, required), " + + "`evaluation context` (optional) and `tracking event details` (optional), which returns nothing.") + @Specification( + number = "6.1.2.1", + text = "The `client` MUST define a function for tracking the occurrence of a " + + "particular action or application state, with parameters `tracking event name` (string, required) and " + + "`tracking event details` (optional), which returns nothing.") @Test @SneakyThrows void trackMethodFulfillsSpec() { @@ -65,7 +67,6 @@ void trackMethodFulfillsSpec() { assertThrows(IllegalArgumentException.class, () -> client.track("", ctx)); assertThrows(IllegalArgumentException.class, () -> client.track("", ctx, details)); - Class clientClass = OpenFeatureClient.class; assertEquals( void.class, @@ -73,20 +74,24 @@ void trackMethodFulfillsSpec() { "The method should return void."); assertEquals( void.class, - clientClass.getMethod("track", String.class, EvaluationContext.class).getReturnType(), + clientClass + .getMethod("track", String.class, EvaluationContext.class) + .getReturnType(), "The method should return void."); assertEquals( void.class, - clientClass.getMethod("track", String.class, EvaluationContext.class, TrackingEventDetails.class).getReturnType(), + clientClass + .getMethod("track", String.class, EvaluationContext.class, TrackingEventDetails.class) + .getReturnType(), "The method should return void."); - - } - @Specification(number = "6.1.3", text = "The evaluation context passed to the provider's track function " + - "MUST be merged in the order: API (global; lowest precedence) -> transaction -> client -> " + - "invocation (highest precedence), with duplicate values being overwritten.") + @Specification( + number = "6.1.3", + text = "The evaluation context passed to the provider's track function " + + "MUST be merged in the order: API (global; lowest precedence) -> transaction -> client -> " + + "invocation (highest precedence), with duplicate values being overwritten.") @Test void contextsGetMerged() { @@ -123,8 +128,10 @@ void contextsGetMerged() { verify(provider).track(eq("event"), argThat(ctx -> ctx.asMap().equals(expectedMap)), notNull()); } - @Specification(number = "6.1.4", text = "If the client's `track` function is called and the associated provider " + - "does not implement tracking, the client's `track` function MUST no-op.") + @Specification( + number = "6.1.4", + text = "If the client's `track` function is called and the associated provider " + + "does not implement tracking, the client's `track` function MUST no-op.") @Test void noopProvider() { FeatureProvider provider = spy(FeatureProvider.class); @@ -133,10 +140,15 @@ void noopProvider() { verify(provider).track(any(), any(), any()); } - @Specification(number = "6.2.1", text = "The `tracking event details` structure MUST define an optional numeric " + - "`value`, associating a scalar quality with an `tracking event`.") - @Specification(number = "6.2.2", text = "The `tracking event details` MUST support the inclusion of custom " + - "fields, having keys of type `string`, and values of type `boolean | string | number | structure`.") + @Specification( + number = "6.2.1", + text = "The `tracking event details` structure MUST define an optional numeric " + + "`value`, associating a scalar quality with an `tracking event`.") + @Specification( + number = "6.2.2", + text = + "The `tracking event details` MUST support the inclusion of custom " + + "fields, having keys of type `string`, and values of type `boolean | string | number | structure`.") @Test void eventDetails() { assertFalse(new MutableTrackingEventDetails().getValue().isPresent()); @@ -144,7 +156,6 @@ void eventDetails() { assertThat(new ImmutableTrackingEventDetails(2).getValue()).hasValue(2); assertThat(new MutableTrackingEventDetails(9.87f).getValue()).hasValue(9.87f); - // using mutable tracking event details Map expectedMap = Maps.newHashMap(); expectedMap.put("my-str", new Value("str")); @@ -159,31 +170,27 @@ void eventDetails() { .add("my-struct", new Value(new MutableTrackingEventDetails())); assertEquals(expectedMap, details.asMap()); - assertThatCode(() -> OpenFeatureAPI.getInstance().getClient(). - track( - "tracking-event-name", - new ImmutableContext(), - new MutableTrackingEventDetails())) + assertThatCode(() -> OpenFeatureAPI.getInstance() + .getClient() + .track("tracking-event-name", new ImmutableContext(), new MutableTrackingEventDetails())) .doesNotThrowAnyException(); - // using immutable tracking event details - ImmutableMap expectedImmutable = ImmutableMap.of("my-str", new Value("str"), - "my-num", new Value(1), - "my-bool", new Value(true), - "my-struct", new Value(new ImmutableStructure()) - ); + ImmutableMap expectedImmutable = ImmutableMap.of( + "my-str", + new Value("str"), + "my-num", + new Value(1), + "my-bool", + new Value(true), + "my-struct", + new Value(new ImmutableStructure())); ImmutableTrackingEventDetails immutableDetails = new ImmutableTrackingEventDetails(2, expectedMap); assertEquals(expectedImmutable, immutableDetails.asMap()); - assertThatCode(() -> OpenFeatureAPI.getInstance().getClient(). - track( - "tracking-event-name", - new ImmutableContext(), - new ImmutableTrackingEventDetails())) + assertThatCode(() -> OpenFeatureAPI.getInstance() + .getClient() + .track("tracking-event-name", new ImmutableContext(), new ImmutableTrackingEventDetails())) .doesNotThrowAnyException(); - - } - } diff --git a/src/test/java/dev/openfeature/sdk/ValueTest.java b/src/test/java/dev/openfeature/sdk/ValueTest.java index 816190ab..c2553850 100644 --- a/src/test/java/dev/openfeature/sdk/ValueTest.java +++ b/src/test/java/dev/openfeature/sdk/ValueTest.java @@ -9,16 +9,17 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; - import org.junit.jupiter.api.Test; public class ValueTest { - @Test public void noArgShouldContainNull() { + @Test + public void noArgShouldContainNull() { Value value = new Value(); assertTrue(value.isNull()); } - @Test public void objectArgShouldContainObject() { + @Test + public void objectArgShouldContainObject() { try { // int is a special case, see intObjectArgShouldConvertToInt() List list = new ArrayList<>(); @@ -30,7 +31,7 @@ public class ValueTest { list.add(Instant.now()); int i = 0; - for (Object l: list) { + for (Object l : list) { Value value = new Value(l); assertEquals(list.get(i), value.asObject()); i++; @@ -40,7 +41,8 @@ public class ValueTest { } } - @Test public void intObjectArgShouldConvertToInt() { + @Test + public void intObjectArgShouldConvertToInt() { try { Object innerValue = 1; Value value = new Value(innerValue); @@ -50,7 +52,8 @@ public class ValueTest { } } - @Test public void invalidObjectArgShouldThrow() { + @Test + public void invalidObjectArgShouldThrow() { class Something {} @@ -59,18 +62,20 @@ class Something {} }); } - @Test public void boolArgShouldContainBool() { + @Test + public void boolArgShouldContainBool() { boolean innerValue = true; Value value = new Value(innerValue); assertTrue(value.isBoolean()); assertEquals(innerValue, value.asBoolean()); } - @Test public void numericArgShouldReturnDoubleOrInt() { + @Test + public void numericArgShouldReturnDoubleOrInt() { double innerDoubleValue = 1.75; Value doubleValue = new Value(innerDoubleValue); assertTrue(doubleValue.isNumber()); - assertEquals(1, doubleValue.asInteger()); // the double value represented by this object converted to type int + assertEquals(1, doubleValue.asInteger()); // the double value represented by this object converted to type int assertEquals(1.75, doubleValue.asDouble()); int innerIntValue = 100; @@ -80,21 +85,24 @@ class Something {} assertEquals(innerIntValue, intValue.asDouble()); } - @Test public void stringArgShouldContainString() { + @Test + public void stringArgShouldContainString() { String innerValue = "hi!"; Value value = new Value(innerValue); assertTrue(value.isString()); assertEquals(innerValue, value.asString()); } - @Test public void dateShouldContainDate() { + @Test + public void dateShouldContainDate() { Instant innerValue = Instant.now(); Value value = new Value(innerValue); assertTrue(value.isInstant()); assertEquals(innerValue, value.asInstant()); } - @Test public void structureShouldContainStructure() { + @Test + public void structureShouldContainStructure() { String INNER_KEY = "key"; String INNER_VALUE = "val"; MutableStructure innerValue = new MutableStructure().add(INNER_KEY, INNER_VALUE); @@ -103,7 +111,8 @@ class Something {} assertEquals(INNER_VALUE, value.asStructure().getValue(INNER_KEY).asString()); } - @Test public void listArgShouldContainList() { + @Test + public void listArgShouldContainList() { String ITEM_VALUE = "val"; List innerValue = new ArrayList(); innerValue.add(new Value(ITEM_VALUE)); @@ -112,7 +121,8 @@ class Something {} assertEquals(ITEM_VALUE, value.asList().get(0).asString()); } - @Test public void listMustBeOfValues() { + @Test + public void listMustBeOfValues() { String item = "item"; List list = new ArrayList<>(); list.add(item); @@ -124,7 +134,8 @@ class Something {} } } - @Test public void emptyListAllowed() { + @Test + public void emptyListAllowed() { List list = new ArrayList<>(); try { Value value = new Value((Object) list); @@ -136,15 +147,17 @@ class Something {} } } - @Test public void valueConstructorValidateListInternals() { + @Test + public void valueConstructorValidateListInternals() { List list = new ArrayList<>(); list.add(new Value("item")); list.add("item"); - assertThrows(InstantiationException.class, ()-> new Value(list)); + assertThrows(InstantiationException.class, () -> new Value(list)); } - @Test public void noOpFinalize() { + @Test + public void noOpFinalize() { Value val = new Value(); assertDoesNotThrow(val::finalize); // does nothing, but we want to defined in and make it final. } diff --git a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java index cb9422e3..5bc89d03 100644 --- a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java +++ b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java @@ -6,17 +6,6 @@ import static dev.openfeature.sdk.testutils.TestFlagsUtils.OBJECT_FLAG_KEY; import static dev.openfeature.sdk.testutils.TestFlagsUtils.STRING_FLAG_KEY; -import java.util.Map; -import java.util.HashMap; -import java.util.Optional; - -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Warmup; - import dev.openfeature.sdk.Client; import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.Hook; @@ -26,6 +15,13 @@ import dev.openfeature.sdk.NoOpProvider; import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Value; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Mode; /** * Runs a large volume of flag evaluations on a VM with 1G memory and GC @@ -38,7 +34,7 @@ public class AllocationBenchmark { @Benchmark @BenchmarkMode(Mode.SingleShotTime) - @Fork(jvmArgsAppend = { "-Xmx1024m", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC" }) + @Fork(jvmArgsAppend = {"-Xmx1024m", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC"}) public void run() { OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider()); @@ -50,7 +46,7 @@ public void run() { Client client = OpenFeatureAPI.getInstance().getClient(); Map clientAttrs = new HashMap<>(); - clientAttrs.put("client", new Value(2)); + clientAttrs.put("client", new Value(2)); client.setEvaluationContext(new ImmutableContext(clientAttrs)); client.addHooks(new Hook() { @Override @@ -60,7 +56,7 @@ public Optional before(HookContext ctx, Map invocationAttrs = new HashMap<>(); - invocationAttrs.put("invoke", new Value(3)); + invocationAttrs.put("invoke", new Value(3)); EvaluationContext invocationContext = new ImmutableContext(invocationAttrs); for (int i = 0; i < ITERATIONS; i++) { diff --git a/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java b/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java index 8051a167..db048f8d 100644 --- a/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java +++ b/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java @@ -8,7 +8,6 @@ import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; - import org.openjdk.jmh.infra.BenchmarkParams; import org.openjdk.jmh.infra.IterationParams; import org.openjdk.jmh.profile.InternalProfiler; @@ -45,16 +44,16 @@ public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams ite } @Override - public Collection afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, - IterationResult result) { + public Collection afterIteration( + BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) { long totalHeap = Runtime.getRuntime().totalMemory(); AllocationTotals allocationTotals = AllocationProfiler.printHeapHistogram(System.out, 120); Collection results = new ArrayList<>(); results.add(new ScalarResult("+totalHeap", totalHeap, "bytes", AggregationPolicy.MAX)); - results.add(new ScalarResult("+totalAllocatedInstances", allocationTotals.instances, "instances", - AggregationPolicy.MAX)); + results.add(new ScalarResult( + "+totalAllocatedInstances", allocationTotals.instances, "instances", AggregationPolicy.MAX)); results.add(new ScalarResult("+totalAllocatedBytes", allocationTotals.bytes, "bytes", AggregationPolicy.MAX)); return results; @@ -66,25 +65,19 @@ private static String getJmapExcutable() { if (javaHome.endsWith(jreDir)) { javaHome = javaHome.substring(0, javaHome.length() - jreDir.length()); } - return (javaHome + - File.separator + - "bin" + - File.separator + - "jmap" + - (Utils.isWindows() ? ".exe" : "")); + return (javaHome + File.separator + "bin" + File.separator + "jmap" + (Utils.isWindows() ? ".exe" : "")); } // runs JMAP executable in a new process to collect a heap dump - // heavily inspired by: https://github.com/cache2k/cache2k-benchmark/blob/master/jmh-suite/src/main/java/org/cache2k/benchmark/jmh/HeapProfiler.java + // heavily inspired by: + // https://github.com/cache2k/cache2k-benchmark/blob/master/jmh-suite/src/main/java/org/cache2k/benchmark/jmh/HeapProfiler.java private static AllocationTotals printHeapHistogram(PrintStream out, int maxLines) { long totalBytes = 0; long totalInstances = 0; boolean partial = false; try { - Process jmapProcess = Runtime.getRuntime().exec(new String[] { - getJmapExcutable(), - "-histo:live", - Long.toString(Utils.getPid()) }); + Process jmapProcess = Runtime.getRuntime() + .exec(new String[] {getJmapExcutable(), "-histo:live", Long.toString(Utils.getPid())}); InputStream in = jmapProcess.getInputStream(); LineNumberReader r = new LineNumberReader(new InputStreamReader(in)); String line; @@ -121,4 +114,4 @@ private static AllocationTotals printHeapHistogram(PrintStream out, int maxLines } return new AllocationTotals(totalInstances, totalBytes); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/e2e/EvaluationTest.java b/src/test/java/dev/openfeature/sdk/e2e/EvaluationTest.java index 3e0f2ee8..8a338141 100644 --- a/src/test/java/dev/openfeature/sdk/e2e/EvaluationTest.java +++ b/src/test/java/dev/openfeature/sdk/e2e/EvaluationTest.java @@ -1,21 +1,16 @@ package dev.openfeature.sdk.e2e; +import static io.cucumber.junit.platform.engine.Constants.GLUE_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.Suite; -import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; -import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; - @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features/evaluation.feature") @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.sdk.e2e.evaluation") -public class EvaluationTest { - -} - - - +public class EvaluationTest {} diff --git a/src/test/java/dev/openfeature/sdk/e2e/evaluation/StepDefinitions.java b/src/test/java/dev/openfeature/sdk/e2e/evaluation/StepDefinitions.java index cf190592..c1e56429 100644 --- a/src/test/java/dev/openfeature/sdk/e2e/evaluation/StepDefinitions.java +++ b/src/test/java/dev/openfeature/sdk/e2e/evaluation/StepDefinitions.java @@ -1,27 +1,26 @@ package dev.openfeature.sdk.e2e.evaluation; -import dev.openfeature.sdk.Value; -import dev.openfeature.sdk.EvaluationContext; -import dev.openfeature.sdk.Reason; +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; +import dev.openfeature.sdk.FlagEvaluationDetails; +import dev.openfeature.sdk.ImmutableContext; import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Reason; import dev.openfeature.sdk.Structure; -import dev.openfeature.sdk.ImmutableContext; -import dev.openfeature.sdk.FlagEvaluationDetails; +import dev.openfeature.sdk.Value; import dev.openfeature.sdk.providers.memory.Flag; import dev.openfeature.sdk.providers.memory.InMemoryProvider; import io.cucumber.java.BeforeAll; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import lombok.SneakyThrows; - import java.util.HashMap; import java.util.Map; - -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 lombok.SneakyThrows; public class StepDefinitions { @@ -66,8 +65,8 @@ public static void setup() { // boolean value @When("a boolean flag with key {string} is evaluated with default value {string}") - public void a_boolean_flag_with_key_boolean_flag_is_evaluated_with_default_value_false(String flagKey, - String defaultValue) { + public void a_boolean_flag_with_key_boolean_flag_is_evaluated_with_default_value_false( + String flagKey, String defaultValue) { this.booleanFlagValue = client.getBooleanValue(flagKey, Boolean.valueOf(defaultValue)); } @@ -115,12 +114,19 @@ public void an_object_flag_with_key_is_evaluated_with_a_null_default_value(Strin this.objectFlagValue = client.getObjectValue(flagKey, new Value()); } - @Then("the resolved object value should be contain fields {string}, {string}, and {string}, with values {string}, {string} and {int}, respectively") - public void the_resolved_object_value_should_be_contain_fields_and_with_values_and_respectively(String boolField, - String stringField, String numberField, String boolValue, String stringValue, int numberValue) { + @Then( + "the resolved object value should be contain fields {string}, {string}, and {string}, with values {string}, {string} and {int}, respectively") + public void the_resolved_object_value_should_be_contain_fields_and_with_values_and_respectively( + String boolField, + String stringField, + String numberField, + String boolValue, + String stringValue, + int numberValue) { Structure structure = this.objectFlagValue.asStructure(); - assertEquals(Boolean.valueOf(boolValue), structure.asMap().get(boolField).asBoolean()); + assertEquals( + Boolean.valueOf(boolValue), structure.asMap().get(boolField).asBoolean()); assertEquals(stringValue, structure.asMap().get(stringField).asString()); assertEquals(numberValue, structure.asMap().get(numberField).asInteger()); } @@ -131,15 +137,15 @@ public void the_resolved_object_value_should_be_contain_fields_and_with_values_a // boolean details @When("a boolean flag with key {string} is evaluated with details and default value {string}") - public void a_boolean_flag_with_key_is_evaluated_with_details_and_default_value(String flagKey, - String defaultValue) { + public void a_boolean_flag_with_key_is_evaluated_with_details_and_default_value( + String flagKey, String defaultValue) { this.booleanFlagDetails = client.getBooleanDetails(flagKey, Boolean.valueOf(defaultValue)); } - @Then("the resolved boolean details value should be {string}, the variant should be {string}, and the reason should be {string}") + @Then( + "the resolved boolean details value should be {string}, the variant should be {string}, and the reason should be {string}") public void the_resolved_boolean_value_should_be_the_variant_should_be_and_the_reason_should_be( - String expectedValue, - String expectedVariant, String expectedReason) { + String expectedValue, String expectedVariant, String expectedReason) { assertEquals(Boolean.valueOf(expectedValue), booleanFlagDetails.getValue()); assertEquals(expectedVariant, booleanFlagDetails.getVariant()); assertEquals(expectedReason, booleanFlagDetails.getReason()); @@ -147,14 +153,15 @@ public void the_resolved_boolean_value_should_be_the_variant_should_be_and_the_r // string details @When("a string flag with key {string} is evaluated with details and default value {string}") - public void a_string_flag_with_key_is_evaluated_with_details_and_default_value(String flagKey, - String defaultValue) { + public void a_string_flag_with_key_is_evaluated_with_details_and_default_value( + String flagKey, String defaultValue) { this.stringFlagDetails = client.getStringDetails(flagKey, defaultValue); } - @Then("the resolved string details value should be {string}, the variant should be {string}, and the reason should be {string}") - public void the_resolved_string_value_should_be_the_variant_should_be_and_the_reason_should_be(String expectedValue, - String expectedVariant, String expectedReason) { + @Then( + "the resolved string details value should be {string}, the variant should be {string}, and the reason should be {string}") + public void the_resolved_string_value_should_be_the_variant_should_be_and_the_reason_should_be( + String expectedValue, String expectedVariant, String expectedReason) { assertEquals(expectedValue, this.stringFlagDetails.getValue()); assertEquals(expectedVariant, this.stringFlagDetails.getVariant()); assertEquals(expectedReason, this.stringFlagDetails.getReason()); @@ -166,9 +173,10 @@ public void an_integer_flag_with_key_is_evaluated_with_details_and_default_value this.intFlagDetails = client.getIntegerDetails(flagKey, defaultValue); } - @Then("the resolved integer details value should be {int}, the variant should be {string}, and the reason should be {string}") - public void the_resolved_integer_value_should_be_the_variant_should_be_and_the_reason_should_be(int expectedValue, - String expectedVariant, String expectedReason) { + @Then( + "the resolved integer details value should be {int}, the variant should be {string}, and the reason should be {string}") + public void the_resolved_integer_value_should_be_the_variant_should_be_and_the_reason_should_be( + int expectedValue, String expectedVariant, String expectedReason) { assertEquals(expectedValue, this.intFlagDetails.getValue()); assertEquals(expectedVariant, this.intFlagDetails.getVariant()); assertEquals(expectedReason, this.intFlagDetails.getReason()); @@ -180,9 +188,10 @@ public void a_float_flag_with_key_is_evaluated_with_details_and_default_value(St this.doubleFlagDetails = client.getDoubleDetails(flagKey, defaultValue); } - @Then("the resolved float details value should be {double}, the variant should be {string}, and the reason should be {string}") - public void the_resolved_float_value_should_be_the_variant_should_be_and_the_reason_should_be(double expectedValue, - String expectedVariant, String expectedReason) { + @Then( + "the resolved float details value should be {double}, the variant should be {string}, and the reason should be {string}") + public void the_resolved_float_value_should_be_the_variant_should_be_and_the_reason_should_be( + double expectedValue, String expectedVariant, String expectedReason) { assertEquals(expectedValue, this.doubleFlagDetails.getValue()); assertEquals(expectedVariant, this.doubleFlagDetails.getVariant()); assertEquals(expectedReason, this.doubleFlagDetails.getReason()); @@ -194,13 +203,19 @@ public void an_object_flag_with_key_is_evaluated_with_details_and_a_null_default this.objectFlagDetails = client.getObjectDetails(flagKey, new Value()); } - @Then("the resolved object details value should be contain fields {string}, {string}, and {string}, with values {string}, {string} and {int}, respectively") + @Then( + "the resolved object details value should be contain fields {string}, {string}, and {string}, with values {string}, {string} and {int}, respectively") public void the_resolved_object_value_should_be_contain_fields_and_with_values_and_respectively_again( String boolField, - String stringField, String numberField, String boolValue, String stringValue, int numberValue) { + String stringField, + String numberField, + String boolValue, + String stringValue, + int numberValue) { Structure structure = this.objectFlagDetails.getValue().asStructure(); - assertEquals(Boolean.valueOf(boolValue), structure.asMap().get(boolField).asBoolean()); + assertEquals( + Boolean.valueOf(boolValue), structure.asMap().get(boolField).asBoolean()); assertEquals(stringValue, structure.asMap().get(stringField).asString()); assertEquals(numberValue, structure.asMap().get(numberField).asInteger()); } @@ -215,9 +230,17 @@ public void the_variant_should_be_and_the_reason_should_be(String expectedVarian * Context-aware evaluation */ - @When("context contains keys {string}, {string}, {string}, {string} with values {string}, {string}, {int}, {string}") - public void context_contains_keys_with_values(String field1, String field2, String field3, String field4, - String value1, String value2, Integer value3, String value4) { + @When( + "context contains keys {string}, {string}, {string}, {string} with values {string}, {string}, {int}, {string}") + public void context_contains_keys_with_values( + String field1, + String field2, + String field3, + String field4, + String value1, + String value2, + Integer value3, + String value4) { Map attributes = new HashMap<>(); attributes.put(field1, new Value(value1)); attributes.put(field2, new Value(value2)); @@ -231,7 +254,6 @@ public void an_a_flag_with_key_is_evaluated(String flagKey, String defaultValue) contextAwareFlagKey = flagKey; contextAwareDefaultValue = defaultValue; contextAwareValue = client.getStringValue(flagKey, contextAwareDefaultValue, context); - } @Then("the resolved string response should be {string}") @@ -241,8 +263,8 @@ public void the_resolved_string_response_should_be(String expected) { @Then("the resolved flag value is {string} when the context is empty") public void the_resolved_flag_value_is_when_the_context_is_empty(String expected) { - String emptyContextValue = client.getStringValue(contextAwareFlagKey, contextAwareDefaultValue, - new ImmutableContext()); + String emptyContextValue = + client.getStringValue(contextAwareFlagKey, contextAwareDefaultValue, new ImmutableContext()); assertEquals(expected, emptyContextValue); } @@ -252,8 +274,8 @@ public void the_resolved_flag_value_is_when_the_context_is_empty(String expected // not found @When("a non-existent string flag with key {string} is evaluated with details and a default value {string}") - public void a_non_existent_string_flag_with_key_is_evaluated_with_details_and_a_default_value(String flagKey, - String defaultValue) { + public void a_non_existent_string_flag_with_key_is_evaluated_with_details_and_a_default_value( + String flagKey, String defaultValue) { notFoundFlagKey = flagKey; notFoundDefaultValue = defaultValue; notFoundDetails = client.getStringDetails(notFoundFlagKey, notFoundDefaultValue); @@ -272,8 +294,8 @@ public void the_reason_should_indicate_an_error_and_the_error_code_should_be_fla // type mismatch @When("a string flag with key {string} is evaluated as an integer, with details and a default value {int}") - public void a_string_flag_with_key_is_evaluated_as_an_integer_with_details_and_a_default_value(String flagKey, - int defaultValue) { + public void a_string_flag_with_key_is_evaluated_as_an_integer_with_details_and_a_default_value( + String flagKey, int defaultValue) { typeErrorFlagKey = flagKey; typeErrorDefaultValue = defaultValue; typeErrorDetails = client.getIntegerDetails(typeErrorFlagKey, typeErrorDefaultValue); @@ -289,5 +311,4 @@ public void the_reason_should_indicate_an_error_and_the_error_code_should_be_typ assertEquals(Reason.ERROR.toString(), typeErrorDetails.getReason()); assertTrue(typeErrorDetails.getErrorCode().name().equals(errorCode)); } - } diff --git a/src/test/java/dev/openfeature/sdk/exceptions/ExceptionUtilsTest.java b/src/test/java/dev/openfeature/sdk/exceptions/ExceptionUtilsTest.java index 58e59457..0a9a522c 100644 --- a/src/test/java/dev/openfeature/sdk/exceptions/ExceptionUtilsTest.java +++ b/src/test/java/dev/openfeature/sdk/exceptions/ExceptionUtilsTest.java @@ -1,6 +1,10 @@ package dev.openfeature.sdk.exceptions; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import dev.openfeature.sdk.ErrorCode; +import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -8,11 +12,6 @@ import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; - class ExceptionUtilsTest { @ParameterizedTest @@ -38,8 +37,7 @@ public Stream provideArguments(ExtensionContext context) { Arguments.of(ErrorCode.INVALID_CONTEXT, InvalidContextError.class), Arguments.of(ErrorCode.PARSE_ERROR, ParseError.class), Arguments.of(ErrorCode.TARGETING_KEY_MISSING, TargetingKeyMissingError.class), - Arguments.of(ErrorCode.TYPE_MISMATCH, TypeMismatchError.class) - ); + Arguments.of(ErrorCode.TYPE_MISMATCH, TypeMismatchError.class)); } } } diff --git a/src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java b/src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java index 9886c383..b94e58a1 100644 --- a/src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java +++ b/src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java @@ -1,13 +1,13 @@ package dev.openfeature.sdk.fixtures; +import static org.mockito.Mockito.spy; + import dev.openfeature.sdk.BooleanHook; import dev.openfeature.sdk.DoubleHook; import dev.openfeature.sdk.Hook; import dev.openfeature.sdk.IntegerHook; import dev.openfeature.sdk.StringHook; -import static org.mockito.Mockito.spy; - public interface HookFixtures { default Hook mockBooleanHook() { @@ -29,5 +29,4 @@ default Hook mockDoubleHook() { default Hook mockGenericHook() { return spy(Hook.class); } - } diff --git a/src/test/java/dev/openfeature/sdk/fixtures/ProviderFixture.java b/src/test/java/dev/openfeature/sdk/fixtures/ProviderFixture.java index c00b8ff2..b9c6bc15 100644 --- a/src/test/java/dev/openfeature/sdk/fixtures/ProviderFixture.java +++ b/src/test/java/dev/openfeature/sdk/fixtures/ProviderFixture.java @@ -7,15 +7,13 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import java.io.FileNotFoundException; -import java.util.concurrent.CountDownLatch; - -import org.mockito.stubbing.Answer; - import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.ImmutableContext; import dev.openfeature.sdk.ProviderState; +import java.io.FileNotFoundException; +import java.util.concurrent.CountDownLatch; import lombok.experimental.UtilityClass; +import org.mockito.stubbing.Answer; @UtilityClass public class ProviderFixture { @@ -56,11 +54,12 @@ private static Answer createAnswerExecutingCode(Runnable onAnswer) { public static FeatureProvider createUnblockingProvider(CountDownLatch latch) throws Exception { FeatureProvider provider = createMockedProvider(); doAnswer(invocation -> { - latch.countDown(); - return null; - }).when(provider).initialize(new ImmutableContext()); + latch.countDown(); + return null; + }) + .when(provider) + .initialize(new ImmutableContext()); doReturn("unblockingProvider").when(provider).toString(); return provider; } - } diff --git a/src/test/java/dev/openfeature/sdk/hooks/logging/LoggingHookTest.java b/src/test/java/dev/openfeature/sdk/hooks/logging/LoggingHookTest.java index fad24caf..b7e463ad 100644 --- a/src/test/java/dev/openfeature/sdk/hooks/logging/LoggingHookTest.java +++ b/src/test/java/dev/openfeature/sdk/hooks/logging/LoggingHookTest.java @@ -45,18 +45,24 @@ class LoggingHookTest { void each() { // create a fake hook context - hookContext = HookContext.builder().flagKey(FLAG_KEY).defaultValue(DEFAULT_VALUE) + hookContext = HookContext.builder() + .flagKey(FLAG_KEY) + .defaultValue(DEFAULT_VALUE) .clientMetadata(new ClientMetadata() { @Override public String getDomain() { return DOMAIN; } - }).providerMetadata(new Metadata() { + }) + .providerMetadata(new Metadata() { @Override public String getName() { return PROVIDER_NAME; } - }).type(FlagValueType.BOOLEAN).ctx(new ImmutableContext()).build(); + }) + .type(FlagValueType.BOOLEAN) + .ctx(new ImmutableContext()) + .build(); // mock logging logger = mock(Logger.class); @@ -95,7 +101,11 @@ void beforeLogsAllPropsAndEvaluationContext() { @Test void afterLogsAllPropsExceptEvaluationContext() { LoggingHook hook = new LoggingHook(); - FlagEvaluationDetails details = FlagEvaluationDetails.builder().reason(REASON).variant(VARIANT).value(VALUE).build(); + FlagEvaluationDetails details = FlagEvaluationDetails.builder() + .reason(REASON) + .variant(VARIANT) + .value(VALUE) + .build(); hook.after(hookContext, details, null); verify(logger).atDebug(); @@ -109,7 +119,11 @@ void afterLogsAllPropsExceptEvaluationContext() { @Test void afterLogsAllPropsAndEvaluationContext() { LoggingHook hook = new LoggingHook(true); - FlagEvaluationDetails details = FlagEvaluationDetails.builder().reason(REASON).variant(VARIANT).value(VALUE).build(); + FlagEvaluationDetails details = FlagEvaluationDetails.builder() + .reason(REASON) + .variant(VARIANT) + .value(VALUE) + .build(); hook.after(hookContext, details, null); verify(logger).atDebug(); @@ -164,4 +178,4 @@ private void verifyErrorProps(LoggingEventBuilder mockBuilder) { verify(mockBuilder).addKeyValue(LoggingHook.ERROR_CODE_KEY, ERROR_CODE); verify(mockBuilder).addKeyValue(LoggingHook.ERROR_MESSAGE_KEY, ERROR_MESSAGE); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/internal/ObjectUtilsTest.java b/src/test/java/dev/openfeature/sdk/internal/ObjectUtilsTest.java index c4525e74..e0efeed6 100644 --- a/src/test/java/dev/openfeature/sdk/internal/ObjectUtilsTest.java +++ b/src/test/java/dev/openfeature/sdk/internal/ObjectUtilsTest.java @@ -1,12 +1,16 @@ package dev.openfeature.sdk.internal; -import java.util.*; - -import org.junit.jupiter.api.*; - import static dev.openfeature.sdk.internal.ObjectUtils.defaultIfNull; import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + class ObjectUtilsTest { @Nested @@ -89,6 +93,4 @@ void shouldReturnGivenMapIfNotNull() { assertThat(actual).isEqualTo(expectedValue); } } - - } diff --git a/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java b/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java index 0c85a7cc..a10fa31f 100644 --- a/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java +++ b/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.concurrent.atomic.AtomicInteger; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -31,4 +30,4 @@ void shouldRunAfterAccept() { composed.accept(1, 2, 3); assertEquals(12, result.get()); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java index 55ddc07c..86782b39 100644 --- a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java +++ b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java @@ -1,26 +1,27 @@ 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.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + import com.google.common.collect.ImmutableMap; -import dev.openfeature.sdk.*; +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.EventDetails; +import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.FlagNotFoundError; -import dev.openfeature.sdk.exceptions.GeneralError; import dev.openfeature.sdk.exceptions.ProviderNotReadyError; import dev.openfeature.sdk.exceptions.TypeMismatchError; -import lombok.SneakyThrows; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; - -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.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.*; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class InMemoryProviderTest { @@ -33,16 +34,17 @@ class InMemoryProviderTest { void beforeEach() { Map> flags = buildFlags(); provider = spy(new InMemoryProvider(flags)); - OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> { - }); + OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {}); OpenFeatureAPI.getInstance().setProviderAndWait(provider); client = OpenFeatureAPI.getInstance().getClient(); provider.updateFlags(flags); - provider.updateFlag("addedFlag", Flag.builder() - .variant("on", true) - .variant("off", false) - .defaultVariant("on") - .build()); + provider.updateFlag( + "addedFlag", + Flag.builder() + .variant("on", true) + .variant("off", false) + .defaultVariant("on") + .build()); } @Test @@ -70,8 +72,7 @@ void getObjectEvaluation() { Value expectedObject = new Value(mapToStructure(ImmutableMap.of( "showImages", new Value(true), "title", new Value("Check out these pics!"), - "imagesPerPage", new Value(100) - ))); + "imagesPerPage", new Value(100)))); assertEquals(expectedObject, client.getObjectValue("object-flag", new Value(true))); } @@ -95,7 +96,9 @@ void shouldThrowIfNotInitialized() { InMemoryProvider inMemoryProvider = new InMemoryProvider(new HashMap<>()); // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client - assertThrows(ProviderNotReadyError.class, () -> inMemoryProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); + assertThrows( + ProviderNotReadyError.class, + () -> inMemoryProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); } @SuppressWarnings("unchecked") @@ -110,6 +113,7 @@ void emitChangedFlagsOnlyIfThereAreChangedFlags() { provider.updateFlags(flags); await().untilAsserted(() -> verify(handler, times(1)) - .accept(argThat(details -> details.getFlagsChanged().size() == buildFlags().size()))); + .accept(argThat(details -> + details.getFlagsChanged().size() == buildFlags().size()))); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java b/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java index 12fb71b1..c9ad77d8 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java +++ b/src/test/java/dev/openfeature/sdk/testutils/FeatureProviderTestUtils.java @@ -1,13 +1,13 @@ 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 dev.openfeature.sdk.*; import lombok.experimental.UtilityClass; -import static org.awaitility.Awaitility.await; - // todo check the need of this utility class as we now have setProviderAndWait capability @UtilityClass public class FeatureProviderTestUtils { @@ -17,11 +17,11 @@ public static void setFeatureProvider(FeatureProvider provider) { waitForProviderInitializationComplete(OpenFeatureAPI::getProvider, provider); } - private static void waitForProviderInitializationComplete(Function extractor, FeatureProvider provider) { - await() - .pollDelay(Duration.ofMillis(1)) - .atMost(Duration.ofSeconds(1)) - .until(() -> extractor.apply(OpenFeatureAPI.getInstance()).equals(provider)); + private static void waitForProviderInitializationComplete( + Function 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) { diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java b/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java index 1944fce2..7cd2ea31 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java +++ b/src/test/java/dev/openfeature/sdk/testutils/TestEventsProvider.java @@ -1,6 +1,13 @@ package dev.openfeature.sdk.testutils; -import dev.openfeature.sdk.*; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EventProvider; +import dev.openfeature.sdk.Metadata; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.ProviderEvent; +import dev.openfeature.sdk.ProviderEventDetails; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.FatalError; import dev.openfeature.sdk.exceptions.GeneralError; import lombok.SneakyThrows; @@ -16,8 +23,7 @@ public class TestEventsProvider extends EventProvider { private Metadata metadata = () -> name; private boolean isFatalInitError = false; - public TestEventsProvider() { - } + public TestEventsProvider() {} public TestEventsProvider(int initTimeoutMs) { this.initTimeoutMs = initTimeoutMs; @@ -110,8 +116,8 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default } @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, - EvaluationContext invocationContext) { + public ProviderEvaluation getObjectEvaluation( + String key, Value defaultValue, EvaluationContext invocationContext) { return ProviderEvaluation.builder() .value(defaultValue) .variant(PASSED_IN_DEFAULT) diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java b/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java index dd2d03ca..157b0717 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java +++ b/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java @@ -1,14 +1,13 @@ package dev.openfeature.sdk.testutils; +import static dev.openfeature.sdk.Structure.mapToStructure; + import com.google.common.collect.ImmutableMap; import dev.openfeature.sdk.Value; import dev.openfeature.sdk.providers.memory.Flag; -import lombok.experimental.UtilityClass; - import java.util.HashMap; import java.util.Map; - -import static dev.openfeature.sdk.Structure.mapToStructure; +import lombok.experimental.UtilityClass; /** * Test flags utils. @@ -30,52 +29,67 @@ public class TestFlagsUtils { */ public static Map> buildFlags() { Map> flags = new HashMap<>(); - flags.put(BOOLEAN_FLAG_KEY, Flag.builder() - .variant("on", true) - .variant("off", false) - .defaultVariant("on") - .build()); - flags.put(STRING_FLAG_KEY, Flag.builder() - .variant("greeting", "hi") - .variant("parting", "bye") - .defaultVariant("greeting") - .build()); - flags.put(INT_FLAG_KEY, Flag.builder() - .variant("one", 1) - .variant("ten", 10) - .defaultVariant("ten") - .build()); - flags.put(FLOAT_FLAG_KEY, Flag.builder() - .variant("tenth", 0.1) - .variant("half", 0.5) - .defaultVariant("half") - .build()); - flags.put(OBJECT_FLAG_KEY, Flag.builder() - .variant("empty", new HashMap<>()) - .variant("template", new Value(mapToStructure(ImmutableMap.of( - "showImages", new Value(true), - "title", new Value("Check out these pics!"), - "imagesPerPage", new Value(100) - )))) - .defaultVariant("template") - .build()); - flags.put(CONTEXT_AWARE_FLAG_KEY, Flag.builder() - .variant("internal", "INTERNAL") - .variant("external", "EXTERNAL") - .defaultVariant("external") - .contextEvaluator((flag, evaluationContext) -> { - if (new Value(false).equals(evaluationContext.getValue("customer"))) { - return (String) flag.getVariants().get("internal"); - } else { - return (String) flag.getVariants().get(flag.getDefaultVariant()); - } - }) - .build()); - flags.put(WRONG_FLAG_KEY, Flag.builder() - .variant("one", "uno") - .variant("two", "dos") - .defaultVariant("one") - .build()); + flags.put( + BOOLEAN_FLAG_KEY, + Flag.builder() + .variant("on", true) + .variant("off", false) + .defaultVariant("on") + .build()); + flags.put( + STRING_FLAG_KEY, + Flag.builder() + .variant("greeting", "hi") + .variant("parting", "bye") + .defaultVariant("greeting") + .build()); + flags.put( + INT_FLAG_KEY, + Flag.builder() + .variant("one", 1) + .variant("ten", 10) + .defaultVariant("ten") + .build()); + flags.put( + FLOAT_FLAG_KEY, + Flag.builder() + .variant("tenth", 0.1) + .variant("half", 0.5) + .defaultVariant("half") + .build()); + flags.put( + OBJECT_FLAG_KEY, + Flag.builder() + .variant("empty", new HashMap<>()) + .variant( + "template", + new Value(mapToStructure(ImmutableMap.of( + "showImages", new Value(true), + "title", new Value("Check out these pics!"), + "imagesPerPage", new Value(100))))) + .defaultVariant("template") + .build()); + flags.put( + CONTEXT_AWARE_FLAG_KEY, + Flag.builder() + .variant("internal", "INTERNAL") + .variant("external", "EXTERNAL") + .defaultVariant("external") + .contextEvaluator((flag, evaluationContext) -> { + if (new Value(false).equals(evaluationContext.getValue("customer"))) { + return (String) flag.getVariants().get("internal"); + } else { + return (String) flag.getVariants().get(flag.getDefaultVariant()); + } + }) + .build()); + flags.put( + WRONG_FLAG_KEY, + Flag.builder() + .variant("one", "uno") + .variant("two", "dos") + .defaultVariant("one") + .build()); return flags; } } diff --git a/src/test/java/dev/openfeature/sdk/testutils/stubbing/ConditionStubber.java b/src/test/java/dev/openfeature/sdk/testutils/stubbing/ConditionStubber.java index 11cf2649..886a7bbd 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/stubbing/ConditionStubber.java +++ b/src/test/java/dev/openfeature/sdk/testutils/stubbing/ConditionStubber.java @@ -1,13 +1,13 @@ package dev.openfeature.sdk.testutils.stubbing; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.mockito.Mockito.doAnswer; + import java.time.Duration; import java.util.concurrent.CountDownLatch; - import lombok.experimental.UtilityClass; -import org.mockito.stubbing.*; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.mockito.Mockito.doAnswer; +import org.mockito.stubbing.Answer; +import org.mockito.stubbing.Stubber; @UtilityClass public class ConditionStubber { @@ -33,5 +33,4 @@ public static Stubber doBlock(CountDownLatch latch, Answer answer) { return answer.answer(invocation); }); } - } From 20bbb2337cb5afbee9b8d5143b45416673cb4154 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:24:06 +0000 Subject: [PATCH 20/28] chore(deps): update dependency org.assertj:assertj-core to v3.27.1 (#1266) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7fe58c6..85a46562 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ org.assertj assertj-core - 3.27.0 + 3.27.1 test From 2e10d34920f57d863c09ce1522c9ccff20413f74 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:14:06 +0000 Subject: [PATCH 21/28] chore(deps): update dependency org.assertj:assertj-core to v3.27.2 (#1268) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 85a46562..fb441318 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ org.assertj assertj-core - 3.27.1 + 3.27.2 test From a1c558f4ffb95772bd141ab7660e2c5b065482f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:06:18 +0000 Subject: [PATCH 22/28] chore(deps): update actions/cache digest to 53aa38c (#1270) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/merge.yml | 2 +- .github/workflows/pullrequest.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 92993ff5..050d4036 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -32,7 +32,7 @@ jobs: server-password: ${{ secrets.OSSRH_PASSWORD }} - name: Cache local Maven repository - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 + uses: actions/cache@53aa38c736a561b9c17b62df3fe885a17b78ee6d with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 4fa803aa..142c29e7 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -25,7 +25,7 @@ jobs: languages: java - name: Cache local Maven repository - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 + uses: actions/cache@53aa38c736a561b9c17b62df3fe885a17b78ee6d with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} From 4086dea703a950dcacc792be9a9346cc1fa8409d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 00:40:07 +0000 Subject: [PATCH 23/28] chore(deps): update github/codeql-action digest to 3407610 (#1269) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 142c29e7..d0d3cb7c 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@5b6e617dc0241b2d60c2bccea90c56b67eceb797 + uses: github/codeql-action/init@3407610120cd5656b6fc71991415cb50748b9489 with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5b6e617dc0241b2d60c2bccea90c56b67eceb797 + uses: github/codeql-action/analyze@3407610120cd5656b6fc71991415cb50748b9489 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index f03229c2..41ecc36d 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5b6e617dc0241b2d60c2bccea90c56b67eceb797 + uses: github/codeql-action/init@3407610120cd5656b6fc71991415cb50748b9489 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@5b6e617dc0241b2d60c2bccea90c56b67eceb797 + uses: github/codeql-action/autobuild@3407610120cd5656b6fc71991415cb50748b9489 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5b6e617dc0241b2d60c2bccea90c56b67eceb797 + uses: github/codeql-action/analyze@3407610120cd5656b6fc71991415cb50748b9489 From 3c97b7baaf9eee719479c059cb923d8d64f2c25f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:21:18 +0000 Subject: [PATCH 24/28] chore(deps): update github/codeql-action digest to fb65b6c (#1273) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index d0d3cb7c..96e800be 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@3407610120cd5656b6fc71991415cb50748b9489 + uses: github/codeql-action/init@fb65b6ce7884900fde5b15518bec92ad6875180e with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3407610120cd5656b6fc71991415cb50748b9489 + uses: github/codeql-action/analyze@fb65b6ce7884900fde5b15518bec92ad6875180e diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 41ecc36d..24f3104b 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@3407610120cd5656b6fc71991415cb50748b9489 + uses: github/codeql-action/init@fb65b6ce7884900fde5b15518bec92ad6875180e with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@3407610120cd5656b6fc71991415cb50748b9489 + uses: github/codeql-action/autobuild@fb65b6ce7884900fde5b15518bec92ad6875180e - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3407610120cd5656b6fc71991415cb50748b9489 + uses: github/codeql-action/analyze@fb65b6ce7884900fde5b15518bec92ad6875180e From ae85278c30eb5279b80ea73ec6b92db040ad0bb7 Mon Sep 17 00:00:00 2001 From: chrfwow Date: Thu, 9 Jan 2025 22:12:14 +0100 Subject: [PATCH 25/28] feat: Add evaluation details to finally hook stage #1246 (#1262) Signed-off-by: christian.lutnik Co-authored-by: Todd Baert --- README.md | 2 +- src/main/java/dev/openfeature/sdk/Hook.java | 2 +- .../java/dev/openfeature/sdk/HookSupport.java | 8 +- .../openfeature/sdk/OpenFeatureClient.java | 2 +- .../sdk/DeveloperExperienceTest.java | 6 +- .../dev/openfeature/sdk/HookSpecTest.java | 100 +++++++++++++++--- .../dev/openfeature/sdk/HookSupportTest.java | 8 +- .../sdk/OpenFeatureClientTest.java | 59 +---------- 8 files changed, 106 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index e58ef9d8..44f4c81c 100644 --- a/README.md +++ b/README.md @@ -426,7 +426,7 @@ class MyHook implements Hook { } @Override - public void finallyAfter(HookContext ctx, Map hints) { + public void finallyAfter(HookContext ctx, FlagEvaluationDetails details, Map hints) { // code that runs regardless of success or error } }; diff --git a/src/main/java/dev/openfeature/sdk/Hook.java b/src/main/java/dev/openfeature/sdk/Hook.java index 9ca7e6b9..08aa1831 100644 --- a/src/main/java/dev/openfeature/sdk/Hook.java +++ b/src/main/java/dev/openfeature/sdk/Hook.java @@ -46,7 +46,7 @@ default void error(HookContext ctx, Exception error, Map hint * @param ctx Information about the particular flag evaluation * @param hints An immutable mapping of data for users to communicate to the hooks. */ - default void finallyAfter(HookContext ctx, Map hints) {} + default void finallyAfter(HookContext ctx, FlagEvaluationDetails details, Map hints) {} default boolean supportsFlagValueType(FlagValueType flagValueType) { return true; diff --git a/src/main/java/dev/openfeature/sdk/HookSupport.java b/src/main/java/dev/openfeature/sdk/HookSupport.java index 95c8ff17..73518ee8 100644 --- a/src/main/java/dev/openfeature/sdk/HookSupport.java +++ b/src/main/java/dev/openfeature/sdk/HookSupport.java @@ -29,8 +29,12 @@ public void afterHooks( } public void afterAllHooks( - FlagValueType flagValueType, HookContext hookCtx, List hooks, Map hints) { - executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, hints)); + FlagValueType flagValueType, + HookContext hookCtx, + FlagEvaluationDetails details, + List hooks, + Map hints) { + executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, details, hints)); } public void errorHooks( diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index 60f987b7..66f25f60 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -228,7 +228,7 @@ private FlagEvaluationDetails evaluateFlag( enrichDetailsWithErrorDefaults(defaultValue, details); hookSupport.errorHooks(type, afterHookContext, e, mergedHooks, hints); } finally { - hookSupport.afterAllHooks(type, afterHookContext, mergedHooks, hints); + hookSupport.afterAllHooks(type, afterHookContext, details, mergedHooks, hints); } return details; diff --git a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java index c39c5ba3..aacf0916 100644 --- a/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java +++ b/src/test/java/dev/openfeature/sdk/DeveloperExperienceTest.java @@ -39,7 +39,7 @@ void clientHooks() { Client client = api.getClient(); client.addHooks(exampleHook); Boolean retval = client.getBooleanValue(flagKey, false); - verify(exampleHook, times(1)).finallyAfter(any(), any()); + verify(exampleHook, times(1)).finallyAfter(any(), any(), any()); assertFalse(retval); } @@ -57,8 +57,8 @@ void evalHooks() { false, null, FlagEvaluationOptions.builder().hook(evalHook).build()); - verify(clientHook, times(1)).finallyAfter(any(), any()); - verify(evalHook, times(1)).finallyAfter(any(), any()); + verify(clientHook, times(1)).finallyAfter(any(), any(), any()); + verify(evalHook, times(1)).finallyAfter(any(), any(), any()); assertFalse(retval); } diff --git a/src/test/java/dev/openfeature/sdk/HookSpecTest.java b/src/test/java/dev/openfeature/sdk/HookSpecTest.java index 4a141c61..d6247c64 100644 --- a/src/test/java/dev/openfeature/sdk/HookSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/HookSpecTest.java @@ -1,10 +1,20 @@ package dev.openfeature.sdk; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assertions.*; +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 static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import dev.openfeature.sdk.exceptions.FlagNotFoundError; import dev.openfeature.sdk.fixtures.HookFixtures; @@ -187,7 +197,7 @@ void feo_has_hook_list() { void error_hook_run_during_non_finally_stage() { final boolean[] error_called = {false}; Hook h = mockBooleanHook(); - doThrow(RuntimeException.class).when(h).finallyAfter(any(), any()); + doThrow(RuntimeException.class).when(h).finallyAfter(any(), any(), any()); verify(h, times(0)).error(any(), any(), any()); } @@ -219,7 +229,7 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() { verify(hook, times(1)).before(any(), any()); verify(hook, times(1)).error(any(), captor.capture(), any()); - verify(hook, times(1)).finallyAfter(any(), any()); + verify(hook, times(1)).finallyAfter(any(), any(), any()); verify(hook, never()).after(any(), any(), any()); Exception exception = captor.getValue(); @@ -274,7 +284,10 @@ public void error(HookContext ctx, Exception error, Map } @Override - public void finallyAfter(HookContext ctx, Map hints) { + public void finallyAfter( + HookContext ctx, + FlagEvaluationDetails details, + Map hints) { evalOrder.add("provider finally"); } }); @@ -300,7 +313,8 @@ public void error(HookContext ctx, Exception error, Map } @Override - public void finallyAfter(HookContext ctx, Map hints) { + public void finallyAfter( + HookContext ctx, FlagEvaluationDetails details, Map hints) { evalOrder.add("api finally"); } }); @@ -325,7 +339,8 @@ public void error(HookContext ctx, Exception error, Map } @Override - public void finallyAfter(HookContext ctx, Map hints) { + public void finallyAfter( + HookContext ctx, FlagEvaluationDetails details, Map hints) { evalOrder.add("client finally"); } }); @@ -357,7 +372,10 @@ public void error(HookContext ctx, Exception error, Map } @Override - public void finallyAfter(HookContext ctx, Map hints) { + public void finallyAfter( + HookContext ctx, + FlagEvaluationDetails details, + Map hints) { evalOrder.add("invocation finally"); } }) @@ -462,7 +480,8 @@ public void error(HookContext ctx, Exception error, Map } @Override - public void finallyAfter(HookContext ctx, Map hints) { + public void finallyAfter( + HookContext ctx, FlagEvaluationDetails details, Map hints) { assertThatCode(() -> hints.put(hintKey, "changed value")) .isInstanceOf(UnsupportedOperationException.class); } @@ -509,7 +528,7 @@ void flag_eval_hook_order() { order.verify(hook).before(any(), any()); order.verify(provider).getBooleanEvaluation(any(), any(), any()); order.verify(hook).after(any(), any(), any()); - order.verify(hook).finallyAfter(any(), any()); + order.verify(hook).finallyAfter(any(), any(), any()); } @Specification( @@ -550,6 +569,58 @@ void error_hooks__after() { verify(hook, times(1)).error(any(), any(), any()); } + @Test + void erroneous_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails() { + Hook hook = mockBooleanHook(); + doThrow(RuntimeException.class).when(hook).after(any(), any(), any()); + String flagKey = "test-flag-key"; + Client client = getClient(TestEventsProvider.newInitializedTestEventsProvider()); + client.getBooleanValue( + flagKey, + true, + new ImmutableContext(), + FlagEvaluationOptions.builder().hook(hook).build()); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(FlagEvaluationDetails.class); + verify(hook).finallyAfter(any(), captor.capture(), any()); + + FlagEvaluationDetails evaluationDetails = captor.getValue(); + assertThat(evaluationDetails).isNotNull(); + + assertThat(evaluationDetails.getErrorCode()).isEqualTo(ErrorCode.GENERAL); + assertThat(evaluationDetails.getReason()).isEqualTo("ERROR"); + assertThat(evaluationDetails.getVariant()).isEqualTo("Passed in default"); + assertThat(evaluationDetails.getFlagKey()).isEqualTo(flagKey); + assertThat(evaluationDetails.getFlagMetadata()) + .isEqualTo(ImmutableMetadata.builder().build()); + assertThat(evaluationDetails.getValue()).isTrue(); + } + + @Test + void successful_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails() { + Hook hook = mockBooleanHook(); + String flagKey = "test-flag-key"; + Client client = getClient(TestEventsProvider.newInitializedTestEventsProvider()); + client.getBooleanValue( + flagKey, + true, + new ImmutableContext(), + FlagEvaluationOptions.builder().hook(hook).build()); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(FlagEvaluationDetails.class); + verify(hook).finallyAfter(any(), captor.capture(), any()); + + FlagEvaluationDetails evaluationDetails = captor.getValue(); + assertThat(evaluationDetails).isNotNull(); + assertThat(evaluationDetails.getErrorCode()).isNull(); + assertThat(evaluationDetails.getReason()).isEqualTo("DEFAULT"); + assertThat(evaluationDetails.getVariant()).isEqualTo("Passed in default"); + assertThat(evaluationDetails.getFlagKey()).isEqualTo(flagKey); + assertThat(evaluationDetails.getFlagMetadata()) + .isEqualTo(ImmutableMetadata.builder().build()); + assertThat(evaluationDetails.getValue()).isTrue(); + } + @Test void multi_hooks_early_out__before() { Hook hook = mockBooleanHook(); @@ -649,7 +720,7 @@ void mergeHappensCorrectly() { void first_finally_broken() { Hook hook = mockBooleanHook(); doThrow(RuntimeException.class).when(hook).before(any(), any()); - doThrow(RuntimeException.class).when(hook).finallyAfter(any(), any()); + doThrow(RuntimeException.class).when(hook).finallyAfter(any(), any(), any()); Hook hook2 = mockBooleanHook(); InOrder order = inOrder(hook, hook2); @@ -661,8 +732,8 @@ void first_finally_broken() { FlagEvaluationOptions.builder().hook(hook2).hook(hook).build()); order.verify(hook).before(any(), any()); - order.verify(hook2).finallyAfter(any(), any()); - order.verify(hook).finallyAfter(any(), any()); + order.verify(hook2).finallyAfter(any(), any(), any()); + order.verify(hook).finallyAfter(any(), any(), any()); } @Specification( @@ -711,7 +782,8 @@ void doesnt_use_finally() { .as("Not possible. Finally is a reserved word.") .isInstanceOf(NoSuchMethodException.class); - assertThatCode(() -> Hook.class.getMethod("finallyAfter", HookContext.class, Map.class)) + assertThatCode(() -> + Hook.class.getMethod("finallyAfter", HookContext.class, FlagEvaluationDetails.class, Map.class)) .doesNotThrowAnyException(); } } diff --git a/src/test/java/dev/openfeature/sdk/HookSupportTest.java b/src/test/java/dev/openfeature/sdk/HookSupportTest.java index 73256ab5..02a8ff90 100644 --- a/src/test/java/dev/openfeature/sdk/HookSupportTest.java +++ b/src/test/java/dev/openfeature/sdk/HookSupportTest.java @@ -64,7 +64,11 @@ void shouldAlwaysCallGenericHook(FlagValueType flagValueType) { Collections.singletonList(genericHook), Collections.emptyMap()); hookSupport.afterAllHooks( - flagValueType, hookContext, Collections.singletonList(genericHook), Collections.emptyMap()); + flagValueType, + hookContext, + FlagEvaluationDetails.builder().build(), + Collections.singletonList(genericHook), + Collections.emptyMap()); hookSupport.errorHooks( flagValueType, hookContext, @@ -74,7 +78,7 @@ void shouldAlwaysCallGenericHook(FlagValueType flagValueType) { verify(genericHook).before(any(), any()); verify(genericHook).after(any(), any(), any()); - verify(genericHook).finallyAfter(any(), any()); + verify(genericHook).finallyAfter(any(), any(), any()); verify(genericHook).error(any(), any(), any()); } diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java index 50b5254c..4f4d3200 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java @@ -5,13 +5,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import dev.openfeature.sdk.exceptions.FatalError; import dev.openfeature.sdk.fixtures.HookFixtures; import dev.openfeature.sdk.testutils.TestEventsProvider; import java.util.HashMap; -import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -104,59 +104,4 @@ void shouldNotCallEvaluationMethodsWhenProviderIsInNotReadyState() { assertThat(details.getErrorCode()).isEqualTo(ErrorCode.PROVIDER_NOT_READY); } - - private static class MockProvider implements FeatureProvider { - private final AtomicBoolean evaluationCalled = new AtomicBoolean(); - private final ProviderState providerState; - - public MockProvider(ProviderState providerState) { - this.providerState = providerState; - } - - public boolean isEvaluationCalled() { - return evaluationCalled.get(); - } - - @Override - public ProviderState getState() { - return providerState; - } - - @Override - public Metadata getMetadata() { - return null; - } - - @Override - public ProviderEvaluation getBooleanEvaluation( - String key, Boolean defaultValue, EvaluationContext ctx) { - evaluationCalled.set(true); - return null; - } - - @Override - public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { - evaluationCalled.set(true); - return null; - } - - @Override - public ProviderEvaluation getIntegerEvaluation( - String key, Integer defaultValue, EvaluationContext ctx) { - evaluationCalled.set(true); - return null; - } - - @Override - public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { - evaluationCalled.set(true); - return null; - } - - @Override - public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { - evaluationCalled.set(true); - return null; - } - } } From d825ff83639a2bd902bf0559209c2b80e17e0316 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:08:41 +0000 Subject: [PATCH 26/28] chore(deps): update actions/cache digest to 36f1e14 (#1274) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/merge.yml | 2 +- .github/workflows/pullrequest.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 050d4036..bcdd87a0 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -32,7 +32,7 @@ jobs: server-password: ${{ secrets.OSSRH_PASSWORD }} - name: Cache local Maven repository - uses: actions/cache@53aa38c736a561b9c17b62df3fe885a17b78ee6d + uses: actions/cache@36f1e144e1c8edb0a652766b484448563d8baf46 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 96e800be..352aca47 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -25,7 +25,7 @@ jobs: languages: java - name: Cache local Maven repository - uses: actions/cache@53aa38c736a561b9c17b62df3fe885a17b78ee6d + uses: actions/cache@36f1e144e1c8edb0a652766b484448563d8baf46 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} From 9c92ebb1bdb23c80461f143753f2fb42956462e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:17:41 +0000 Subject: [PATCH 27/28] chore(deps): update github/codeql-action digest to e83e0a4 (#1275) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pullrequest.yml | 4 ++-- .github/workflows/static-code-scanning.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 352aca47..f3a625b1 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,7 +20,7 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@fb65b6ce7884900fde5b15518bec92ad6875180e + uses: github/codeql-action/init@e83e0a4f58f2ca25f7dd222e8689519a74bf26fc with: languages: java @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fb65b6ce7884900fde5b15518bec92ad6875180e + uses: github/codeql-action/analyze@e83e0a4f58f2ca25f7dd222e8689519a74bf26fc diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index 24f3104b..40b0511a 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fb65b6ce7884900fde5b15518bec92ad6875180e + uses: github/codeql-action/init@e83e0a4f58f2ca25f7dd222e8689519a74bf26fc with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@fb65b6ce7884900fde5b15518bec92ad6875180e + uses: github/codeql-action/autobuild@e83e0a4f58f2ca25f7dd222e8689519a74bf26fc - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fb65b6ce7884900fde5b15518bec92ad6875180e + uses: github/codeql-action/analyze@e83e0a4f58f2ca25f7dd222e8689519a74bf26fc From 9274c117abdde734361231048e7f467c07e03da6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:10:55 -0500 Subject: [PATCH 28/28] chore(main): release 1.14.0 (#1242) * chore(main): release 1.14.0 * Update CHANGELOG.md Signed-off-by: Todd Baert --------- Signed-off-by: Todd Baert Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Todd Baert --- .release-please-manifest.json | 2 +- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++ README.md | 8 +++--- pom.xml | 2 +- version.txt | 2 +- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 12295c5d..e2d18dc1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"1.13.0"} \ No newline at end of file +{".":"1.14.0"} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d91800bc..3f7000d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## [1.14.0](https://github.com/open-feature/java-sdk/compare/v1.13.0...v1.14.0) (2025-01-10) + + +### โš  BREAKING CHANGES + +The signature of the `finallyAfter` hook stage has been changed. The signature now includes the `evaluation details`, as per the [OpenFeature specification](https://openfeature.dev/specification/sections/hooks#requirement-438). Note that since hooks are still `experimental,` this does not constitute a change requiring a new major version. To migrate, update any hook that implements the `finallyAfter` stage to accept `evaluation details` as the second argument. + +* Add evaluation details to finally hook stage [#1246](https://github.com/open-feature/java-sdk/issues/1246) ([#1262](https://github.com/open-feature/java-sdk/issues/1262)) ([ae85278](https://github.com/open-feature/java-sdk/commit/ae85278c30eb5279b80ea73ec6b92db040ad0bb7)) + + +### ๐Ÿ› Bug Fixes + +* **deps:** update junit5 monorepo ([#1251](https://github.com/open-feature/java-sdk/issues/1251)) ([834f720](https://github.com/open-feature/java-sdk/commit/834f72071806680353f42c750b04e36956736a9e)) + + +### โœจ New Features + +* Add evaluation details to finally hook stage [#1246](https://github.com/open-feature/java-sdk/issues/1246) ([#1262](https://github.com/open-feature/java-sdk/issues/1262)) ([ae85278](https://github.com/open-feature/java-sdk/commit/ae85278c30eb5279b80ea73ec6b92db040ad0bb7)) + + +### ๐Ÿงน Chore + +* **deps:** update actions/cache digest to 36f1e14 ([#1274](https://github.com/open-feature/java-sdk/issues/1274)) ([d825ff8](https://github.com/open-feature/java-sdk/commit/d825ff83639a2bd902bf0559209c2b80e17e0316)) +* **deps:** update actions/cache digest to 53aa38c ([#1270](https://github.com/open-feature/java-sdk/issues/1270)) ([a1c558f](https://github.com/open-feature/java-sdk/commit/a1c558f4ffb95772bd141ab7660e2c5b065482f1)) +* **deps:** update actions/setup-java digest to 7136edc ([#1244](https://github.com/open-feature/java-sdk/issues/1244)) ([9acc861](https://github.com/open-feature/java-sdk/commit/9acc8612a5fa7ea086da476195154a007cb55b7e)) +* **deps:** update actions/setup-java digest to 7a6d8a8 ([#1248](https://github.com/open-feature/java-sdk/issues/1248)) ([86e18c5](https://github.com/open-feature/java-sdk/commit/86e18c5d28a9f5fdd7234274720ba7ddcb529268)) +* **deps:** update codecov/codecov-action action to v5.1.2 ([#1255](https://github.com/open-feature/java-sdk/issues/1255)) ([d274cda](https://github.com/open-feature/java-sdk/commit/d274cdac3780286a0b45865864b12c3e4cff9f4b)) +* **deps:** update dependency com.google.guava:guava to v33.4.0-jre ([#1253](https://github.com/open-feature/java-sdk/issues/1253)) ([f39c4b5](https://github.com/open-feature/java-sdk/commit/f39c4b5af5e341bfec230d4cecd2037fc5430400)) +* **deps:** update dependency net.bytebuddy:byte-buddy to v1.15.11 ([#1249](https://github.com/open-feature/java-sdk/issues/1249)) ([4440cda](https://github.com/open-feature/java-sdk/commit/4440cda6a5b42a903ba11835a975bf6247de845f)) +* **deps:** update dependency net.bytebuddy:byte-buddy-agent to v1.15.11 ([#1250](https://github.com/open-feature/java-sdk/issues/1250)) ([6772d3f](https://github.com/open-feature/java-sdk/commit/6772d3f3943fb3b7f7522c80b732aa058fd03bb9)) +* **deps:** update dependency org.assertj:assertj-core to v3.27.0 ([#1258](https://github.com/open-feature/java-sdk/issues/1258)) ([c62ade3](https://github.com/open-feature/java-sdk/commit/c62ade3878dabf9194536d551f3316ba5c0ce5e1)) +* **deps:** update dependency org.assertj:assertj-core to v3.27.1 ([#1266](https://github.com/open-feature/java-sdk/issues/1266)) ([20bbb23](https://github.com/open-feature/java-sdk/commit/20bbb2337cb5afbee9b8d5143b45416673cb4154)) +* **deps:** update dependency org.assertj:assertj-core to v3.27.2 ([#1268](https://github.com/open-feature/java-sdk/issues/1268)) ([2e10d34](https://github.com/open-feature/java-sdk/commit/2e10d34920f57d863c09ce1522c9ccff20413f74)) +* **deps:** update github/codeql-action digest to 3407610 ([#1269](https://github.com/open-feature/java-sdk/issues/1269)) ([4086dea](https://github.com/open-feature/java-sdk/commit/4086dea703a950dcacc792be9a9346cc1fa8409d)) +* **deps:** update github/codeql-action digest to 4d64ab6 ([#1243](https://github.com/open-feature/java-sdk/issues/1243)) ([884f8fb](https://github.com/open-feature/java-sdk/commit/884f8fbf77c41e070526da0f73e136d4c3e41a4d)) +* **deps:** update github/codeql-action digest to 562042d ([#1254](https://github.com/open-feature/java-sdk/issues/1254)) ([6a79874](https://github.com/open-feature/java-sdk/commit/6a7987455ef7e46d40b835c7d8dbda29322e3b2d)) +* **deps:** update github/codeql-action digest to 5b6e617 ([#1263](https://github.com/open-feature/java-sdk/issues/1263)) ([f1817d8](https://github.com/open-feature/java-sdk/commit/f1817d8fef585f957de1cfb9222b03cb591ed2e9)) +* **deps:** update github/codeql-action digest to 64cc90b ([#1256](https://github.com/open-feature/java-sdk/issues/1256)) ([992c003](https://github.com/open-feature/java-sdk/commit/992c00396cb2fca6a6a7dc63d727b063a79386b6)) +* **deps:** update github/codeql-action digest to 7876007 ([#1260](https://github.com/open-feature/java-sdk/issues/1260)) ([fc6f35e](https://github.com/open-feature/java-sdk/commit/fc6f35e581cacb0ad149c58a5943ec1429ce25ca)) +* **deps:** update github/codeql-action digest to 78d0136 ([#1245](https://github.com/open-feature/java-sdk/issues/1245)) ([fd1c170](https://github.com/open-feature/java-sdk/commit/fd1c1702c6d4067c432c1522143266ddf470d18d)) +* **deps:** update github/codeql-action digest to 8975792 ([#1241](https://github.com/open-feature/java-sdk/issues/1241)) ([b0abfd0](https://github.com/open-feature/java-sdk/commit/b0abfd02cf9e97f7409df3296818ac990b429058)) +* **deps:** update github/codeql-action digest to 9d59969 ([#1252](https://github.com/open-feature/java-sdk/issues/1252)) ([482a5ae](https://github.com/open-feature/java-sdk/commit/482a5aef1005b2ebe2fdb9ee43243b6c2aeeadc8)) +* **deps:** update github/codeql-action digest to d01b25e ([#1257](https://github.com/open-feature/java-sdk/issues/1257)) ([6d60c96](https://github.com/open-feature/java-sdk/commit/6d60c962fbac48a13d86271b361fb0cfd91a5342)) +* **deps:** update github/codeql-action digest to dd75594 ([#1247](https://github.com/open-feature/java-sdk/issues/1247)) ([6d169f5](https://github.com/open-feature/java-sdk/commit/6d169f55e235a071033a9bf1138484f09a5e472d)) +* **deps:** update github/codeql-action digest to e83e0a4 ([#1275](https://github.com/open-feature/java-sdk/issues/1275)) ([9c92ebb](https://github.com/open-feature/java-sdk/commit/9c92ebb1bdb23c80461f143753f2fb42956462e3)) +* **deps:** update github/codeql-action digest to fb65b6c ([#1273](https://github.com/open-feature/java-sdk/issues/1273)) ([3c97b7b](https://github.com/open-feature/java-sdk/commit/3c97b7baaf9eee719479c059cb923d8d64f2c25f)) + ## [1.13.0](https://github.com/open-feature/java-sdk/compare/v1.12.2...v1.13.0) (2024-12-07) diff --git a/README.md b/README.md index 44f4c81c..cbb9d9f1 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ - - Release + + Release @@ -59,7 +59,7 @@ Note that this library is intended to be used in server-side contexts and has no dev.openfeature sdk - 1.13.0 + 1.14.0 ``` @@ -84,7 +84,7 @@ If you would like snapshot builds, this is the relevant repository information: ```groovy dependencies { - implementation 'dev.openfeature:sdk:1.13.0' + implementation 'dev.openfeature:sdk:1.14.0' } ``` diff --git a/pom.xml b/pom.xml index fb441318..5d8ea34c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ dev.openfeature sdk - 1.13.0 + 1.14.0 UTF-8 diff --git a/version.txt b/version.txt index feaae22b..850e7424 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.13.0 +1.14.0