Skip to content

Commit 3987ace

Browse files
committed
feat: add integration tests
1 parent d954ca6 commit 3987ace

1 file changed

Lines changed: 276 additions & 0 deletions

File tree

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package dev.openfga.sdk.errors;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import dev.openfga.sdk.api.client.OpenFgaClient;
7+
import dev.openfga.sdk.api.client.model.*;
8+
import dev.openfga.sdk.api.configuration.ClientConfiguration;
9+
import dev.openfga.sdk.api.model.*;
10+
import java.io.IOException;
11+
import java.nio.file.Files;
12+
import java.nio.file.Paths;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.concurrent.CompletableFuture;
16+
import java.util.concurrent.ExecutionException;
17+
import org.junit.jupiter.api.BeforeAll;
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.TestInstance;
21+
import org.junit.jupiter.api.TestInstance.Lifecycle;
22+
import org.testcontainers.junit.jupiter.Container;
23+
import org.testcontainers.junit.jupiter.Testcontainers;
24+
import org.testcontainers.openfga.OpenFGAContainer;
25+
26+
@TestInstance(Lifecycle.PER_CLASS)
27+
@Testcontainers
28+
class FgaErrorIntegrationTest {
29+
30+
@Container
31+
private static final OpenFGAContainer openfga = new OpenFGAContainer("openfga/openfga:v1.10.2");
32+
33+
private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
34+
private static WriteAuthorizationModelRequest authModelRequest;
35+
36+
private OpenFgaClient fga;
37+
private String storeId;
38+
39+
@BeforeAll
40+
static void loadAuthModel() throws IOException {
41+
String authModelJson = Files.readString(Paths.get("src", "test-integration", "resources", "auth-model.json"));
42+
authModelRequest = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class);
43+
}
44+
45+
@BeforeEach
46+
void setUp() throws Exception {
47+
ClientConfiguration config = new ClientConfiguration().apiUrl(openfga.getHttpEndpoint());
48+
fga = new OpenFgaClient(config);
49+
50+
CreateStoreResponse storeResponse =
51+
fga.createStore(new CreateStoreRequest().name("test-store")).get();
52+
storeId = storeResponse.getId();
53+
fga.setStoreId(storeId);
54+
}
55+
56+
@Test
57+
void testValidationError_InvalidType() throws Exception {
58+
WriteAuthorizationModelResponse authModelResponse =
59+
fga.writeAuthorizationModel(authModelRequest).get();
60+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
61+
62+
ClientCheckRequest request = new ClientCheckRequest()
63+
.user("user:123")
64+
.relation("viewer")
65+
._object("invalid_type:doc1");
66+
67+
CompletableFuture<ClientCheckResponse> future = fga.check(request);
68+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
69+
70+
String message = exception.getMessage();
71+
assertNotNull(message);
72+
assertTrue(
73+
message.contains("check") || message.contains("invalid_type") || message.contains("not found"),
74+
"Error message should contain context, got: " + message);
75+
76+
Throwable cause = exception.getCause();
77+
assertInstanceOf(FgaApiValidationError.class, cause);
78+
79+
FgaApiValidationError error = (FgaApiValidationError) cause;
80+
assertEquals("check", error.getOperationName());
81+
assertEquals(400, error.getStatusCode());
82+
assertNotNull(error.getApiErrorMessage());
83+
assertNotNull(error.getMessage());
84+
}
85+
86+
@Test
87+
void testValidationError_InvalidRelation() throws Exception {
88+
WriteAuthorizationModelResponse authModelResponse =
89+
fga.writeAuthorizationModel(authModelRequest).get();
90+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
91+
92+
ClientCheckRequest request = new ClientCheckRequest()
93+
.user("user:123")
94+
.relation("invalid_relation")
95+
._object("document:doc1");
96+
97+
CompletableFuture<ClientCheckResponse> future = fga.check(request);
98+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
99+
100+
Throwable cause = exception.getCause();
101+
assertInstanceOf(FgaApiValidationError.class, cause);
102+
103+
FgaApiValidationError error = (FgaApiValidationError) cause;
104+
assertEquals("check", error.getOperationName());
105+
assertEquals(400, error.getStatusCode());
106+
assertNotNull(error.getApiErrorMessage());
107+
108+
String errorMessage = error.getMessage();
109+
assertNotNull(errorMessage);
110+
assertTrue(
111+
errorMessage.contains("check") || errorMessage.contains("relation") || errorMessage.contains("invalid"),
112+
"Error message should provide context, got: " + errorMessage);
113+
}
114+
115+
@Test
116+
void testValidationError_EmptyUser() throws Exception {
117+
WriteAuthorizationModelResponse authModelResponse =
118+
fga.writeAuthorizationModel(authModelRequest).get();
119+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
120+
121+
ClientCheckRequest request =
122+
new ClientCheckRequest().user("").relation("reader")._object("document:doc1");
123+
124+
CompletableFuture<ClientCheckResponse> future = fga.check(request);
125+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
126+
127+
Throwable cause = exception.getCause();
128+
assertInstanceOf(FgaApiValidationError.class, cause);
129+
130+
FgaApiValidationError error = (FgaApiValidationError) cause;
131+
assertEquals(400, error.getStatusCode());
132+
assertNotNull(error.getApiErrorMessage());
133+
}
134+
135+
@Test
136+
void testValidationError_InvalidStoreId() throws Exception {
137+
fga.setStoreId("01HZZZZZZZZZZZZZZZZZZZZZZ");
138+
139+
CompletableFuture<ClientGetStoreResponse> future = fga.getStore();
140+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
141+
142+
Throwable cause = exception.getCause();
143+
assertInstanceOf(FgaApiValidationError.class, cause);
144+
145+
FgaApiValidationError error = (FgaApiValidationError) cause;
146+
assertEquals(400, error.getStatusCode());
147+
assertNotNull(error.getMessage());
148+
}
149+
150+
@Test
151+
void testValidationError_InvalidAuthorizationModelId() throws Exception {
152+
fga.setAuthorizationModelId("01HZZZZZZZZZZZZZZZZZZZZZZ");
153+
154+
CompletableFuture<ClientReadAuthorizationModelResponse> future = fga.readAuthorizationModel();
155+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
156+
157+
Throwable cause = exception.getCause();
158+
assertInstanceOf(FgaApiValidationError.class, cause);
159+
160+
FgaApiValidationError error = (FgaApiValidationError) cause;
161+
assertEquals(400, error.getStatusCode());
162+
assertEquals("readAuthorizationModel", error.getOperationName());
163+
}
164+
165+
@Test
166+
void testErrorMetadataExtensibility() throws Exception {
167+
WriteAuthorizationModelResponse authModelResponse =
168+
fga.writeAuthorizationModel(authModelRequest).get();
169+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
170+
171+
ClientCheckRequest request = new ClientCheckRequest()
172+
.user("user:123")
173+
.relation("viewer")
174+
._object("invalid_type:doc1");
175+
176+
CompletableFuture<ClientCheckResponse> future = fga.check(request);
177+
178+
try {
179+
future.get();
180+
fail("Expected ExecutionException");
181+
} catch (ExecutionException e) {
182+
FgaError error = (FgaError) e.getCause();
183+
184+
error.addMetadata("retry_attempt", 1);
185+
error.addMetadata("user_context", "admin");
186+
error.addMetadata("store_id", storeId);
187+
188+
Map<String, Object> metadata = error.getMetadata();
189+
assertEquals(1, metadata.get("retry_attempt"));
190+
assertEquals("admin", metadata.get("user_context"));
191+
assertEquals(storeId, metadata.get("store_id"));
192+
}
193+
}
194+
195+
@Test
196+
void testErrorMessageVisibilityInExecutionException() throws Exception {
197+
WriteAuthorizationModelResponse authModelResponse =
198+
fga.writeAuthorizationModel(authModelRequest).get();
199+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
200+
201+
ClientCheckRequest request = new ClientCheckRequest()
202+
.user("user:123")
203+
.relation("viewer")
204+
._object("invalid_type:doc1");
205+
206+
CompletableFuture<ClientCheckResponse> future = fga.check(request);
207+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
208+
209+
String exceptionMessage = exception.getMessage();
210+
assertNotNull(exceptionMessage);
211+
assertFalse(exceptionMessage.trim().isEmpty());
212+
213+
FgaApiValidationError error = (FgaApiValidationError) exception.getCause();
214+
assertEquals("check", error.getOperationName());
215+
assertNotNull(error.getMessage());
216+
assertNotNull(error.getDetailedMessage());
217+
assertNotNull(error.toString());
218+
}
219+
220+
@Test
221+
void testCompleteErrorContext() throws Exception {
222+
WriteAuthorizationModelResponse authModelResponse =
223+
fga.writeAuthorizationModel(authModelRequest).get();
224+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
225+
226+
ClientCheckRequest request = new ClientCheckRequest()
227+
.user("user:123")
228+
.relation("viewer")
229+
._object("invalid_type:doc1");
230+
231+
CompletableFuture<ClientCheckResponse> future = fga.check(request);
232+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
233+
FgaApiValidationError error = (FgaApiValidationError) exception.getCause();
234+
235+
assertEquals(400, error.getStatusCode());
236+
assertEquals("POST", error.getMethod());
237+
assertNotNull(error.getRequestUrl());
238+
assertEquals("check", error.getOperationName());
239+
assertNotNull(error.getApiErrorMessage());
240+
241+
assertDoesNotThrow(() -> error.getRequestId());
242+
assertDoesNotThrow(() -> error.getApiErrorCode());
243+
244+
assertNotNull(error.getMessage());
245+
assertFalse(error.getMessage().isEmpty());
246+
assertNotNull(error.getDetailedMessage());
247+
assertFalse(error.getDetailedMessage().isEmpty());
248+
assertNotNull(error.toString());
249+
assertTrue(error.toString().startsWith("FgaApiValidationError"));
250+
}
251+
252+
@Test
253+
void testWriteValidationError_InvalidTupleKey() throws Exception {
254+
WriteAuthorizationModelResponse authModelResponse =
255+
fga.writeAuthorizationModel(authModelRequest).get();
256+
fga.setAuthorizationModelId(authModelResponse.getAuthorizationModelId());
257+
258+
ClientWriteRequest writeRequest = new ClientWriteRequest()
259+
.writes(List.of(new ClientTupleKey()
260+
.user("user:123")
261+
.relation("reader")
262+
._object("invalid_type:doc1")));
263+
264+
CompletableFuture<ClientWriteResponse> future = fga.write(writeRequest);
265+
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
266+
267+
Throwable cause = exception.getCause();
268+
assertInstanceOf(FgaApiValidationError.class, cause);
269+
270+
FgaApiValidationError error = (FgaApiValidationError) cause;
271+
assertEquals("write", error.getOperationName());
272+
assertEquals(400, error.getStatusCode());
273+
assertNotNull(error.getApiErrorMessage());
274+
}
275+
}
276+

0 commit comments

Comments
 (0)