Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ components:
- toddbaert
tools/flagd-http-connector:
- liran2000
tools/provider-tck:
- aepfli

ignored-authors:
- renovate-bot
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"tools/flagd-http-connector": "0.0.5",
"tools/flagd-api": "1.0.0",
"tools/flagd-api-testkit": "0.2.1",
"tools/provider-tck": "0.0.1",
"tools/flagd-core": "2.0.1",
".": "1.0.0",
"providers/optimizely": "1.0.0"
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</licenses>

<modules>
<module>tools/provider-tck</module>
<module>tools/flagd-api-testkit</module>
<module>tools/flagd-api</module>
<module>tools/flagd-core</module>
Expand Down
13 changes: 13 additions & 0 deletions providers/flagd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<com.vmlens.version>1.2.28</com.vmlens.version>
<!-- Transitive flagd-core version -->
<flagd-core.version>[2.0.0,3.0.0)</flagd-core.version>
<!-- Match any provider-tck version locally; CI resolves it from the reactor -->
<provider-tck.version>[0.0.1,)</provider-tck.version>
</properties>

<name>flagd</name>
Expand Down Expand Up @@ -98,6 +100,17 @@
<version>5.14.3</version>
<scope>test</scope>
</dependency>
<!--
OpenFeature Provider TCK. Brings its own Gherkin, step definitions and Compose
lifecycle; FlagdTckTest is the whole adoption. Version range so a local reactor
build matches whatever is checked out.
-->
<dependency>
<groupId>dev.openfeature.contrib.tools</groupId>
<artifactId>provider-tck</artifactId>
<version>${provider-tck.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package dev.openfeature.contrib.providers.flagd.e2e;

import dev.openfeature.contrib.providers.flagd.Config;
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
import dev.openfeature.contrib.tools.providertck.AbstractProviderTckTest;
import dev.openfeature.contrib.tools.providertck.BackendEndpoint;
import dev.openfeature.contrib.tools.providertck.Capability;
import dev.openfeature.sdk.FeatureProvider;
import java.io.File;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

/**
* Shared configuration for running the OpenFeature Provider TCK against the flagd provider.
*
* <p>flagd resolves flags in two quite different ways, and both are worth conforming: RPC evaluates
* remotely over gRPC, while in-process syncs the ruleset and evaluates locally. They share a backend
* stack and differ only in resolver and port, so the modes are two small subclasses.
*
* <p>Each concrete subclass is its own JUnit suite and its own TCK harness; the TCK works out which
* one is running from the JUnit test plan, so adding a mode needs no registration or build
* configuration.
*/
abstract class AbstractFlagdTckTest extends AbstractProviderTckTest {

/**
* A port nothing listens on, for the initialisation-failure scenarios.
*
* <p>Deliberately not a port on the Compose stack: the stack must stay up for the whole suite,
* and simulated outages belong to the control API.
*/
private static final int UNAVAILABLE_PORT = 9999;

/**
* gRPC deadline for a provider that is expected to connect.
*
* <p>Generous on purpose. flagd derives its initialisation deadline from this value, and the
* in-process resolver must sync the entire ruleset before it reports ready — which intermittently
* takes longer than a deadline tuned for a single RPC round trip.
*/
private static final int CONNECTED_DEADLINE_MS = 5000;

/**
* gRPC deadline for a provider pointed at a dead port.
*
* <p>Short on purpose, and deliberately not the same as {@link #CONNECTED_DEADLINE_MS}: the
* initialisation-failure scenarios assert that the failure is reported <em>promptly</em>, so a
* provider that takes as long to give up as it does to connect would defeat the point.
*/
private static final int UNAVAILABLE_DEADLINE_MS = 1000;

/** The resolver under test. */
protected abstract Config.Resolver resolver();

/** The container-internal port that resolver connects to. */
protected abstract int backendPort();

@Override
public File composeFile() {
return new File("src/test/resources/tck/docker-compose.yaml");
}

@Override
public List<Integer> backendPorts() {
return Collections.singletonList(backendPort());
}

@Override
public FeatureProvider createProvider(BackendEndpoint endpoint) {
return new FlagdProvider(baseOptions()
.deadline(CONNECTED_DEADLINE_MS)
.host(endpoint.host())
.port(endpoint.port(backendPort()))
.build());
}

@Override
public FeatureProvider createUnavailableProvider() {
return new FlagdProvider(baseOptions()
.deadline(UNAVAILABLE_DEADLINE_MS)
.host("localhost")
.port(UNAVAILABLE_PORT)
.build());
}

/**
* {@inheritDoc}
*
* <p>Everything except {@link Capability#STRICT_NUMERIC_TYPING}. Evaluating {@code float-flag}
* (0.5) through the integer API returns {@code 0} with <em>no</em> error code rather than
* {@code TYPE_MISMATCH} with the code default — the value is silently truncated. That is a
* defect to fix, not a design choice; this override should be deleted once it is.
*
* <p>Declared here rather than per mode because both resolvers behave identically, which places
* the defect in the shared provider layer rather than in either transport. Every other
* capability, including the full non-numeric type-mismatch matrix, holds in both modes.
*
* <p>That includes {@link Capability#LIFECYCLE}, and legitimately so: flagd reaches its backend
* during initialisation in both modes — an RPC round trip, or a full ruleset sync — so the
* lifecycle scenarios assert something real here rather than passing vacuously.
*/
@Override
public Set<Capability> capabilities() {
return EnumSet.complementOf(EnumSet.of(Capability.STRICT_NUMERIC_TYPING));
}

private FlagdOptions.FlagdOptionsBuilder baseOptions() {
return FlagdOptions.builder()
.resolverType(resolver())
.retryGracePeriod(2)
.retryBackoffMs(500);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.openfeature.contrib.providers.flagd.e2e;

import dev.openfeature.contrib.providers.flagd.Config;

/** Runs the OpenFeature Provider TCK against the flagd provider in in-process mode. */
public class FlagdInProcessTckTest extends AbstractFlagdTckTest {

@Override
protected Config.Resolver resolver() {
return Config.Resolver.IN_PROCESS;
}

@Override
protected int backendPort() {
return 8015;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.openfeature.contrib.providers.flagd.e2e;

import dev.openfeature.contrib.providers.flagd.Config;

/** Runs the OpenFeature Provider TCK against the flagd provider in RPC mode. */
public class FlagdRpcTckTest extends AbstractFlagdTckTest {

@Override
protected Config.Resolver resolver() {
return Config.Resolver.RPC;
}

@Override
protected int backendPort() {
return 8013;
}
}
15 changes: 15 additions & 0 deletions providers/flagd/src/test/resources/tck/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Backend stack for the OpenFeature Provider TCK, wrapping the unmodified flagd testbed image.
#
# The image already serves everything the TCK needs: flagd itself, and the "launchpad" control
# API on 8080 whose endpoints this TCK's control API contract was derived from.
#
# Note there are no host port bindings. The TCK requires dynamically mapped ports and discovers
# them after startup — a pinned host port would make the suite unrunnable in parallel and would
# collide with a developer's local flagd.
services:
backend:
image: ghcr.io/open-feature/flagd-testbed:v3.8.0
ports:
- 8013 # flagd RPC evaluation (gRPC)
- 8015 # flagd in-process sync (gRPC)
- 8080 # launchpad control API
11 changes: 11 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,17 @@
"README.md"
]
},
"tools/provider-tck": {
"package-name": "dev.openfeature.contrib.tools.providertck",
"release-type": "simple",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"versioning": "default",
"extra-files": [
"pom.xml",
"README.md"
]
},
"tools/flagd-api-testkit": {
"package-name": "dev.openfeature.contrib.tools.flagdapitestkit",
"release-type": "simple",
Expand Down
Loading
Loading