Skip to content

Commit 578f8ee

Browse files
committed
feat: e2e test for verification
1 parent 8d6bcaa commit 578f8ee

6 files changed

Lines changed: 685 additions & 18 deletions

File tree

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,10 @@ tasks.register('test-integration') {
173173
tasks.named('check').configure {
174174
dependsOn 'spotlessCheck'
175175
}
176+
177+
// Temporary task for running the E2E streaming auth test against Docker containers.
178+
// Usage: ./gradlew e2eStreamingAuth
179+
tasks.register('e2eStreamingAuth', JavaExec) {
180+
classpath = sourceSets.test.runtimeClasspath
181+
mainClass = 'dev.openfga.sdk.e2e.E2EStreamingAuthTest'
182+
}

e2e-test/E2EStreamingAuthTest.java

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package dev.openfga.sdk.e2e;
2+
3+
import dev.openfga.sdk.api.client.OpenFgaClient;
4+
import dev.openfga.sdk.api.client.model.*;
5+
import dev.openfga.sdk.api.configuration.*;
6+
import dev.openfga.sdk.api.model.*;
7+
import dev.openfga.sdk.api.model.CreateStoreRequest;
8+
import dev.openfga.sdk.api.model.WriteAuthorizationModelRequest;
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
/**
15+
* End-to-end verification against real Docker containers.
16+
* NOT a JUnit test — run manually after docker-compose up.
17+
*
18+
* Tests:
19+
* 1. API_TOKEN auth → preshared-key OpenFGA on port 8082
20+
* 2. CLIENT_CREDENTIALS auth → OIDC OpenFGA on port 8080 + mock OAuth2 on port 9090
21+
*
22+
* For each auth method, exercises:
23+
* - CreateStore
24+
* - WriteAuthorizationModel
25+
* - Write (tuples)
26+
* - Check (non-streaming)
27+
* - ListObjects (non-streaming)
28+
* - StreamedListObjects (streaming) ← the bug from #330
29+
*/
30+
public class E2EStreamingAuthTest {
31+
32+
// The mock-oauth2-server token endpoint URL.
33+
// Both the host SDK client and the openfga container reach mock-oauth2 via
34+
// localhost:9090 (the container uses extra_hosts: localhost → host-gateway).
35+
// This ensures the JWT "iss" claim matches OpenFGA's configured OIDC issuer.
36+
private static final String OAUTH2_TOKEN_ENDPOINT = "http://localhost:9090/default/token";
37+
private static final String OIDC_AUDIENCE = "openfga";
38+
39+
private static int passed = 0;
40+
private static int failed = 0;
41+
42+
public static void main(String[] args) throws Exception {
43+
System.out.println("╔══════════════════════════════════════════════════════════╗");
44+
System.out.println("║ E2E Streaming Auth Test — openfga/java-sdk#330 fix ║");
45+
System.out.println("╚══════════════════════════════════════════════════════════╝\n");
46+
47+
testApiTokenAuth();
48+
System.out.println();
49+
testClientCredentialsAuth();
50+
51+
System.out.println("\n════════════════════════════════════════════════════════════");
52+
System.out.printf(" Results: %d passed, %d failed%n", passed, failed);
53+
System.out.println("════════════════════════════════════════════════════════════");
54+
55+
if (failed > 0) {
56+
System.exit(1);
57+
}
58+
}
59+
60+
// -----------------------------------------------------------------------
61+
// Test 1: API_TOKEN (preshared key)
62+
// -----------------------------------------------------------------------
63+
private static void testApiTokenAuth() throws Exception {
64+
System.out.println("─── Test Suite: API_TOKEN (preshared key on port 8082) ───");
65+
66+
ClientConfiguration config = new ClientConfiguration()
67+
.apiUrl("http://localhost:8082")
68+
.credentials(new Credentials(new ApiToken("test-api-key-123")));
69+
70+
OpenFgaClient client = new OpenFgaClient(config);
71+
72+
// Create store
73+
var store = client.createStore(new CreateStoreRequest().name("e2e-apitoken-test")).get();
74+
String storeId = store.getId();
75+
check("createStore", storeId != null && !storeId.isEmpty());
76+
System.out.println(" Store ID: " + storeId);
77+
78+
config.storeId(storeId);
79+
client = new OpenFgaClient(config);
80+
81+
// Write auth model
82+
var model = writeModel(client);
83+
check("writeAuthModel", model != null);
84+
85+
// Write tuples
86+
writeTuples(client);
87+
check("writeTuples", true);
88+
89+
// Non-streaming check
90+
var checkResult = client.check(new ClientCheckRequest()
91+
.user("user:anne").relation("reader")._object("document:readme"))
92+
.get();
93+
check("check (non-streaming)", checkResult.getAllowed());
94+
95+
// Non-streaming listObjects
96+
var listResult = client.listObjects(new ClientListObjectsRequest()
97+
.user("user:anne").relation("reader").type("document"))
98+
.get();
99+
check("listObjects (non-streaming)", listResult.getObjects().contains("document:readme"));
100+
101+
// ★ STREAMING listObjects — this is the #330 regression
102+
List<StreamedListObjectsResponse> streamResults = new ArrayList<>();
103+
client.streamedListObjects(
104+
new ClientListObjectsRequest().user("user:anne").relation("reader").type("document"),
105+
streamResults::add)
106+
.get();
107+
boolean streamOk = streamResults.stream().anyMatch(r -> "document:readme".equals(r.getObject()));
108+
check("streamedListObjects (streaming) ★ #330 fix", streamOk);
109+
110+
System.out.println(" Streamed objects: "
111+
+ streamResults.stream().map(StreamedListObjectsResponse::getObject).toList());
112+
}
113+
114+
// -----------------------------------------------------------------------
115+
// Test 2: CLIENT_CREDENTIALS (OAuth2 OIDC)
116+
// -----------------------------------------------------------------------
117+
private static void testClientCredentialsAuth() throws Exception {
118+
System.out.println("─── Test Suite: CLIENT_CREDENTIALS (OIDC on port 8080) ───");
119+
120+
// The mock-oauth2-server accepts any client_id/secret for client_credentials grant.
121+
ClientConfiguration config = new ClientConfiguration()
122+
.apiUrl("http://localhost:8080")
123+
.credentials(new Credentials(new ClientCredentials()
124+
.clientId("e2e-test-client")
125+
.clientSecret("e2e-test-secret")
126+
.apiAudience(OIDC_AUDIENCE)
127+
.apiTokenIssuer(OAUTH2_TOKEN_ENDPOINT)));
128+
129+
OpenFgaClient client = new OpenFgaClient(config);
130+
131+
// Create store
132+
var store = client.createStore(new CreateStoreRequest().name("e2e-oidc-test")).get();
133+
String storeId = store.getId();
134+
check("createStore", storeId != null && !storeId.isEmpty());
135+
System.out.println(" Store ID: " + storeId);
136+
137+
config.storeId(storeId);
138+
client = new OpenFgaClient(config);
139+
140+
// Write auth model
141+
var model = writeModel(client);
142+
check("writeAuthModel", model != null);
143+
144+
// Write tuples
145+
writeTuples(client);
146+
check("writeTuples", true);
147+
148+
// Non-streaming check
149+
var checkResult = client.check(new ClientCheckRequest()
150+
.user("user:anne").relation("reader")._object("document:readme"))
151+
.get();
152+
check("check (non-streaming)", checkResult.getAllowed());
153+
154+
// Non-streaming listObjects
155+
var listResult = client.listObjects(new ClientListObjectsRequest()
156+
.user("user:anne").relation("reader").type("document"))
157+
.get();
158+
check("listObjects (non-streaming)", listResult.getObjects().contains("document:readme"));
159+
160+
// ★ STREAMING listObjects — the bug from #330
161+
List<StreamedListObjectsResponse> streamResults = new ArrayList<>();
162+
client.streamedListObjects(
163+
new ClientListObjectsRequest().user("user:anne").relation("reader").type("document"),
164+
streamResults::add)
165+
.get();
166+
boolean streamOk = streamResults.stream().anyMatch(r -> "document:readme".equals(r.getObject()));
167+
check("streamedListObjects (streaming) ★ #330 fix", streamOk);
168+
169+
System.out.println(" Streamed objects: "
170+
+ streamResults.stream().map(StreamedListObjectsResponse::getObject).toList());
171+
}
172+
173+
// -----------------------------------------------------------------------
174+
// Helpers
175+
// -----------------------------------------------------------------------
176+
177+
private static String writeModel(OpenFgaClient client) throws Exception {
178+
var response = client.writeAuthorizationModel(new WriteAuthorizationModelRequest()
179+
.schemaVersion("1.1")
180+
.typeDefinitions(List.of(
181+
new TypeDefinition().type("user"),
182+
new TypeDefinition().type("document").relations(Map.of(
183+
"reader", new Userset()._this(new HashMap<>()),
184+
"writer", new Userset()._this(new HashMap<>())
185+
)).metadata(new Metadata().relations(Map.of(
186+
"reader", new RelationMetadata().directlyRelatedUserTypes(
187+
List.of(new RelationReference().type("user"))),
188+
"writer", new RelationMetadata().directlyRelatedUserTypes(
189+
List.of(new RelationReference().type("user")))
190+
))))))
191+
.get();
192+
String modelId = response.getAuthorizationModelId();
193+
System.out.println(" Auth Model ID: " + modelId);
194+
return modelId;
195+
}
196+
197+
private static void writeTuples(OpenFgaClient client) throws Exception {
198+
client.write(new ClientWriteRequest()
199+
.writes(List.of(
200+
new ClientTupleKey()
201+
.user("user:anne")
202+
.relation("reader")
203+
._object("document:readme"),
204+
new ClientTupleKey()
205+
.user("user:anne")
206+
.relation("reader")
207+
._object("document:changelog"),
208+
new ClientTupleKey()
209+
.user("user:anne")
210+
.relation("writer")
211+
._object("document:readme")
212+
)), new ClientWriteOptions())
213+
.get();
214+
System.out.println(" Wrote 3 tuples");
215+
}
216+
217+
private static void check(String name, boolean ok) {
218+
if (ok) {
219+
passed++;
220+
System.out.printf(" ✅ %s%n", name);
221+
} else {
222+
failed++;
223+
System.out.printf(" ❌ %s%n", name);
224+
}
225+
}
226+
}
227+
228+

e2e-test/Makefile

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
.PHONY: help up down restart build run test clean logs status
2+
3+
# SDK jar — resolved dynamically from the parent build output
4+
SDK_JAR := $(wildcard ../build/libs/openfga-sdk-*.jar)
5+
BUILD_DIR := /tmp/e2e-build
6+
GRADLE_CACHE := $(HOME)/.gradle/caches/modules-2/files-2.1
7+
8+
# Dependency versions (must match ../build.gradle)
9+
JACKSON_VER := 2.21.2
10+
JACKSON_ANN_VER := 2.21
11+
NULLABLE_VER := 0.2.10
12+
JSR305_VER := 3.0.2
13+
OTEL_VER := 1.61.0
14+
15+
# Resolve jars from Gradle cache
16+
DEP_JARS := $(shell find $(GRADLE_CACHE) -name "*.jar" 2>/dev/null | \
17+
grep -E "jackson-core-$(JACKSON_VER)|jackson-databind-$(JACKSON_VER)|jackson-annotations-$(JACKSON_ANN_VER)\b|jackson-datatype-jsr310-$(JACKSON_VER)|jackson-databind-nullable-$(NULLABLE_VER)|jsr305-$(JSR305_VER)|opentelemetry-api-$(OTEL_VER)|opentelemetry-context-$(OTEL_VER)" | \
18+
sort -u | paste -sd: -)
19+
20+
COMPILE_CP := $(SDK_JAR):$(DEP_JARS)
21+
RUN_CP := $(BUILD_DIR):$(COMPILE_CP)
22+
23+
# Default target
24+
help:
25+
@echo ""
26+
@echo " E2E Streaming Auth Test — openfga/java-sdk#330"
27+
@echo " ─────────────────────────────────────────────────"
28+
@echo ""
29+
@echo " Targets:"
30+
@echo " up - Start Docker containers (OpenFGA + mock OAuth2)"
31+
@echo " down - Stop and remove Docker containers"
32+
@echo " restart - Recreate Docker containers from scratch"
33+
@echo " status - Show container status"
34+
@echo " logs - Tail container logs"
35+
@echo ""
36+
@echo " build - Build the SDK jar and compile the E2E test"
37+
@echo " run - Compile (if needed) and run the E2E test"
38+
@echo " test - Full end-to-end: up → build → run → report"
39+
@echo ""
40+
@echo " clean - Remove compiled E2E classes"
41+
@echo ""
42+
43+
# ── Docker ──────────────────────────────────────────────────
44+
45+
up:
46+
@echo "Starting Docker containers..."
47+
docker-compose up -d
48+
@echo "Waiting for services to be ready..."
49+
@sleep 3
50+
@curl -sf http://localhost:9090/default/.well-known/openid-configuration > /dev/null && \
51+
echo " ✅ mock-oauth2 ready" || echo " ⏳ mock-oauth2 not ready yet"
52+
@curl -sf -o /dev/null -w "" http://localhost:8082/healthz && \
53+
echo " ✅ openfga-preshared ready" || echo " ⏳ openfga-preshared not ready yet"
54+
@curl -sf -o /dev/null -w "" http://localhost:8080/healthz && \
55+
echo " ✅ openfga-oidc ready" || echo " ⏳ openfga-oidc not ready yet"
56+
57+
down:
58+
docker-compose down
59+
60+
restart: down up
61+
62+
status:
63+
docker-compose ps
64+
65+
logs:
66+
docker-compose logs -f --tail=50
67+
68+
# ── Build & Run ─────────────────────────────────────────────
69+
70+
# Build the SDK jar (delegates to parent Gradle)
71+
../build/libs/openfga-sdk-%.jar:
72+
@echo "Building SDK jar..."
73+
cd .. && ./gradlew jar
74+
75+
build: ../build/libs/openfga-sdk-*.jar
76+
@mkdir -p $(BUILD_DIR)
77+
@echo "Compiling E2EStreamingAuthTest..."
78+
javac -cp "$(COMPILE_CP)" -d $(BUILD_DIR) E2EStreamingAuthTest.java 2>&1 | \
79+
grep -v "^warning:" || true
80+
@echo " ✅ Compiled"
81+
82+
run: build
83+
@echo ""
84+
java -cp "$(RUN_CP)" dev.openfga.sdk.e2e.E2EStreamingAuthTest
85+
86+
# Full lifecycle: start containers, build, run tests
87+
test: up build
88+
@echo ""
89+
@java -cp "$(RUN_CP)" dev.openfga.sdk.e2e.E2EStreamingAuthTest; \
90+
EXIT=$$?; \
91+
echo ""; \
92+
if [ $$EXIT -eq 0 ]; then \
93+
echo "🎉 All E2E tests passed!"; \
94+
else \
95+
echo "💥 Some E2E tests failed (exit $$EXIT)"; \
96+
fi; \
97+
exit $$EXIT
98+
99+
clean:
100+
rm -rf $(BUILD_DIR)
101+

0 commit comments

Comments
 (0)