Skip to content

test(ofrep): run the provider conformance suite against OFREP - #1840

Draft
aepfli wants to merge 1 commit into
feat/provider-tckfrom
feat/provider-tck-ofrep
Draft

test(ofrep): run the provider conformance suite against OFREP#1840
aepfli wants to merge 1 commit into
feat/provider-tckfrom
feat/provider-tck-ofrep

Conversation

@aepfli

@aepfli aepfli commented Aug 24, 2026

Copy link
Copy Markdown
Member

Stacked on #1830. Base is feat/provider-tck, so this diff shows only the new work. Review #1830 first.

Part of #1829 and open-feature/spec#417.

What

The OFREP provider under the conformance suite. Three files, 159 insertions:

  • providers/ofrep/src/test/java/.../e2e/OfrepTckTest.java — the whole adoption, one class, no test infrastructure
  • providers/ofrep/src/test/resources/tck/docker-compose.yaml — the same flagd-testbed image the flagd suites use, exposing 8016 (OFREP) and 8080 (launchpad), no pinned host ports
  • providers/ofrep/pom.xml — the provider-tck test dependency, copied verbatim from the flagd module

No new infrastructure, and no surefire executions were needed: *TckTest matches surefire's default includes, and provider-tck brings Testcontainers, Cucumber and the JUnit platform launcher transitively.

Capabilities: OBJECT and STRICT_NUMERIC_TYPING — 24 of 29 scenarios run

Every omission traces to one fact: OfrepProvider has no lifecycle of its own.

  • EVENTSOfrepProvider.java:19 is implements FeatureProvider, not extends EventProvider. No ProviderEvent reference, no emit* call anywhere in the file.
  • STALE — nothing survives an evaluation. Resolver.resolve builds its result purely from the current response and records nothing on failure (Resolver.java:93-96, OfrepApi.java:114-115).
  • CONFIGURATION_CHANGE — the only outbound call is the per-evaluation POST /ofrep/v1/evaluate/flags/{key} (OfrepApi.java:27,93-109). No bulk endpoint, no ETag, no watch.
  • UNAVAILABLE_INITinitialize is not overridden, so the interface default runs and cannot fail. constructProvider validates arguments and never touches the network (OfrepProvider.java:38-68). A provider aimed at a dead port reaches READY.

One judgement call worth flagging. I verified against sdk-1.22.0.jar that FeatureProviderStateManager emits PROVIDER_READY/PROVIDER_ERROR around initialize for any provider, independent of EventProvider. So lifecycle.feature's readiness scenario would pass if EVENTS were declared, and the other @events scenarios carry finer tags that would still gate them — declaring it is mechanically possible and would give 25 running instead of 24. I withheld it anyway: that READY is synthesised by the SDK and would appear identically for NoOpProvider, so declaring EVENTS would assert a PROVIDER_ERROR capability the provider can never demonstrate. This is a decision, not a forced outcome, and I would like it challenged.

STRICT_NUMERIC_TYPING is declared — the OFREP provider does not share flagd's defect. Different code path, and it holds up: OfrepResponse.java:16 is an untyped Object value filled by a plain Jackson ObjectMapper, so a JSON fraction arrives as Double and a JSON integer as Integer. handleResolved admits the value only on an exact type.isInstance(...) check and otherwise returns TYPE_MISMATCH with the code default (Resolver.java:183-190). Nothing widens or narrows, so float-flag (0.5) requested as an integer is rejected rather than truncated to 0.

@events is a feature-level tag on both events.feature and lifecycle.feature, so all 5 skip. What runs is the full evaluation and error-code matrix: 7 from evaluation.feature, 17 from errors.feature.

Verification — this one was actually built

Neither mvn nor java was on PATH, but IntelliJ's bundled JBR 21 and the repo's cached mvnw were, so:

Check Result
mvnw --projects tools/provider-tck,providers/ofrep --also-make test-compile BUILD SUCCESS
mvnw --projects tools/provider-tck,providers/ofrep test existing 18 OFREP unit tests pass; OfrepTckTest is discovered by surefire, the Cucumber engine loads it as a @Suite, harness discovery resolves, and it fails at exactly one point — IllegalStateException: Could not find a valid Docker environment
spotless:check one violation, fixed with spotless:apply, re-verified clean
Recompile under -Pcodequality BUILD SUCCESS
The 24 scenarios actually passing unverified — Docker is absent, so no scenario has ever executed

The wiring is therefore proven end to end up to the container boundary. The 24/5 split is arithmetic from the feature files and the capability gate, not an observed run.

No CI will run on this PR while it is stacked: .github/workflows/ci.yml triggers on pull_request: branches: [main] and this targets feat/provider-tck.

Worth filing against the Java OFREP provider

  1. ParseError escapes the provider's own error mapping. Resolver.resolve:93 catches only GeneralError, but OfrepApi throws ParseError on JsonProcessingException (OfrepApi.java:111-112), and the two are siblings under OpenFeatureError. A malformed or empty response body propagates out unmapped. The SDK's blanket catch means an application still gets details rather than a throw, so the TCK will not catch this — but the provider reports PARSE_ERROR where its own design intends GENERAL.
  2. No initialize() means a misconfigured provider reports READY. Point it at a bogus baseUrl and every evaluation silently returns the code default with GENERAL, while the client status says everything is fine. This is the single biggest conformance gap and the reason UNAVAILABLE_INIT is withheld; one round trip in initialize() would fix it and unlock both @unavailable scenarios.
  3. Not an EventProvider. OFREP's bulk-evaluation endpoint with ETag support is designed for exactly this; polling it would unlock EVENTS, STALE and CONFIGURATION_CHANGE and take the suite from 24 to 29.
  4. Rate-limit backoff is provider-global and cross-flag. A single 429 on one flag sets nextAllowedRequestTime (OfrepApi.java:127-131), after which every flag's evaluation throws GeneralError until the deadline — including flags that were never rate-limited.
  5. Latent, not currently reachable: OfrepResponse.getMetadata() calls ImmutableMap.copyOf(metadata) on a field that stays null when the JSON omits metadata. Every live path launders the object through Resolution, whose constructor reads the field directly, so it is safe today — but a direct use of a deserialised OfrepResponse would NPE.

OFREP is a protocol, not a vendor, so the suite needs no new infrastructure:
flagd already serves the OFREP HTTP API on 8016 inside the flagd-testbed image
that the flagd TCK suites use, alongside the launchpad control API on 8080. The
Compose stack is therefore the same image with a different port exposed, and the
whole adoption is one test class plus one dependency.

Four capabilities are withheld, all traceable to the same fact: OfrepProvider
implements FeatureProvider rather than extending EventProvider and overrides no
lifecycle method, so it has no state, no stream, no poll loop and no
initialize(). It cannot emit events (EVENTS), cannot observe the backend going
away (STALE) or changing (CONFIGURATION_CHANGE), and cannot fail initialisation
against a dead port (UNAVAILABLE_INIT). Each omission is justified against
specific lines of the provider in the capabilities() javadoc. events.feature and
lifecycle.feature are both tagged @events at feature level, so 5 scenarios are
reported as skipped and 24 run.

OBJECT and STRICT_NUMERIC_TYPING are both declared. Unlike the flagd provider,
OFREP does not silently narrow a float to an integer: values are deserialised by
a plain Jackson ObjectMapper into an untyped Object, so a JSON fraction arrives
as Double and a JSON integer as Integer, and handleResolved admits a value only
on an exact type.isInstance check. float-flag requested as an integer is
reported as TYPE_MISMATCH with the code default rather than truncated to 0.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aepfli aepfli changed the title feat(provider-tck): adopt the conformance suite in the OFREP provider test(ofrep): run the provider conformance suite against OFREP Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant