Conversation
8eb6720 to
db7548c
Compare
| ); | ||
|
|
||
| assertThat(result.getValue()) | ||
| .as("[%s] %s → value", fileName, description) |
There was a problem hiding this comment.
Not 100% sure on the necessity of this formatting.
| public String errorCode; | ||
| } | ||
|
|
||
| static class RawJsonDeserializer extends JsonDeserializer<String> { |
There was a problem hiding this comment.
Jackson does not have the equivalent of GetRawText. I could have fetched a generic JSON object and then reserialised it, but I'd prefer to get the raw text from the file to avoid unintentional changes to the "response".
|
|
||
| static Stream<Arguments> fixtureTestCases() throws IOException { | ||
| ObjectMapper mapper = JsonMapper.builder() | ||
| .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) |
There was a problem hiding this comment.
Required for the RawJsonDeserializer below.
e1c6d2f to
91bab70
Compare
There was a problem hiding this comment.
Pull request overview
Adds a shared, submodule-backed specification test harness to validate OpenFeature provider behavior against JSON fixtures.
Changes:
- Introduces parameterized
SpecificationTeststhat discover and run JSON fixture cases against a WireMock-backed fake service. - Adds a
Serverhelper wrapping WireMock for per-test-case Bearer-token-based stubbing. - Updates configuration/CI to support the new test approach (package-private server URI override, submodule checkout).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/java/com/octopus/openfeature/provider/SpecificationTests.java | New parameterized spec runner that loads fixture JSON and asserts evaluation results. |
| src/test/java/com/octopus/openfeature/provider/Server.java | WireMock wrapper to serve fixture responses keyed by unique tokens. |
| src/test/java/com/octopus/openfeature/provider/OctopusConfigurationTests.java | Tests for default server URI and overriding server URI. |
| src/main/java/com/octopus/openfeature/provider/OctopusConfiguration.java | Makes server URI configurable (package-private setter) for tests. |
| specification | Adds the specification repo as a git submodule pointer. |
| pom.xml | Adds WireMock test dependency and scopes JUnit to tests. |
| .github/workflows/ci.yml | Ensures submodules are checked out in CI and updates workflow naming/steps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return jsonFiles.stream().flatMap(path -> { | ||
| try { | ||
| String fileContent = Files.readString(path); | ||
| Fixture fixture = mapper.readValue(fileContent, Fixture.class); | ||
| String fileName = path.getFileName().toString(); | ||
| return Stream.of(fixture.cases) | ||
| .map(c -> Arguments.of(fileName, c.description, fixture.response, c)); | ||
| } catch (IOException e) { |
There was a problem hiding this comment.
Stream.of(fixture.cases) creates a Stream<FixtureCase[]> (single element: the array), so c.description won’t compile/work. Use an element stream (e.g., Arrays.stream(fixture.cases)) so each FixtureCase becomes a separate Arguments row.
| static class RawJsonDeserializer extends JsonDeserializer<String> { | ||
| @Override | ||
| public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException { | ||
| long begin = jp.currentLocation().getCharOffset(); | ||
| jp.skipChildren(); | ||
| long end = jp.currentLocation().getCharOffset(); | ||
| String json = jp.currentLocation().contentReference().getRawContent().toString(); | ||
| return json.substring((int) begin - 1, (int) end); | ||
| } | ||
| } |
There was a problem hiding this comment.
This “slice raw source by char offsets” approach is fragile (offset semantics vary by parser/source, begin - 1 can underflow, getRawContent().toString() is not guaranteed to be the actual JSON text, and (int) casts can truncate). A more robust approach is to deserialize response as a JsonNode (or Object) and then serialize it when configuring the server (e.g., mapper.writeValueAsString(node)), avoiding any dependency on raw input offsets.
| class SpecificationTests { | ||
|
|
||
| private static Server server; | ||
|
|
||
| @BeforeAll | ||
| static void startServer() { | ||
| server = new Server(); | ||
| } | ||
|
|
||
| @AfterAll | ||
| static void stopServer() { | ||
| server.stop(); | ||
| } | ||
|
|
||
| @AfterEach | ||
| void shutdownApi() { | ||
| OpenFeatureAPI.getInstance().shutdown(); | ||
| } |
There was a problem hiding this comment.
These tests share global mutable state (OpenFeatureAPI singleton and a static Server with accumulating stubs). If JUnit parallel execution is enabled (locally or in CI), this can cause cross-test interference/flakiness. Consider explicitly forcing same-thread execution for this class (e.g., JUnit 5 @Execution(ExecutionMode.SAME_THREAD)), or otherwise applying a lock/serialization mechanism around these tests.
Background
We are adding shared specification tests across our three OpenFeature provider libraries as per this ADR.
The one existing test fixture is imported from our specifications repository as a Git submodule. Our intention is to add a separate fixture file for each area of functionality.
Each test fixture set will contain a list of toggles and test cases with expected outcomes.
Changes
OctopusConfigurationto have a package-private settable server URI.Server, a wrapper around a WireMock server allowing registration of evaluation payloads for particular client identifiers.SpecificationTestswhich uses@ParameterizedTestand@MethodSourceto run a test for each case in each specification JSON file using the WireMock server as the service for the OpenFeature client to query.Result