Skip to content

Commit f4a8c40

Browse files
Adds test coverage for Enumerated Entity Types support (#340)
Signed-off-by: Mudit Chaudhary <chmudit@amazon.com>
1 parent 36d3b8d commit f4a8c40

9 files changed

Lines changed: 682 additions & 0 deletions

File tree

CedarJava/src/test/java/com/cedarpolicy/EntityValidationTests.java

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@
3535
import com.cedarpolicy.model.schema.Schema;
3636
import com.cedarpolicy.pbt.EntityGen;
3737
import com.cedarpolicy.value.EntityTypeName;
38+
import com.cedarpolicy.value.EntityUID;
3839
import com.cedarpolicy.value.PrimBool;
3940
import com.cedarpolicy.value.PrimString;
4041

42+
import java.util.HashMap;
43+
import java.util.HashSet;
44+
4145
/**
4246
* Tests for entity validator
4347
*/
@@ -193,6 +197,147 @@ public void testEntityWithUnknownTagWithCedarSchema() throws AuthException {
193197
"Expected to match regex but was: '%s'".formatted(errMsg));
194198
}
195199

200+
/**
201+
* Test that valid enum entities are accepted.
202+
*/
203+
@Test
204+
public void testValidEnumEntities() throws AuthException {
205+
// Create valid entities using enum types
206+
EntityTypeName userType = EntityTypeName.parse("User").get();
207+
EntityTypeName taskType = EntityTypeName.parse("Task").get();
208+
EntityTypeName colorType = EntityTypeName.parse("Color").get();
209+
210+
Entity user = new Entity(userType.of("alice"), new HashMap<>() {
211+
{
212+
put("name", new PrimString("Alice"));
213+
}
214+
}, new HashSet<>());
215+
216+
Entity task = new Entity(taskType.of("task1"), new HashMap<>() {
217+
{
218+
put("owner", user.getEUID());
219+
put("name", new PrimString("Complete project"));
220+
put("status", new EntityUID(colorType, "Red"));
221+
}
222+
}, new HashSet<>());
223+
224+
EntityValidationRequest request = new EntityValidationRequest(ENUM_SCHEMA, List.of(user, task));
225+
engine.validateEntities(request);
226+
}
227+
228+
/**
229+
* Test that enum entities with invalid enum values are rejected.
230+
*/
231+
@Test
232+
public void testEnumEntitiesWithInvalidValues() throws AuthException {
233+
EntityTypeName userType = EntityTypeName.parse("User").get();
234+
EntityTypeName taskType = EntityTypeName.parse("Task").get();
235+
EntityTypeName colorType = EntityTypeName.parse("Color").get();
236+
237+
Entity user = new Entity(userType.of("alice"), new HashMap<>() {
238+
{
239+
put("name", new PrimString("Alice"));
240+
}
241+
}, new HashSet<>());
242+
243+
// Create task with invalid enum value "Purple" (not in Color enum)
244+
Entity task = new Entity(taskType.of("task1"), new HashMap<>() {
245+
{
246+
put("owner", user.getEUID());
247+
put("name", new PrimString("Complete project"));
248+
put("status", new EntityUID(colorType, "Purple")); // Invalid enum value
249+
}
250+
}, new HashSet<>());
251+
252+
EntityValidationRequest request = new EntityValidationRequest(ENUM_SCHEMA, List.of(user, task));
253+
254+
BadRequestException exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(request));
255+
256+
String errMsg = exception.getErrors().get(0);
257+
assertTrue(errMsg.contains("Purple") || errMsg.contains("Color"),
258+
"Expected error about invalid enum value but was: '%s'".formatted(errMsg));
259+
}
260+
261+
/**
262+
* Test that enum entities cannot have attributes.
263+
*/
264+
@Test
265+
public void testEnumEntitiesCannotHaveAttributes() throws AuthException {
266+
EntityTypeName colorType = EntityTypeName.parse("Color").get();
267+
268+
// Try to create enum entity with attributes (should fail)
269+
Entity enumEntity = new Entity(colorType.of("Red"), new HashMap<>() {
270+
{
271+
put("shade", new PrimString("Dark")); // Enum entities shouldn't have attributes
272+
}
273+
}, new HashSet<>());
274+
275+
EntityValidationRequest request = new EntityValidationRequest(ENUM_SCHEMA, List.of(enumEntity));
276+
277+
BadRequestException exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(request));
278+
279+
String errMsg = exception.getErrors().get(0);
280+
assertTrue(errMsg.contains("attribute") && (errMsg.contains("Color") || errMsg.contains("Red")),
281+
"Expected error about enum entity having attributes but was: '%s'".formatted(errMsg));
282+
}
283+
284+
/**
285+
* Test that enum entities cannot have parents.
286+
*/
287+
@Test
288+
public void testEnumEntitiesCannotHaveParents() throws AuthException {
289+
EntityTypeName colorType = EntityTypeName.parse("Color").get();
290+
EntityTypeName userType = EntityTypeName.parse("User").get();
291+
292+
Entity user = new Entity(userType.of("alice"), new HashMap<>() {
293+
{
294+
put("name", new PrimString("Alice"));
295+
}
296+
}, new HashSet<>());
297+
298+
// Try to create enum entity with parent (should fail)
299+
Entity enumEntity = new Entity(colorType.of("Red"), new HashMap<>(), new HashSet<>() {
300+
{
301+
add(user.getEUID()); // Enum entities shouldn't have parents
302+
}
303+
});
304+
305+
EntityValidationRequest request = new EntityValidationRequest(ENUM_SCHEMA, List.of(user, enumEntity));
306+
307+
BadRequestException exception = assertThrows(BadRequestException.class, () -> engine.validateEntities(request));
308+
309+
String errMsg = exception.getErrors().get(0);
310+
assertTrue(errMsg.contains("parent") || errMsg.contains("ancestor") || errMsg.contains("Color"),
311+
"Expected error about enum entity having parents but was: '%s'".formatted(errMsg));
312+
}
313+
314+
/**
315+
* Test enum entity validation with Cedar schema format.
316+
*/
317+
@Test
318+
public void testEnumEntitiesWithCedarSchema() throws AuthException {
319+
EntityTypeName userType = EntityTypeName.parse("User").get();
320+
EntityTypeName taskType = EntityTypeName.parse("Task").get();
321+
EntityTypeName colorType = EntityTypeName.parse("Color").get();
322+
323+
Entity user = new Entity(userType.of("bob"), new HashMap<>() {
324+
{
325+
put("name", new PrimString("Bob"));
326+
}
327+
}, new HashSet<>());
328+
329+
Entity task = new Entity(taskType.of("task2"), new HashMap<>() {
330+
{
331+
put("owner", user.getEUID());
332+
put("name", new PrimString("Review code"));
333+
put("status", new EntityUID(colorType, "Green"));
334+
}
335+
}, new HashSet<>());
336+
337+
EntityValidationRequest cedarRequest = new EntityValidationRequest(ENUM_SCHEMA_CEDAR, List.of(user, task));
338+
engine.validateEntities(cedarRequest);
339+
}
340+
196341
@BeforeAll
197342
public static void setUp() {
198343

@@ -204,4 +349,6 @@ public static void setUp() {
204349

205350
private static final Schema ROLE_SCHEMA = loadSchemaResource("/role_schema.json");
206351
private static final Schema ROLE_SCHEMA_CEDAR = loadCedarSchemaResource("/role_schema.cedarschema");
352+
private static final Schema ENUM_SCHEMA = loadSchemaResource("/enum_schema.json");
353+
private static final Schema ENUM_SCHEMA_CEDAR = loadCedarSchemaResource("/enum_schema.cedarschema");
207354
}

CedarJava/src/test/java/com/cedarpolicy/SchemaTests.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.Optional;
2020

21+
import static com.cedarpolicy.TestUtil.loadSchemaResource;
22+
import static com.cedarpolicy.TestUtil.loadCedarSchemaResource;
2123
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2224
import static org.junit.jupiter.api.Assertions.assertEquals;
2325
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -235,4 +237,88 @@ void testMalformedSchema() {
235237
assertThrows(InternalException.class, malformedSchema::toJsonFormat);
236238
}
237239
}
240+
241+
@Nested
242+
@DisplayName("Enum Schema Tests")
243+
class EnumSchemaTests {
244+
245+
@Test
246+
void testParseJsonEnumSchema() {
247+
assertDoesNotThrow(() -> {
248+
Schema enumSchema = loadSchemaResource("/enum_schema.json");
249+
assertNotNull(enumSchema, "Enum schema should not be null");
250+
});
251+
}
252+
253+
@Test
254+
@DisplayName("Should parse Cedar schema with enum entities")
255+
void testParseCedarEnumSchema() {
256+
assertDoesNotThrow(() -> {
257+
Schema enumSchema = loadCedarSchemaResource("/enum_schema.cedarschema");
258+
assertNotNull(enumSchema, "Enum schema should not be null");
259+
});
260+
}
261+
262+
@Test
263+
void testRejectEmptyEnums() {
264+
// Test Cedar format empty enum
265+
assertThrows(Exception.class, () -> {
266+
Schema.parse(JsonOrCedar.Cedar, "entity Color enum [];");
267+
});
268+
269+
// Test JSON format empty enum
270+
assertThrows(Exception.class, () -> {
271+
Schema.parse(JsonOrCedar.Json, """
272+
{
273+
"": {
274+
"entityTypes": {
275+
"Color": {
276+
"enum": []
277+
}
278+
},
279+
"actions": {}
280+
}
281+
}
282+
""");
283+
});
284+
}
285+
286+
@Test
287+
void testEnumSchemaFormatConversion() throws Exception {
288+
// Test Cedar to JSON conversion
289+
Schema cedarEnumSchema = Schema.parse(JsonOrCedar.Cedar, """
290+
entity Color enum ["Red", "Blue", "Green"];
291+
entity User;
292+
action view appliesTo { principal: [User], resource: [User] };
293+
""");
294+
295+
JsonNode jsonResult = cedarEnumSchema.toJsonFormat();
296+
assertNotNull(jsonResult, "JSON conversion result should not be null");
297+
298+
// Test JSON to Cedar conversion
299+
String jsonEnumSchema = """
300+
{
301+
"": {
302+
"entityTypes": {
303+
"Color": {
304+
"enum": ["Red", "Blue", "Green"]
305+
},
306+
"User": {}
307+
},
308+
"actions": {
309+
"view": {
310+
"appliesTo": {
311+
"principalTypes": ["User"],
312+
"resourceTypes": ["User"]
313+
}
314+
}
315+
}
316+
}
317+
}
318+
""";
319+
Schema jsonSchemaObj = Schema.parse(JsonOrCedar.Json, jsonEnumSchema);
320+
String cedarResult = jsonSchemaObj.toCedarFormat();
321+
assertNotNull(cedarResult, "Cedar conversion result should not be null");
322+
}
323+
}
238324
}

CedarJava/src/test/java/com/cedarpolicy/ValidationTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,26 @@ public void validateLevelPolicyFailsWhenExpected() {
222222
thenIsNotValid(levelResponse);
223223
}
224224

225+
/** Test enum entity validation with valid enum values. */
226+
@Test
227+
public void givenEnumSchemaAndValidEnumUsageReturnsValid() {
228+
givenSchema(ENUM_SCHEMA);
229+
givenPolicy("policy0", "permit(" + " principal == User::\"alice\"," + " action == Action::\"UpdateTask\","
230+
+ " resource == Task::\"task1\"" + ") when {" + " resource.status == Color::\"Red\"" + "};");
231+
ValidationResponse response = whenValidated();
232+
thenIsValid(response);
233+
}
234+
235+
/** Test enum entity validation with invalid enum values. */
236+
@Test
237+
public void givenEnumSchemaAndInvalidEnumValueReturnsInvalid() {
238+
givenSchema(ENUM_SCHEMA);
239+
givenPolicy("policy0", "permit(" + " principal == User::\"alice\"," + " action == Action::\"UpdateTask\","
240+
+ " resource == Task::\"task1\"" + ") when {" + " resource.status != Color::\"Purple\"" + "};");
241+
ValidationResponse response = whenValidated();
242+
thenIsNotValid(response);
243+
}
244+
225245
private void givenSchema(Schema testSchema) {
226246
this.schema = testSchema;
227247
}
@@ -285,4 +305,6 @@ private void reset() {
285305
private static final Schema PHOTOFLASH_SCHEMA = loadSchemaResource("/photoflash_schema.json");
286306
private static final Schema LIBRARY_SCHEMA = loadSchemaResource("/library_schema.json");
287307
private static final Schema LEVEL_SCHEMA = loadSchemaResource("/level_schema.json");
308+
private static final Schema ENUM_SCHEMA = loadSchemaResource("/enum_schema.json");
309+
288310
}

0 commit comments

Comments
 (0)