From e6d18dc65dad26ea345ae39eff1ddc4de359fe53 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Tue, 3 Dec 2024 09:36:11 -0500 Subject: [PATCH 01/28] Add jakarta.validation-api dependency --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 8df8735..6cf5c2f 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,11 @@ json-path 2.9.0 + + jakarta.validation + jakarta.validation-api + 3.1.0 + junit junit From 8680376ebd39918f05660ee9c93b8bf31d7c3d81 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Tue, 3 Dec 2024 15:23:46 -0500 Subject: [PATCH 02/28] Add validation implementation dep --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 6cf5c2f..1824cb8 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,12 @@ jakarta.validation-api 3.1.0 + + org.apache.bval + bval-jsr + 3.0.1 + + junit junit From cc0de68119649383f6dafbe0f9f666f8c0f880da Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Wed, 4 Dec 2024 13:28:59 -0500 Subject: [PATCH 03/28] Remove warnings in model package --- src/main/java/com/yetanalytics/xapi/model/Extensions.java | 8 +++++--- .../java/com/yetanalytics/xapi/model/InteractionType.java | 2 +- src/main/java/com/yetanalytics/xapi/model/LangMap.java | 2 +- src/main/java/com/yetanalytics/xapi/model/ObjectType.java | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/yetanalytics/xapi/model/Extensions.java b/src/main/java/com/yetanalytics/xapi/model/Extensions.java index 10acc0d..09ee477 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Extensions.java +++ b/src/main/java/com/yetanalytics/xapi/model/Extensions.java @@ -24,7 +24,7 @@ @JsonSerialize(using = ExtensionSerializer.class) public class Extensions { - private Map extMap = new HashMap(); + private Map extMap = new HashMap<>(); public Extensions(Map input) { extMap = input; @@ -65,9 +65,11 @@ public T read(String key, String jsonPathExpression, Class typeKey) { return (T) JsonPath.read(json, jsonPathExpression); } catch (PathNotFoundException e) { //TODO: logging framework - e.printStackTrace(); + System.err.println("Path not found"); + // e.printStackTrace(); } catch (JsonProcessingException e) { - e.printStackTrace(); + System.err.println("JSON cannot be processed"); + // e.printStackTrace(); } return null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/InteractionType.java b/src/main/java/com/yetanalytics/xapi/model/InteractionType.java index 0e4bd59..db2f285 100644 --- a/src/main/java/com/yetanalytics/xapi/model/InteractionType.java +++ b/src/main/java/com/yetanalytics/xapi/model/InteractionType.java @@ -21,7 +21,7 @@ public enum InteractionType { NUMERIC("numeric"), OTHER("other"); - private String displayName; + private final String displayName; InteractionType(String displayName) { this.displayName = displayName; diff --git a/src/main/java/com/yetanalytics/xapi/model/LangMap.java b/src/main/java/com/yetanalytics/xapi/model/LangMap.java index a601a4a..cb451a4 100644 --- a/src/main/java/com/yetanalytics/xapi/model/LangMap.java +++ b/src/main/java/com/yetanalytics/xapi/model/LangMap.java @@ -20,7 +20,7 @@ @JsonSerialize(using = LangMapSerializer.class) public class LangMap { - private HashMap languageHashMap = new HashMap(); + private HashMap languageHashMap = new HashMap<>(); /** * Create a new langmap from a HashMap diff --git a/src/main/java/com/yetanalytics/xapi/model/ObjectType.java b/src/main/java/com/yetanalytics/xapi/model/ObjectType.java index 4f91938..7ba66cc 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ObjectType.java +++ b/src/main/java/com/yetanalytics/xapi/model/ObjectType.java @@ -16,7 +16,7 @@ public enum ObjectType { AGENT("Agent"), GROUP("Group"); - private String displayName; + private final String displayName; ObjectType(String displayName) { this.displayName = displayName; From 4b2ba1118d765be4342f1ec5ca55b2d46ad40d3a Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Wed, 4 Dec 2024 13:32:50 -0500 Subject: [PATCH 04/28] Remove warning in test case --- .../yetanalytics/XapiDeserializationTest.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/yetanalytics/XapiDeserializationTest.java b/src/test/java/com/yetanalytics/XapiDeserializationTest.java index d43a5b7..ffc6f03 100644 --- a/src/test/java/com/yetanalytics/XapiDeserializationTest.java +++ b/src/test/java/com/yetanalytics/XapiDeserializationTest.java @@ -1,8 +1,8 @@ package com.yetanalytics; +import java.io.File; import java.io.IOException; import java.math.BigDecimal; -import java.io.File; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.LinkedHashMap; @@ -12,7 +12,22 @@ import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.databind.DatabindException; import com.yetanalytics.util.TestFileUtils; -import com.yetanalytics.xapi.model.*; +import com.yetanalytics.xapi.model.AbstractActor; +import com.yetanalytics.xapi.model.Activity; +import com.yetanalytics.xapi.model.ActivityDefinition; +import com.yetanalytics.xapi.model.Agent; +import com.yetanalytics.xapi.model.Attachment; +import com.yetanalytics.xapi.model.Context; +import com.yetanalytics.xapi.model.ContextActivities; +import com.yetanalytics.xapi.model.Extensions; +import com.yetanalytics.xapi.model.Group; +import com.yetanalytics.xapi.model.InteractionComponent; +import com.yetanalytics.xapi.model.InteractionType; +import com.yetanalytics.xapi.model.Result; +import com.yetanalytics.xapi.model.Score; +import com.yetanalytics.xapi.model.Statement; +import com.yetanalytics.xapi.model.StatementResult; +import com.yetanalytics.xapi.model.Verb; import com.yetanalytics.xapi.util.Mapper; import junit.framework.Test; @@ -102,7 +117,7 @@ public void testExtensions() throws StreamReadException, DatabindException, IOEx Integer elementNumber = ext.read(extKey, "$.listOfThings[1].number", Integer.class); assertEquals(elementNumber, Integer.valueOf(2)); Double decimalEntry = ext.read(extKey, "$.decimalEntry", Double.class); - assertEquals(decimalEntry, Double.valueOf(3.14159)); + assertEquals(decimalEntry, 3.14159); Boolean boolEntry = ext.read(extKey, "$.boolEntry", Boolean.class); assertEquals(boolEntry, Boolean.TRUE); String nullEntry = ext.read(extKey, "$.nullEntry", String.class); From 847d1b93360e39db4b42892094e35395acd79cc0 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Wed, 4 Dec 2024 13:35:47 -0500 Subject: [PATCH 05/28] Remove remaining warnings from xapi model --- src/main/java/com/yetanalytics/xapi/model/XapiDuration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/yetanalytics/xapi/model/XapiDuration.java b/src/main/java/com/yetanalytics/xapi/model/XapiDuration.java index 870d604..73e0076 100644 --- a/src/main/java/com/yetanalytics/xapi/model/XapiDuration.java +++ b/src/main/java/com/yetanalytics/xapi/model/XapiDuration.java @@ -3,9 +3,9 @@ import java.time.Duration; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonValue; /** * Class representation of ISO 8601 Duration @@ -26,9 +26,9 @@ public XapiDuration(String duration){ this.duration = Duration.parse(duration); } - private String original; + private final String original; - private Duration duration; + private final Duration duration; /** * Returns the original String version of the Duration From fa031cd4b02aa5f3b79602f783175923beecdc95 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Thu, 5 Dec 2024 16:14:30 -0500 Subject: [PATCH 06/28] Add validation that Agents only have 1 IFI --- .../xapi/model/AbstractActor.java | 21 +++++- .../com/yetanalytics/xapi/model/Agent.java | 11 ++++ .../com/yetanalytics/model/AgentTest.java | 65 +++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/yetanalytics/model/AgentTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java index 331d33b..bc92abc 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java @@ -11,12 +11,13 @@ public abstract class AbstractActor extends AbstractObject { private String name; - //IFI + // IFIs private String mbox; private String mbox_sha1sum; private String openid; private Account account; + // Getters and Setters public String getName() { return name; } @@ -51,4 +52,22 @@ public Account getAccount() { public void setAccount(Account account) { this.account = account; } + + // Validation + protected int countIFIs() { + int notNullCount = 0; + if (mbox != null) { + ++notNullCount; + } + if (mbox_sha1sum != null) { + ++notNullCount; + } + if (openid != null) { + ++notNullCount; + } + if (account != null) { + ++notNullCount; + } + return notNullCount; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Agent.java b/src/main/java/com/yetanalytics/xapi/model/Agent.java index 836186c..bf50df3 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Agent.java +++ b/src/main/java/com/yetanalytics/xapi/model/Agent.java @@ -13,4 +13,15 @@ @JsonDeserialize public class Agent extends AbstractActor { + // Validation + + /** + * Assertion that the Agent has only 1 Inverse Functional Identifier (IFI). + * @return true if the Agent has exactly 1 IFI, false otherwise + */ + @AssertTrue + public boolean isIdentifiedAgent() { + return countIFIs() == 1; + } + } diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java new file mode 100644 index 0000000..97e6322 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -0,0 +1,65 @@ +package com.yetanalytics.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Account; +import com.yetanalytics.xapi.model.Agent; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class AgentTest { + private Validator validator; + private Agent agent; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + agent = new Agent(); + } + + @Test + public void testMbox() { + agent.setMbox("mailto:foo@example.com"); + assertTrue(validator.validate(agent).isEmpty()); + } + + @Test + public void testMboxSha1Sum() { + agent.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); + assertTrue(validator.validate(agent).isEmpty()); + } + + @Test + public void testOpenid() { + agent.setOpenid("http://openid.example.com"); + assertTrue(validator.validate(agent).isEmpty()); + } + + @Test + public void testAccount() { + Account account = new Account(); + account.setHomePage("http://examplehomepage.com"); + account.setName("My Account"); + + agent.setAccount(account); + assertTrue(validator.validate(agent).isEmpty()); + } + + @Test + public void testNoIFI() { + var violations = validator.validate(agent); + assertEquals(1, violations.size()); + } + + @Test + public void testMultiIFI() { + agent.setMbox("mailto:foo@example.com"); + agent.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); + var violations = validator.validate(agent); + assertEquals(1, violations.size()); + } +} From 1e1553c37be6222ba406f9f6ddcf20817c325445 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Thu, 5 Dec 2024 16:20:58 -0500 Subject: [PATCH 07/28] Add validation for anonymous vs identified groups --- .../com/yetanalytics/xapi/model/Group.java | 7 ++ .../com/yetanalytics/model/GroupTest.java | 80 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/test/java/com/yetanalytics/model/GroupTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Group.java b/src/main/java/com/yetanalytics/xapi/model/Group.java index 8ee8b8a..5ae74e1 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Group.java +++ b/src/main/java/com/yetanalytics/xapi/model/Group.java @@ -24,5 +24,12 @@ public void setMember(List member) { this.member = member; } + @AssertTrue + public boolean isAnonymousOrIdentifiedGroup() { + return ( + (countIFIs() == 0 && member != null) || + (countIFIs() == 1) + ); + } } diff --git a/src/test/java/com/yetanalytics/model/GroupTest.java b/src/test/java/com/yetanalytics/model/GroupTest.java new file mode 100644 index 0000000..dfd56ac --- /dev/null +++ b/src/test/java/com/yetanalytics/model/GroupTest.java @@ -0,0 +1,80 @@ +package com.yetanalytics.model; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Account; +import com.yetanalytics.xapi.model.Agent; +import com.yetanalytics.xapi.model.Group; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class GroupTest { + private Validator validator; + private Group group; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + group = new Group(); + } + + @Test + public void testAnomyousGroup() { + List member = new ArrayList<>(); + Agent memberAgent = new Agent(); + memberAgent.setMbox("mailto:mem@example.com"); + member.add(memberAgent); + group.setMember(member); + assertTrue(validator.validate(group).isEmpty()); + } + + @Test + public void testMbox() { + group.setMbox("mailto:foo@example.com"); + assertTrue(validator.validate(group).isEmpty()); + } + + @Test + public void testMboxSha1Sum() { + group.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); + assertTrue(validator.validate(group).isEmpty()); + } + + @Test + public void testOpenid() { + group.setOpenid("http://openid.example.com"); + assertTrue(validator.validate(group).isEmpty()); + } + + @Test + public void testAccount() { + Account account = new Account(); + account.setHomePage("http://examplehomepage.com"); + account.setName("My Account"); + + group.setAccount(account); + assertTrue(validator.validate(group).isEmpty()); + } + + @Test + public void testNoIFI() { // No member array => identified group + var violations = validator.validate(group); + assertEquals(1, violations.size()); + } + + @Test + public void testMultiIFI() { + group.setMbox("mailto:foo@example.com"); + group.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); + var violations = validator.validate(group); + assertEquals(1, violations.size()); + } + +} From dd6b67380642e7d84e1427e8d136a2c60def91e4 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Thu, 5 Dec 2024 16:30:03 -0500 Subject: [PATCH 08/28] Add NotNull constraints to Account --- .../com/yetanalytics/xapi/model/Account.java | 5 ++- .../com/yetanalytics/model/AccountTest.java | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/yetanalytics/model/AccountTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Account.java b/src/main/java/com/yetanalytics/xapi/model/Account.java index 2f3469c..5470ad3 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Account.java +++ b/src/main/java/com/yetanalytics/xapi/model/Account.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.NotNull; + /** * Class representation of the Account Component of the * 9274.1.1 xAPI Specification. @@ -10,8 +12,9 @@ @JsonInclude(Include.NON_NULL) public class Account { + @NotNull private String homePage; - + @NotNull private String name; public String getHomePage() { diff --git a/src/test/java/com/yetanalytics/model/AccountTest.java b/src/test/java/com/yetanalytics/model/AccountTest.java new file mode 100644 index 0000000..845c077 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/AccountTest.java @@ -0,0 +1,36 @@ +package com.yetanalytics.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Account; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class AccountTest { + private Validator validator; + private Account account; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + account = new Account(); + } + + @Test + public void testValidAccount() { + account.setHomePage("http://examplehomepage.com"); + account.setName("My Account"); + assertTrue(validator.validate(account).isEmpty()); + } + + @Test + public void testEmptyAccount() { + var violations = validator.validate(account); + assertEquals(2, violations.size()); + } + +} From 1798221c11c74794e05013b2e60d19fbd707a622 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Thu, 5 Dec 2024 16:35:01 -0500 Subject: [PATCH 09/28] Add tests and TODOs for actor fields --- .../java/com/yetanalytics/xapi/model/AbstractActor.java | 9 +++++++++ src/test/java/com/yetanalytics/model/AgentTest.java | 8 ++++++++ src/test/java/com/yetanalytics/model/GroupTest.java | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java index bc92abc..7e96a6b 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.AbstractActorDeserializer; +import jakarta.validation.Valid; + /** * Abstract Class for serialization and deserialization of xAPI Actors */ @@ -12,9 +14,14 @@ public abstract class AbstractActor extends AbstractObject { private String name; // IFIs + + // TODO: Validate mbox is a valid mailto IRI private String mbox; + // TODO: Validate mbox_sha1sum is a valid hash string private String mbox_sha1sum; + // TODO: Validate openid is a valid IRI private String openid; + @Valid private Account account; // Getters and Setters @@ -70,4 +77,6 @@ protected int countIFIs() { } return notNullCount; } + + public abstract boolean isValidAuthority(); } diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java index 97e6322..eecf35a 100644 --- a/src/test/java/com/yetanalytics/model/AgentTest.java +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -49,6 +49,14 @@ public void testAccount() { assertTrue(validator.validate(agent).isEmpty()); } + @Test + public void testInvalidAccount() { + Account account = new Account(); + agent.setAccount(account); + var violations = validator.validate(agent); + assertEquals(2, violations.size()); + } + @Test public void testNoIFI() { var violations = validator.validate(agent); diff --git a/src/test/java/com/yetanalytics/model/GroupTest.java b/src/test/java/com/yetanalytics/model/GroupTest.java index dfd56ac..8ebbce8 100644 --- a/src/test/java/com/yetanalytics/model/GroupTest.java +++ b/src/test/java/com/yetanalytics/model/GroupTest.java @@ -63,6 +63,14 @@ public void testAccount() { assertTrue(validator.validate(group).isEmpty()); } + @Test + public void testInvalidAccount() { + Account account = new Account(); + group.setAccount(account); + var violations = validator.validate(group); + assertEquals(2, violations.size()); + } + @Test public void testNoIFI() { // No member array => identified group var violations = validator.validate(group); From 6314acdd5fa3b9f3afc2445d0ccf549c3b9d2e26 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Thu, 5 Dec 2024 16:35:55 -0500 Subject: [PATCH 10/28] Don't forget imports in Agent and Group classes --- src/main/java/com/yetanalytics/xapi/model/Agent.java | 4 +++- src/main/java/com/yetanalytics/xapi/model/Group.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/yetanalytics/xapi/model/Agent.java b/src/main/java/com/yetanalytics/xapi/model/Agent.java index bf50df3..cc9e8d0 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Agent.java +++ b/src/main/java/com/yetanalytics/xapi/model/Agent.java @@ -1,8 +1,10 @@ package com.yetanalytics.xapi.model; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import jakarta.validation.constraints.AssertTrue; /** * A concrete class representation of the Agent Component of the diff --git a/src/main/java/com/yetanalytics/xapi/model/Group.java b/src/main/java/com/yetanalytics/xapi/model/Group.java index 5ae74e1..cc05c29 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Group.java +++ b/src/main/java/com/yetanalytics/xapi/model/Group.java @@ -2,9 +2,11 @@ import java.util.List; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import jakarta.validation.constraints.AssertTrue; /** * Class representation of the Group Component of the From 8191807caea9f37f6055824ab0f05974146f9ee0 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Thu, 5 Dec 2024 16:50:16 -0500 Subject: [PATCH 11/28] Add Verb validation --- .../com/yetanalytics/xapi/model/Verb.java | 15 +++++-- .../java/com/yetanalytics/model/VerbTest.java | 41 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/yetanalytics/model/VerbTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Verb.java b/src/main/java/com/yetanalytics/xapi/model/Verb.java index b00c5b7..e6d1178 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Verb.java +++ b/src/main/java/com/yetanalytics/xapi/model/Verb.java @@ -1,9 +1,11 @@ package com.yetanalytics.xapi.model; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.yetanalytics.xapi.model.deserializers.LangMapDeserializer; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.yetanalytics.xapi.model.deserializers.LangMapDeserializer; + +import jakarta.validation.constraints.NotNull; /** * Class representation of the Verb component of the @@ -12,7 +14,10 @@ @JsonInclude(Include.NON_NULL) public class Verb { - private String id; + public static final String VOIDING_VERB_IRI = "http://adlnet.gov/expapi/verbs/voided"; + + @NotNull + private String id; // TODO: Validate id is an IRI @JsonDeserialize(using = LangMapDeserializer.class) private LangMap display; @@ -32,5 +37,9 @@ public LangMap getDisplay() { public void setDisplay(LangMap display) { this.display = display; } + + public boolean isVoiding() { + return id == VOIDING_VERB_IRI; + } } diff --git a/src/test/java/com/yetanalytics/model/VerbTest.java b/src/test/java/com/yetanalytics/model/VerbTest.java new file mode 100644 index 0000000..7bac522 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/VerbTest.java @@ -0,0 +1,41 @@ +package com.yetanalytics.model; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.LangMap; +import com.yetanalytics.xapi.model.Verb; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class VerbTest { + private Validator validator; + private Verb verb; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + verb = new Verb(); + } + + @Test + public void testVerb() { + LangMap display = new LangMap(new HashMap<>()); + display.put("en-US", "Example Verb"); + + verb.setId("http://example.com/verb"); + verb.setDisplay(display); + assertTrue(validator.validate(verb).isEmpty()); + } + + @Test + public void testEmptyVerb() { + var violations = validator.validate(verb); + assertEquals(1, violations.size()); + } +} From f8e7427432bca48b7baaebfd73d56636bc83589a Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 13:29:48 -0500 Subject: [PATCH 12/28] Add Activity + ActivityDefinition validation --- .../com/yetanalytics/xapi/model/Activity.java | 7 +- .../xapi/model/ActivityDefinition.java | 81 +++++++++ .../model/ActivityDefinitionTest.java | 156 ++++++++++++++++++ .../com/yetanalytics/model/ActivityTest.java | 42 +++++ 4 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java create mode 100644 src/test/java/com/yetanalytics/model/ActivityTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Activity.java b/src/main/java/com/yetanalytics/xapi/model/Activity.java index 91fa6fd..b730b25 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Activity.java +++ b/src/main/java/com/yetanalytics/xapi/model/Activity.java @@ -1,8 +1,11 @@ package com.yetanalytics.xapi.model; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; /** * Class representation of the Activity Object Type of the @@ -11,8 +14,10 @@ @JsonInclude(Include.NON_NULL) @JsonDeserialize public class Activity extends AbstractObject { + @NotNull private String id; + @Valid private ActivityDefinition definition; public String getId() { diff --git a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java index 853c7be..b83e473 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java +++ b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertTrue; + /** * Class representation of the Activity Definition Component of the * 9274.1.1 xAPI Specification. @@ -101,4 +103,83 @@ public List getSteps() { public void setSteps(List steps) { this.steps = steps; } + + // Validation + + private boolean isChoices() { + return ( + // choices is allowed + scale == null && + source == null && + target == null && + steps == null + ); + } + + private boolean isScale() { + return ( + // scale is allowed + choices == null && + source == null && + target == null && + steps == null + ); + } + + private boolean isSourceTarget() { + return ( + // source and target are allowed + choices == null && + scale == null && + steps == null + ); + } + + private boolean isSteps() { + return ( + // steps is allowed + choices == null && + scale == null && + source == null && + target == null + ); + } + + private boolean isNoInteractionComponents() { + return ( + choices == null && + scale == null && + source == null && + target == null && + steps == null + ); + } + + private boolean isNoInteraction() { + return ( + isNoInteractionComponents() && + correctResponsesPattern == null + ); + } + + @AssertTrue + public boolean isValidInteractionActivity() { + if (interactionType == null) { + return isNoInteraction(); + } + switch (interactionType) { + case CHOICE: + return isChoices(); + case SEQUENCING: + return isChoices(); + case LIKERT: + return isScale(); + case MATCHING: + return isSourceTarget(); + case PERFORMANCE: + return isSteps(); + default: + return isNoInteractionComponents(); + } + } } diff --git a/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java b/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java new file mode 100644 index 0000000..164e194 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java @@ -0,0 +1,156 @@ +package com.yetanalytics.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.ActivityDefinition; +import com.yetanalytics.xapi.model.Extensions; +import com.yetanalytics.xapi.model.InteractionComponent; +import com.yetanalytics.xapi.model.InteractionType; +import com.yetanalytics.xapi.model.LangMap; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class ActivityDefinitionTest { + private Validator validator; + private ActivityDefinition definition; + + private List makeComp(String id, String descStr) { + LangMap desc = new LangMap(new HashMap<>()); + desc.put("en-US", descStr); + + InteractionComponent component = new InteractionComponent(); + component.setId(id); + component.setDescription(desc); + + List components = new ArrayList<>(); + components.add(component); + + return components; + } + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + definition = new ActivityDefinition(); + } + + @Test + public void testEmptyDefinition() { + assertTrue(validator.validate(definition).isEmpty()); + } + + @Test + public void testDefinition() { + LangMap name = new LangMap(new HashMap<>()); + name.put("en-US", "Example Definition"); + + LangMap desc = new LangMap(new HashMap<>()); + desc.put("en-US", "This is an example Activity Definition"); + + String type = "http://example.com/activity-type"; + + String moreInfo = "http://yetanalytics.com"; + + Extensions ext = new Extensions(new HashMap<>()); + ext.put("http://example.org/string-value", "Foo Bar"); + + definition.setName(name); + definition.setDescription(desc); + definition.setType(type); + definition.setMoreInfo(moreInfo); + definition.setExtensions(ext); + + assertTrue(validator.validate(definition).isEmpty()); + + List choices = makeComp("component", "Interaction Component"); + definition.setChoices(choices); + + List correctResponsesPattern = new ArrayList<>(); + correctResponsesPattern.add("Response 1"); + correctResponsesPattern.add("Response 2"); + definition.setCorrectResponsesPattern(correctResponsesPattern); + + var violations = validator.validate(definition); + assertEquals(1, violations.size()); + } + + @Test + public void testChoiceDefinition() { + definition.setInteractionType(InteractionType.CHOICE); + assertTrue(validator.validate(definition).isEmpty()); + + List choices = makeComp("choice", "Choice"); + definition.setChoices(choices); + assertTrue(validator.validate(definition).isEmpty()); + + definition.setInteractionType(InteractionType.TRUE_FALSE); + var violations = validator.validate(definition); + assertEquals(1, violations.size()); + } + + @Test + public void testSequencingDefinition() { + definition.setInteractionType(InteractionType.SEQUENCING); + assertTrue(validator.validate(definition).isEmpty()); + + List choices = makeComp("choice", "Choice"); + definition.setChoices(choices); + assertTrue(validator.validate(definition).isEmpty()); + + definition.setInteractionType(InteractionType.FILL_IN); + var violations = validator.validate(definition); + assertEquals(1, violations.size()); + } + + @Test + public void testLikertDefinition() { + definition.setInteractionType(InteractionType.LIKERT); + assertTrue(validator.validate(definition).isEmpty()); + + List scale = makeComp("scale", "Scale"); + definition.setScale(scale); + assertTrue(validator.validate(definition).isEmpty()); + + definition.setInteractionType(InteractionType.LONG_FILL_IN); + var violations = validator.validate(definition); + assertEquals(1, violations.size()); + } + + @Test + public void testMatchingDefinition() { + definition.setInteractionType(InteractionType.MATCHING); + assertTrue(validator.validate(definition).isEmpty()); + + List source = makeComp("source", "Source"); + List target = makeComp("target", "Target"); + definition.setSource(source); + definition.setTarget(target); + assertTrue(validator.validate(definition).isEmpty()); + + definition.setInteractionType(InteractionType.NUMERIC); + var violations = validator.validate(definition); + assertEquals(1, violations.size()); + } + + @Test + public void testPerformanceDefinition() { + definition.setInteractionType(InteractionType.PERFORMANCE); + assertTrue(validator.validate(definition).isEmpty()); + + List steps = makeComp("steps", "Steps"); + definition.setSteps(steps); + assertTrue(validator.validate(definition).isEmpty()); + + definition.setInteractionType(InteractionType.OTHER); + var violations = validator.validate(definition); + assertEquals(1, violations.size()); + } +} diff --git a/src/test/java/com/yetanalytics/model/ActivityTest.java b/src/test/java/com/yetanalytics/model/ActivityTest.java new file mode 100644 index 0000000..dcf96a3 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/ActivityTest.java @@ -0,0 +1,42 @@ +package com.yetanalytics.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Activity; +import com.yetanalytics.xapi.model.ActivityDefinition; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class ActivityTest { + private Validator validator; + private ActivityDefinition definition; + private Activity activity; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + definition = new ActivityDefinition(); + activity = new Activity(); + } + + @Test + public void testActivity() { + activity.setId("http://example.org/activity"); + assertTrue(validator.validate(activity).isEmpty()); + + // TODO: Enforce that definitions, as JSON objects, can't be empty + // For now we allow them though + activity.setDefinition(definition); + assertTrue(validator.validate(activity).isEmpty()); + } + + @Test + public void testEmptyActivity() { + var violations = validator.validate(activity); + assertEquals(1, violations.size()); + } +} From 2bc3d50006e55d460e67d7b2629427be6eba318f Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 13:47:06 -0500 Subject: [PATCH 13/28] Add Result and Score --- .../com/yetanalytics/xapi/model/Result.java | 3 + .../com/yetanalytics/xapi/model/Score.java | 37 ++++++++++ .../com/yetanalytics/model/ResultTest.java | 48 ++++++++++++ .../com/yetanalytics/model/ScoreTest.java | 73 +++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/test/java/com/yetanalytics/model/ResultTest.java create mode 100644 src/test/java/com/yetanalytics/model/ScoreTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Result.java b/src/main/java/com/yetanalytics/xapi/model/Result.java index bc69724..b4e47db 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Result.java +++ b/src/main/java/com/yetanalytics/xapi/model/Result.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.Valid; + /** * Class representation of the Result component of the * 9274.1.1 xAPI Specification. @@ -10,6 +12,7 @@ @JsonInclude(Include.NON_NULL) public class Result { + @Valid private Score score; private Boolean success; private Boolean completion; diff --git a/src/main/java/com/yetanalytics/xapi/model/Score.java b/src/main/java/com/yetanalytics/xapi/model/Score.java index d7a6225..1388a56 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Score.java +++ b/src/main/java/com/yetanalytics/xapi/model/Score.java @@ -1,9 +1,14 @@ package com.yetanalytics.xapi.model; + import java.math.BigDecimal; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; + /** * Class representation of the Score component of the * 9274.1.1 xAPI Specification. @@ -14,6 +19,9 @@ public class Score { private BigDecimal raw; private BigDecimal min; private BigDecimal max; + + @DecimalMax(value = "1.0") + @DecimalMin(value = "-1.0") private BigDecimal scaled; public BigDecimal getRaw() { @@ -40,4 +48,33 @@ public BigDecimal getScaled() { public void setScaled(BigDecimal scaled) { this.scaled = scaled; } + + // Validation + + @AssertTrue + public boolean isMinLessThanRaw() { + if (raw != null && min != null) { + return min.compareTo(raw) < 0; + } else { + return true; + } + } + + @AssertTrue + public boolean isRawLessThanMax() { + if (raw != null && max != null) { + return raw.compareTo(max) < 0; + } else { + return true; + } + } + + @AssertTrue + public boolean isMinLessThanMax() { + if (min != null && max != null) { + return min.compareTo(max) < 0; + } else { + return true; + } + } } diff --git a/src/test/java/com/yetanalytics/model/ResultTest.java b/src/test/java/com/yetanalytics/model/ResultTest.java new file mode 100644 index 0000000..c5f0c94 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/ResultTest.java @@ -0,0 +1,48 @@ +package com.yetanalytics.model; + +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Result; +import com.yetanalytics.xapi.model.Score; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class ResultTest { + private Validator validator; + private Result result; + private Score score; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + score = new Score(); + result = new Result(); + } + + @Test + public void testResult() { + score.setScaled(new BigDecimal(0.5)); + result.setScore(score); + result.setSuccess(false); + result.setCompletion(true); + result.setResponse("myResponse"); + // TODO: setExtensions + // TODO: setDuration + + assertTrue(validator.validate(result).isEmpty()); + } + + @Test + public void testInvalidScore() { + score.setScaled(new BigDecimal(3.0)); + result.setScore(score); + assertEquals(1, validator.validate(result).size()); + } + +} diff --git a/src/test/java/com/yetanalytics/model/ScoreTest.java b/src/test/java/com/yetanalytics/model/ScoreTest.java new file mode 100644 index 0000000..66c85f1 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/ScoreTest.java @@ -0,0 +1,73 @@ +package com.yetanalytics.model; + +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Score; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class ScoreTest { + private Validator validator; + private Score score; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + score = new Score(); + } + + @Test + public void testScore() { + assertTrue(validator.validate(score).isEmpty()); + + score.setMin(new BigDecimal(0)); + score.setMax(new BigDecimal(100)); + score.setRaw(new BigDecimal(50)); + score.setScaled(new BigDecimal(0)); + assertTrue(validator.validate(score).isEmpty()); + } + + @Test + public void testScaledTooSmall() { + score.setScaled(new BigDecimal(-2.0)); + var violations = validator.validate(score); + assertEquals(1, violations.size()); + } + + @Test + public void testScaledTooBig() { + score.setScaled(new BigDecimal(2.0)); + var violations = validator.validate(score); + assertEquals(1, violations.size()); + } + + @Test + public void testMinBiggerThanMax() { + score.setMax(new BigDecimal(0.4)); + score.setMin(new BigDecimal(0.6)); + var violations = validator.validate(score); + assertEquals(1, violations.size()); + } + + @Test + public void testRawBiggerThanMax() { + score.setMax(new BigDecimal(0.4)); + score.setRaw(new BigDecimal(0.6)); + var violations = validator.validate(score); + assertEquals(1, violations.size()); + } + + @Test + public void testMinBiggerThanRaw() { + score.setRaw(new BigDecimal(0.4)); + score.setMin(new BigDecimal(0.6)); + var violations = validator.validate(score); + assertEquals(1, violations.size()); + } +} From 7e54c630270262418c5c18bd832c91a62a620e17 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 13:51:48 -0500 Subject: [PATCH 14/28] Add InteractionComponent --- .../xapi/model/InteractionComponent.java | 3 ++ .../model/InteractionComponentTest.java | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/test/java/com/yetanalytics/model/InteractionComponentTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java index bc40fed..0966dc4 100644 --- a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java +++ b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.NotNull; + /** * Class representation of the Interaction Component of the * 9274.1.1 xAPI Specification. @@ -9,6 +11,7 @@ @JsonInclude(Include.NON_NULL) public class InteractionComponent { + @NotNull private String id; private LangMap description; diff --git a/src/test/java/com/yetanalytics/model/InteractionComponentTest.java b/src/test/java/com/yetanalytics/model/InteractionComponentTest.java new file mode 100644 index 0000000..8149fed --- /dev/null +++ b/src/test/java/com/yetanalytics/model/InteractionComponentTest.java @@ -0,0 +1,39 @@ +package com.yetanalytics.model; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.InteractionComponent; +import com.yetanalytics.xapi.model.LangMap; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class InteractionComponentTest { + private Validator validator; + private InteractionComponent interactionComponent; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + interactionComponent = new InteractionComponent(); + } + + @Test + public void testInteractionComponent() { + LangMap desc = new LangMap(new HashMap<>()); + desc.put("en-US", "Foo"); + interactionComponent.setId("foo"); + interactionComponent.setDescription(desc); + assertTrue(validator.validate(interactionComponent).isEmpty()); + } + + @Test + public void testEmpytInteractionComponent() { + assertEquals(1, validator.validate(interactionComponent).size()); + } +} From 94725e4f703613518e372d9daa2b176f4e67d200 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 14:10:54 -0500 Subject: [PATCH 15/28] Add Attachment constraints --- .../yetanalytics/xapi/model/Attachment.java | 7 +++ .../yetanalytics/model/AttachmentTest.java | 53 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/test/java/com/yetanalytics/model/AttachmentTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Attachment.java b/src/main/java/com/yetanalytics/xapi/model/Attachment.java index 69faca2..149f05a 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Attachment.java +++ b/src/main/java/com/yetanalytics/xapi/model/Attachment.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.NotNull; + /** * Class representation of the Attachment Component of the * 9274.1.1 xAPI Specification. @@ -10,11 +12,16 @@ @JsonInclude(Include.NON_NULL) public class Attachment { + @NotNull private String usageType; + @NotNull private LangMap display; private LangMap description; + @NotNull private String contentType; + @NotNull private Integer length; + @NotNull private String sha2; private String fileUrl; diff --git a/src/test/java/com/yetanalytics/model/AttachmentTest.java b/src/test/java/com/yetanalytics/model/AttachmentTest.java new file mode 100644 index 0000000..c82f40b --- /dev/null +++ b/src/test/java/com/yetanalytics/model/AttachmentTest.java @@ -0,0 +1,53 @@ +package com.yetanalytics.model; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Attachment; +import com.yetanalytics.xapi.model.LangMap; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class AttachmentTest { + private Validator validator; + private Attachment attachment; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + attachment = new Attachment(); + } + + @Test + public void testAttachment() { + LangMap display = new LangMap(new HashMap<>()); + display.put("en-US", "Display"); + + LangMap desc = new LangMap(new HashMap<>()); + desc.put("en-US", "Description"); + + String contentType = "application/json"; + int length = 450; + String sha2 = "426cf3a8b2864dd91201b989ba5728181da52bfff9a0489670e54cd8ec8b3a50"; + String fileUrl = "https://www.yetanalytics.com/files/file1.json"; + + attachment.setUsageType("http://example.com/attachment"); + attachment.setDisplay(display); + attachment.setDescription(desc); + attachment.setContentType(contentType); + attachment.setLength(length); + attachment.setSha2(sha2); + attachment.setFileUrl(fileUrl); + assertTrue(validator.validate(attachment).isEmpty()); + } + + @Test + public void testEmptyAttachment() { + assertEquals(5, validator.validate(attachment).size()); + } +} From 1007b0731ea45314ff2ed2da5b092e03ac4ad60b Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 14:18:54 -0500 Subject: [PATCH 16/28] Add StatementRef constraints --- .../yetanalytics/xapi/model/StatementRef.java | 5 ++- .../yetanalytics/model/StatementRefTest.java | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/yetanalytics/model/StatementRefTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java index 2a4e19e..477a7f5 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java @@ -2,9 +2,11 @@ import java.util.UUID; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import jakarta.validation.constraints.NotNull; /** * Class representation of the StatementRef component of the @@ -13,6 +15,7 @@ @JsonInclude(Include.NON_NULL) @JsonDeserialize public class StatementRef extends AbstractObject { + @NotNull private UUID id; public UUID getId() { diff --git a/src/test/java/com/yetanalytics/model/StatementRefTest.java b/src/test/java/com/yetanalytics/model/StatementRefTest.java new file mode 100644 index 0000000..4584ae7 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/StatementRefTest.java @@ -0,0 +1,37 @@ +package com.yetanalytics.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.StatementRef; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class StatementRefTest { + private Validator validator; + private StatementRef statementRef; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + statementRef = new StatementRef(); + } + + @Test + public void testStatementRef() { + String id = "00000000-4000-8000-0000-000000000000"; + statementRef.setId(UUID.fromString(id)); + assertTrue(validator.validate(statementRef).isEmpty()); + } + + @Test + public void testEmptyStatementRef() { + assertEquals(1, validator.validate(statementRef).size()); + } +} From 72445b6f141df2a471055afb24e2f378a55408b8 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 15:22:32 -0500 Subject: [PATCH 17/28] Implement constraints for Statements (incl Authority checks) --- .../com/yetanalytics/xapi/model/Agent.java | 4 + .../com/yetanalytics/xapi/model/Group.java | 14 ++ .../yetanalytics/xapi/model/Statement.java | 81 +++++++- .../com/yetanalytics/model/StatementTest.java | 192 ++++++++++++++++++ 4 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/yetanalytics/model/StatementTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Agent.java b/src/main/java/com/yetanalytics/xapi/model/Agent.java index cc9e8d0..a234efb 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Agent.java +++ b/src/main/java/com/yetanalytics/xapi/model/Agent.java @@ -26,4 +26,8 @@ public boolean isIdentifiedAgent() { return countIFIs() == 1; } + @Override + public boolean isValidAuthority() { + return true; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Group.java b/src/main/java/com/yetanalytics/xapi/model/Group.java index cc05c29..333476e 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Group.java +++ b/src/main/java/com/yetanalytics/xapi/model/Group.java @@ -34,4 +34,18 @@ public boolean isAnonymousOrIdentifiedGroup() { ); } + private boolean isValidConsumer(Agent consumer) { + return consumer.getAccount() != null; + } + + @Override + public boolean isValidAuthority() { + return ( + member.size() == 2 && + ( + isValidConsumer(member.get(0)) || + isValidConsumer(member.get(1)) + ) + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Statement.java b/src/main/java/com/yetanalytics/xapi/model/Statement.java index 965d9f5..7031777 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Statement.java +++ b/src/main/java/com/yetanalytics/xapi/model/Statement.java @@ -1,15 +1,18 @@ package com.yetanalytics.xapi.model; +import java.time.ZonedDateTime; import java.util.List; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.yetanalytics.xapi.model.serializers.DateTimeSerializer; -import java.time.ZonedDateTime; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; /** * Class representation of an Statement from the 9274.1.1 xAPI Specification. @@ -20,16 +23,25 @@ public class Statement extends AbstractObject { private UUID id; + @NotNull + @Valid private AbstractObject actor; + @NotNull + @Valid private Verb verb; + @Valid private Result result; + @Valid private Context context; + @NotNull + @Valid private AbstractObject object; + @Valid private AbstractActor authority; @JsonSerialize(using = DateTimeSerializer.class) @@ -129,4 +141,67 @@ public List getAttachments() { public void setAttachments(List attachments) { this.attachments = attachments; } + + // Validation + + @AssertTrue + public boolean isValidVoidingStatement() { + if (verb != null && verb.isVoiding()) { + return object instanceof StatementRef; + } else { + return true; + } + } + + private boolean isObjectActivity() { + if (object != null) { + return object instanceof Activity; + } else { + return false; // Invalid statement anyways + } + } + + @AssertTrue + public boolean isValidContextRevision() { + return ( + isObjectActivity() || + context == null || + context.getRevision() == null + ); + } + + @AssertTrue + public boolean isValidContextPlatform() { + return ( + isObjectActivity() || + context == null || + context.getPlatform() == null + ); + } + + // TODO: Somehow validate this on the Authority object itself + @AssertTrue + public boolean isValidAuthority() { + return authority == null || authority.isValidAuthority(); + } + + // TODO: Validate this on the SubStatement itself + // (e.g. setting the objectType field) + @AssertTrue + public boolean isValidSubStatement() { + // System.out.println("Object is Statement: " + (object instanceof Statement)); + // TODO: If object is true... + if (object instanceof Statement) { + Statement subStatement = (Statement) object; + return ( + subStatement.getId() == null && + subStatement.getStored() == null && + subStatement.getVersion() == null && + subStatement.getAuthority() == null && + !(subStatement.getObject() instanceof Statement) + ); + } else { + return true; + } + } } diff --git a/src/test/java/com/yetanalytics/model/StatementTest.java b/src/test/java/com/yetanalytics/model/StatementTest.java new file mode 100644 index 0000000..780d101 --- /dev/null +++ b/src/test/java/com/yetanalytics/model/StatementTest.java @@ -0,0 +1,192 @@ +package com.yetanalytics.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Account; +import com.yetanalytics.xapi.model.Activity; +import com.yetanalytics.xapi.model.Agent; +import com.yetanalytics.xapi.model.Context; +import com.yetanalytics.xapi.model.Group; +import com.yetanalytics.xapi.model.Statement; +import com.yetanalytics.xapi.model.StatementRef; +import com.yetanalytics.xapi.model.Verb; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class StatementTest { + private Validator validator; + private Statement statement; + + private StatementRef genericStatementRef() { + UUID id = UUID.fromString("00000000-4000-8000-0000-000000000000"); + StatementRef statementRef = new StatementRef(); + statementRef.setId(id); + return statementRef; + } + + @Before + public void initStatement() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + statement = new Statement(); + + // Valid statement by default + Agent actor = new Agent(); + actor.setMbox("mailto:foo@example.com"); + + Verb verb = new Verb(); + verb.setId("http://example.org/verb"); + + Activity object = new Activity(); + object.setId("http://example.org/object"); + + statement.setActor(actor); + statement.setVerb(verb); + statement.setObject(object); + } + + @Test + public void testStatement() { + assertTrue(validator.validate(statement).isEmpty()); + } + + @Test + public void testEmptyStatement() { + statement.setActor(null); + statement.setVerb(null); + statement.setObject(null); + assertEquals(3, validator.validate(statement).size()); + } + + @Test + public void testVoidingStatement() { + Verb voidingVerb = new Verb(); + voidingVerb.setId(Verb.VOIDING_VERB_IRI); + + statement.setVerb(voidingVerb); + assertEquals(1, validator.validate(statement).size()); + + StatementRef statementRef = genericStatementRef(); + statement.setObject(statementRef); + + assertTrue(validator.validate(statement).isEmpty()); + } + + @Test + public void testValidContextRevision() { + Context context = new Context(); + context.setRevision("myRevision"); + statement.setContext(context); + assertTrue(validator.validate(statement).isEmpty()); + + StatementRef statementRef = genericStatementRef(); + statement.setObject(statementRef); + + assertEquals(1, validator.validate(statement).size()); + } + + @Test + public void testValidContextPlatform() { + Context context = new Context(); + context.setRevision("myPlatform"); + statement.setContext(context); + assertTrue(validator.validate(statement).isEmpty()); + + StatementRef statementRef = genericStatementRef(); + statement.setObject(statementRef); + + assertEquals(1, validator.validate(statement).size()); + } + + @Test + public void testValidAuthority() { + Account authAccount = new Account(); + authAccount.setHomePage("http://myauthority.com"); + authAccount.setName("My Authority"); + + // Agent authority is always valid + Agent agentAuthority = new Agent(); + agentAuthority.setAccount(authAccount); + statement.setAuthority(agentAuthority); + assertTrue(validator.validate(statement).isEmpty()); + + // Group authority fails if there is not two members + Group groupAuthority = new Group(); + List groupAuthMember = new ArrayList<>(); + groupAuthMember.add(agentAuthority); + groupAuthority.setMember(groupAuthMember); + statement.setAuthority(groupAuthority); + assertEquals(1, validator.validate(statement).size()); + + // Need to add second member + Agent nonConsumer = new Agent(); + nonConsumer.setMbox("mailto:someagent@example.com"); + groupAuthMember.add(nonConsumer); + assertTrue(validator.validate(statement).isEmpty()); + + // At least one member needs to have an Account + groupAuthMember.set(0, nonConsumer); + assertEquals(1, validator.validate(statement).size()); + + // Cannot have three (or more) members + groupAuthMember.set(0, agentAuthority); + groupAuthMember.add(nonConsumer); + assertEquals(1, validator.validate(statement).size()); + } + + @Test + public void testValidSubStatement() { + Statement subStatement = new Statement(); + Agent actor = new Agent(); + actor.setMbox("mailto:bar@example.com"); + Verb verb = new Verb(); + verb.setId("http://example.org/verb2"); + Activity object = new Activity(); + object.setId("http://example.org/object2"); + subStatement.setActor(actor); + subStatement.setVerb(verb); + subStatement.setObject(object); + statement.setObject(subStatement); + assertTrue(statement.getObject() instanceof Statement); + assertTrue(validator.validate(statement).isEmpty()); + + UUID id = UUID.fromString("00000000-4000-8000-0000-000000000000"); + subStatement.setId(id); + assertEquals(1, validator.validate(statement).size()); + + // TODO: test Stored presence + + String version = "1.0.3"; + subStatement.setId(null); + subStatement.setVersion(version); + assertEquals(1, validator.validate(statement).size()); + + Agent authority = new Agent(); + authority.setMbox("mailto:myauthority@example.com"); + subStatement.setVersion(null); + subStatement.setAuthority(authority); + assertEquals(1, validator.validate(statement).size()); + + Statement subSubStatement = new Statement(); + Agent actor2 = new Agent(); + actor2.setMbox("mailto:baz@example.com"); + Verb verb2 = new Verb(); + verb2.setId("http://example.org/verb3"); + Activity object2 = new Activity(); + object2.setId("http://example.org/object3"); + subSubStatement.setActor(actor); + subSubStatement.setVerb(verb); + subSubStatement.setObject(object); + subStatement.setAuthority(null); + subStatement.setObject(subStatement); + // TODO: Dig deeper why this is 2 errors and not 1 + assertEquals(2, validator.validate(statement).size()); + } +} From 9074e731d203852781a96c2ef29488197410ad07 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Fri, 6 Dec 2024 15:24:25 -0500 Subject: [PATCH 18/28] Add StatementResult --- .../xapi/model/StatementResult.java | 3 + .../model/StatementResultTest.java | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/test/java/com/yetanalytics/model/StatementResultTest.java diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java index b77ac05..6272807 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java @@ -5,12 +5,15 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.NotNull; + /** * Class representation of a StatementResult from the 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) public class StatementResult { + @NotNull private List statements; private String more; diff --git a/src/test/java/com/yetanalytics/model/StatementResultTest.java b/src/test/java/com/yetanalytics/model/StatementResultTest.java new file mode 100644 index 0000000..b9a9d0f --- /dev/null +++ b/src/test/java/com/yetanalytics/model/StatementResultTest.java @@ -0,0 +1,57 @@ +package com.yetanalytics.model; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +import com.yetanalytics.xapi.model.Activity; +import com.yetanalytics.xapi.model.Agent; +import com.yetanalytics.xapi.model.Statement; +import com.yetanalytics.xapi.model.StatementResult; +import com.yetanalytics.xapi.model.Verb; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class StatementResultTest { + private Validator validator; + private StatementResult statementResult; + + @Before + public void initValidator() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + statementResult = new StatementResult(); + } + + @Test + public void testStatementResult() { + Agent actor = new Agent(); + actor.setMbox("mailto:foo@example.com"); + + Verb verb = new Verb(); + verb.setId("http://example.org/verb"); + + Activity object = new Activity(); + object.setId("http://example.org/object"); + + Statement statement = new Statement(); + statement.setActor(actor); + statement.setVerb(verb); + statement.setObject(object); + + List statements = new ArrayList<>(); + statements.add(statement); + statementResult.setStatements(statements); + + assertTrue(validator.validate(statements).isEmpty()); + } + + @Test + public void testEmptyStatementResult() { + assertEquals(1, validator.validate(statementResult).size()); + } +} From 91d5ccd9828841d50e5ae27cf45c2ae7e1560f5f Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Mon, 9 Dec 2024 10:18:55 -0500 Subject: [PATCH 19/28] Add ValidationUtils class with custom asserts --- .../com/yetanalytics/model/AccountTest.java | 14 ++---- .../model/ActivityDefinitionTest.java | 48 ++++++++----------- .../com/yetanalytics/model/ActivityTest.java | 15 +++--- .../com/yetanalytics/model/AgentTest.java | 25 ++++------ .../yetanalytics/model/AttachmentTest.java | 13 +++-- .../com/yetanalytics/model/GroupTest.java | 28 +++++------ .../model/InteractionComponentTest.java | 12 ++--- .../com/yetanalytics/model/ResultTest.java | 13 ++--- .../com/yetanalytics/model/ScoreTest.java | 27 ++++------- .../yetanalytics/model/StatementRefTest.java | 13 ++--- .../model/StatementResultTest.java | 12 ++--- .../com/yetanalytics/model/StatementTest.java | 34 ++++++------- .../java/com/yetanalytics/model/VerbTest.java | 13 ++--- .../yetanalytics/util/ValidationUtils.java | 26 ++++++++++ 14 files changed, 137 insertions(+), 156 deletions(-) create mode 100644 src/test/java/com/yetanalytics/util/ValidationUtils.java diff --git a/src/test/java/com/yetanalytics/model/AccountTest.java b/src/test/java/com/yetanalytics/model/AccountTest.java index 845c077..fa54cb3 100644 --- a/src/test/java/com/yetanalytics/model/AccountTest.java +++ b/src/test/java/com/yetanalytics/model/AccountTest.java @@ -1,13 +1,11 @@ package com.yetanalytics.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Account; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class AccountTest { @@ -15,8 +13,8 @@ public class AccountTest { private Account account; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); account = new Account(); } @@ -24,13 +22,11 @@ public void initValidator() { public void testValidAccount() { account.setHomePage("http://examplehomepage.com"); account.setName("My Account"); - assertTrue(validator.validate(account).isEmpty()); + ValidationUtils.assertValid(validator, account); } @Test public void testEmptyAccount() { - var violations = validator.validate(account); - assertEquals(2, violations.size()); + ValidationUtils.assertInvalid(validator, account, 2); } - } diff --git a/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java b/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java index 164e194..19ab497 100644 --- a/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java +++ b/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java @@ -4,18 +4,17 @@ import java.util.HashMap; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.ActivityDefinition; import com.yetanalytics.xapi.model.Extensions; import com.yetanalytics.xapi.model.InteractionComponent; import com.yetanalytics.xapi.model.InteractionType; import com.yetanalytics.xapi.model.LangMap; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class ActivityDefinitionTest { @@ -37,14 +36,14 @@ private List makeComp(String id, String descStr) { } @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); definition = new ActivityDefinition(); } @Test public void testEmptyDefinition() { - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); } @Test @@ -68,6 +67,7 @@ public void testDefinition() { definition.setMoreInfo(moreInfo); definition.setExtensions(ext); + ValidationUtils.assertValid(validator, definition); assertTrue(validator.validate(definition).isEmpty()); List choices = makeComp("component", "Interaction Component"); @@ -78,79 +78,73 @@ public void testDefinition() { correctResponsesPattern.add("Response 2"); definition.setCorrectResponsesPattern(correctResponsesPattern); - var violations = validator.validate(definition); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, definition); } @Test public void testChoiceDefinition() { definition.setInteractionType(InteractionType.CHOICE); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); List choices = makeComp("choice", "Choice"); definition.setChoices(choices); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); definition.setInteractionType(InteractionType.TRUE_FALSE); - var violations = validator.validate(definition); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, definition); } @Test public void testSequencingDefinition() { definition.setInteractionType(InteractionType.SEQUENCING); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); List choices = makeComp("choice", "Choice"); definition.setChoices(choices); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); definition.setInteractionType(InteractionType.FILL_IN); - var violations = validator.validate(definition); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, definition); } @Test public void testLikertDefinition() { definition.setInteractionType(InteractionType.LIKERT); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); List scale = makeComp("scale", "Scale"); definition.setScale(scale); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); definition.setInteractionType(InteractionType.LONG_FILL_IN); - var violations = validator.validate(definition); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, definition); } @Test public void testMatchingDefinition() { definition.setInteractionType(InteractionType.MATCHING); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); List source = makeComp("source", "Source"); List target = makeComp("target", "Target"); definition.setSource(source); definition.setTarget(target); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); definition.setInteractionType(InteractionType.NUMERIC); - var violations = validator.validate(definition); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, definition); } @Test public void testPerformanceDefinition() { definition.setInteractionType(InteractionType.PERFORMANCE); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); List steps = makeComp("steps", "Steps"); definition.setSteps(steps); - assertTrue(validator.validate(definition).isEmpty()); + ValidationUtils.assertValid(validator, definition); definition.setInteractionType(InteractionType.OTHER); - var violations = validator.validate(definition); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, definition); } } diff --git a/src/test/java/com/yetanalytics/model/ActivityTest.java b/src/test/java/com/yetanalytics/model/ActivityTest.java index dcf96a3..32c8082 100644 --- a/src/test/java/com/yetanalytics/model/ActivityTest.java +++ b/src/test/java/com/yetanalytics/model/ActivityTest.java @@ -1,14 +1,12 @@ package com.yetanalytics.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Activity; import com.yetanalytics.xapi.model.ActivityDefinition; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class ActivityTest { @@ -17,8 +15,8 @@ public class ActivityTest { private Activity activity; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); definition = new ActivityDefinition(); activity = new Activity(); } @@ -26,17 +24,16 @@ public void initValidator() { @Test public void testActivity() { activity.setId("http://example.org/activity"); - assertTrue(validator.validate(activity).isEmpty()); + ValidationUtils.assertValid(validator, activity); // TODO: Enforce that definitions, as JSON objects, can't be empty // For now we allow them though activity.setDefinition(definition); - assertTrue(validator.validate(activity).isEmpty()); + ValidationUtils.assertValid(validator, activity); } @Test public void testEmptyActivity() { - var violations = validator.validate(activity); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, activity); } } diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java index eecf35a..17bfd38 100644 --- a/src/test/java/com/yetanalytics/model/AgentTest.java +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -1,14 +1,12 @@ package com.yetanalytics.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Account; import com.yetanalytics.xapi.model.Agent; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class AgentTest { @@ -16,27 +14,27 @@ public class AgentTest { private Agent agent; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); agent = new Agent(); } @Test public void testMbox() { agent.setMbox("mailto:foo@example.com"); - assertTrue(validator.validate(agent).isEmpty()); + ValidationUtils.assertValid(validator, agent); } @Test public void testMboxSha1Sum() { agent.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); - assertTrue(validator.validate(agent).isEmpty()); + ValidationUtils.assertValid(validator, agent); } @Test public void testOpenid() { agent.setOpenid("http://openid.example.com"); - assertTrue(validator.validate(agent).isEmpty()); + ValidationUtils.assertValid(validator, agent); } @Test @@ -46,28 +44,25 @@ public void testAccount() { account.setName("My Account"); agent.setAccount(account); - assertTrue(validator.validate(agent).isEmpty()); + ValidationUtils.assertValid(validator, agent); } @Test public void testInvalidAccount() { Account account = new Account(); agent.setAccount(account); - var violations = validator.validate(agent); - assertEquals(2, violations.size()); + ValidationUtils.assertInvalid(validator, agent, 2); } @Test public void testNoIFI() { - var violations = validator.validate(agent); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, agent); } @Test public void testMultiIFI() { agent.setMbox("mailto:foo@example.com"); agent.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); - var violations = validator.validate(agent); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, agent); } } diff --git a/src/test/java/com/yetanalytics/model/AttachmentTest.java b/src/test/java/com/yetanalytics/model/AttachmentTest.java index c82f40b..fe71f99 100644 --- a/src/test/java/com/yetanalytics/model/AttachmentTest.java +++ b/src/test/java/com/yetanalytics/model/AttachmentTest.java @@ -2,15 +2,13 @@ import java.util.HashMap; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Attachment; import com.yetanalytics.xapi.model.LangMap; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class AttachmentTest { @@ -18,8 +16,8 @@ public class AttachmentTest { private Attachment attachment; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); attachment = new Attachment(); } @@ -43,11 +41,12 @@ public void testAttachment() { attachment.setLength(length); attachment.setSha2(sha2); attachment.setFileUrl(fileUrl); - assertTrue(validator.validate(attachment).isEmpty()); + + ValidationUtils.assertValid(validator, attachment); } @Test public void testEmptyAttachment() { - assertEquals(5, validator.validate(attachment).size()); + ValidationUtils.assertInvalid(validator, attachment, 5); } } diff --git a/src/test/java/com/yetanalytics/model/GroupTest.java b/src/test/java/com/yetanalytics/model/GroupTest.java index 8ebbce8..691daf4 100644 --- a/src/test/java/com/yetanalytics/model/GroupTest.java +++ b/src/test/java/com/yetanalytics/model/GroupTest.java @@ -3,16 +3,14 @@ import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Account; import com.yetanalytics.xapi.model.Agent; import com.yetanalytics.xapi.model.Group; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class GroupTest { @@ -20,8 +18,8 @@ public class GroupTest { private Group group; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); group = new Group(); } @@ -32,25 +30,25 @@ public void testAnomyousGroup() { memberAgent.setMbox("mailto:mem@example.com"); member.add(memberAgent); group.setMember(member); - assertTrue(validator.validate(group).isEmpty()); + ValidationUtils.assertValid(validator, group); } @Test public void testMbox() { group.setMbox("mailto:foo@example.com"); - assertTrue(validator.validate(group).isEmpty()); + ValidationUtils.assertValid(validator, group); } @Test public void testMboxSha1Sum() { group.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); - assertTrue(validator.validate(group).isEmpty()); + ValidationUtils.assertValid(validator, group); } @Test public void testOpenid() { group.setOpenid("http://openid.example.com"); - assertTrue(validator.validate(group).isEmpty()); + ValidationUtils.assertValid(validator, group); } @Test @@ -60,29 +58,25 @@ public void testAccount() { account.setName("My Account"); group.setAccount(account); - assertTrue(validator.validate(group).isEmpty()); + ValidationUtils.assertValid(validator, group); } @Test public void testInvalidAccount() { Account account = new Account(); group.setAccount(account); - var violations = validator.validate(group); - assertEquals(2, violations.size()); + ValidationUtils.assertInvalid(validator, group, 2); } @Test public void testNoIFI() { // No member array => identified group - var violations = validator.validate(group); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, group); } @Test public void testMultiIFI() { group.setMbox("mailto:foo@example.com"); group.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); - var violations = validator.validate(group); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, group); } - } diff --git a/src/test/java/com/yetanalytics/model/InteractionComponentTest.java b/src/test/java/com/yetanalytics/model/InteractionComponentTest.java index 8149fed..4183b5b 100644 --- a/src/test/java/com/yetanalytics/model/InteractionComponentTest.java +++ b/src/test/java/com/yetanalytics/model/InteractionComponentTest.java @@ -2,15 +2,13 @@ import java.util.HashMap; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.InteractionComponent; import com.yetanalytics.xapi.model.LangMap; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class InteractionComponentTest { @@ -18,8 +16,8 @@ public class InteractionComponentTest { private InteractionComponent interactionComponent; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); interactionComponent = new InteractionComponent(); } @@ -29,11 +27,11 @@ public void testInteractionComponent() { desc.put("en-US", "Foo"); interactionComponent.setId("foo"); interactionComponent.setDescription(desc); - assertTrue(validator.validate(interactionComponent).isEmpty()); + ValidationUtils.assertValid(validator, interactionComponent); } @Test public void testEmpytInteractionComponent() { - assertEquals(1, validator.validate(interactionComponent).size()); + ValidationUtils.assertInvalid(validator, interactionComponent); } } diff --git a/src/test/java/com/yetanalytics/model/ResultTest.java b/src/test/java/com/yetanalytics/model/ResultTest.java index c5f0c94..ea2dbd8 100644 --- a/src/test/java/com/yetanalytics/model/ResultTest.java +++ b/src/test/java/com/yetanalytics/model/ResultTest.java @@ -2,15 +2,13 @@ import java.math.BigDecimal; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Result; import com.yetanalytics.xapi.model.Score; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class ResultTest { @@ -19,8 +17,8 @@ public class ResultTest { private Score score; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); score = new Score(); result = new Result(); } @@ -35,14 +33,13 @@ public void testResult() { // TODO: setExtensions // TODO: setDuration - assertTrue(validator.validate(result).isEmpty()); + ValidationUtils.assertValid(validator, result); } @Test public void testInvalidScore() { score.setScaled(new BigDecimal(3.0)); result.setScore(score); - assertEquals(1, validator.validate(result).size()); + ValidationUtils.assertInvalid(validator, result); } - } diff --git a/src/test/java/com/yetanalytics/model/ScoreTest.java b/src/test/java/com/yetanalytics/model/ScoreTest.java index 66c85f1..a6e856f 100644 --- a/src/test/java/com/yetanalytics/model/ScoreTest.java +++ b/src/test/java/com/yetanalytics/model/ScoreTest.java @@ -2,14 +2,12 @@ import java.math.BigDecimal; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Score; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class ScoreTest { @@ -17,57 +15,52 @@ public class ScoreTest { private Score score; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); score = new Score(); } @Test public void testScore() { - assertTrue(validator.validate(score).isEmpty()); + ValidationUtils.assertValid(validator, score); // TODO: Make invalid score.setMin(new BigDecimal(0)); score.setMax(new BigDecimal(100)); score.setRaw(new BigDecimal(50)); score.setScaled(new BigDecimal(0)); - assertTrue(validator.validate(score).isEmpty()); + ValidationUtils.assertValid(validator, score); } @Test public void testScaledTooSmall() { score.setScaled(new BigDecimal(-2.0)); - var violations = validator.validate(score); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, score); } @Test public void testScaledTooBig() { score.setScaled(new BigDecimal(2.0)); - var violations = validator.validate(score); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, score); } @Test public void testMinBiggerThanMax() { score.setMax(new BigDecimal(0.4)); score.setMin(new BigDecimal(0.6)); - var violations = validator.validate(score); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, score); } @Test public void testRawBiggerThanMax() { score.setMax(new BigDecimal(0.4)); score.setRaw(new BigDecimal(0.6)); - var violations = validator.validate(score); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, score); } @Test public void testMinBiggerThanRaw() { score.setRaw(new BigDecimal(0.4)); score.setMin(new BigDecimal(0.6)); - var violations = validator.validate(score); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, score); } } diff --git a/src/test/java/com/yetanalytics/model/StatementRefTest.java b/src/test/java/com/yetanalytics/model/StatementRefTest.java index 4584ae7..c20fd05 100644 --- a/src/test/java/com/yetanalytics/model/StatementRefTest.java +++ b/src/test/java/com/yetanalytics/model/StatementRefTest.java @@ -1,16 +1,13 @@ package com.yetanalytics.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.util.UUID; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.StatementRef; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class StatementRefTest { @@ -18,8 +15,8 @@ public class StatementRefTest { private StatementRef statementRef; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); statementRef = new StatementRef(); } @@ -27,11 +24,11 @@ public void initValidator() { public void testStatementRef() { String id = "00000000-4000-8000-0000-000000000000"; statementRef.setId(UUID.fromString(id)); - assertTrue(validator.validate(statementRef).isEmpty()); + ValidationUtils.assertValid(validator, statementRef); } @Test public void testEmptyStatementRef() { - assertEquals(1, validator.validate(statementRef).size()); + ValidationUtils.assertInvalid(validator, statementRef); } } diff --git a/src/test/java/com/yetanalytics/model/StatementResultTest.java b/src/test/java/com/yetanalytics/model/StatementResultTest.java index b9a9d0f..f39a3d6 100644 --- a/src/test/java/com/yetanalytics/model/StatementResultTest.java +++ b/src/test/java/com/yetanalytics/model/StatementResultTest.java @@ -3,18 +3,16 @@ import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Activity; import com.yetanalytics.xapi.model.Agent; import com.yetanalytics.xapi.model.Statement; import com.yetanalytics.xapi.model.StatementResult; import com.yetanalytics.xapi.model.Verb; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class StatementResultTest { @@ -22,8 +20,8 @@ public class StatementResultTest { private StatementResult statementResult; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); statementResult = new StatementResult(); } @@ -47,11 +45,11 @@ public void testStatementResult() { statements.add(statement); statementResult.setStatements(statements); - assertTrue(validator.validate(statements).isEmpty()); + ValidationUtils.assertValid(validator, statementResult); } @Test public void testEmptyStatementResult() { - assertEquals(1, validator.validate(statementResult).size()); + ValidationUtils.assertInvalid(validator, statementResult); } } diff --git a/src/test/java/com/yetanalytics/model/StatementTest.java b/src/test/java/com/yetanalytics/model/StatementTest.java index 780d101..33445e2 100644 --- a/src/test/java/com/yetanalytics/model/StatementTest.java +++ b/src/test/java/com/yetanalytics/model/StatementTest.java @@ -4,11 +4,11 @@ import java.util.List; import java.util.UUID; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.Account; import com.yetanalytics.xapi.model.Activity; import com.yetanalytics.xapi.model.Agent; @@ -18,7 +18,6 @@ import com.yetanalytics.xapi.model.StatementRef; import com.yetanalytics.xapi.model.Verb; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class StatementTest { @@ -33,8 +32,8 @@ private StatementRef genericStatementRef() { } @Before - public void initStatement() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); statement = new Statement(); // Valid statement by default @@ -54,7 +53,7 @@ public void initStatement() { @Test public void testStatement() { - assertTrue(validator.validate(statement).isEmpty()); + ValidationUtils.assertValid(validator, statement); } @Test @@ -62,7 +61,7 @@ public void testEmptyStatement() { statement.setActor(null); statement.setVerb(null); statement.setObject(null); - assertEquals(3, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement, 3); } @Test @@ -71,12 +70,12 @@ public void testVoidingStatement() { voidingVerb.setId(Verb.VOIDING_VERB_IRI); statement.setVerb(voidingVerb); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); StatementRef statementRef = genericStatementRef(); statement.setObject(statementRef); - assertTrue(validator.validate(statement).isEmpty()); + ValidationUtils.assertValid(validator, statement); } @Test @@ -84,12 +83,13 @@ public void testValidContextRevision() { Context context = new Context(); context.setRevision("myRevision"); statement.setContext(context); + ValidationUtils.assertValid(validator, statement); assertTrue(validator.validate(statement).isEmpty()); StatementRef statementRef = genericStatementRef(); statement.setObject(statementRef); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); } @Test @@ -102,7 +102,7 @@ public void testValidContextPlatform() { StatementRef statementRef = genericStatementRef(); statement.setObject(statementRef); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); } @Test @@ -123,7 +123,7 @@ public void testValidAuthority() { groupAuthMember.add(agentAuthority); groupAuthority.setMember(groupAuthMember); statement.setAuthority(groupAuthority); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); // Need to add second member Agent nonConsumer = new Agent(); @@ -133,12 +133,12 @@ public void testValidAuthority() { // At least one member needs to have an Account groupAuthMember.set(0, nonConsumer); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); // Cannot have three (or more) members groupAuthMember.set(0, agentAuthority); groupAuthMember.add(nonConsumer); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); } @Test @@ -159,20 +159,20 @@ public void testValidSubStatement() { UUID id = UUID.fromString("00000000-4000-8000-0000-000000000000"); subStatement.setId(id); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); // TODO: test Stored presence String version = "1.0.3"; subStatement.setId(null); subStatement.setVersion(version); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); Agent authority = new Agent(); authority.setMbox("mailto:myauthority@example.com"); subStatement.setVersion(null); subStatement.setAuthority(authority); - assertEquals(1, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement); Statement subSubStatement = new Statement(); Agent actor2 = new Agent(); @@ -187,6 +187,6 @@ public void testValidSubStatement() { subStatement.setAuthority(null); subStatement.setObject(subStatement); // TODO: Dig deeper why this is 2 errors and not 1 - assertEquals(2, validator.validate(statement).size()); + ValidationUtils.assertInvalid(validator, statement, 2); } } diff --git a/src/test/java/com/yetanalytics/model/VerbTest.java b/src/test/java/com/yetanalytics/model/VerbTest.java index 7bac522..d8920ca 100644 --- a/src/test/java/com/yetanalytics/model/VerbTest.java +++ b/src/test/java/com/yetanalytics/model/VerbTest.java @@ -2,15 +2,13 @@ import java.util.HashMap; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import com.yetanalytics.util.ValidationUtils; import com.yetanalytics.xapi.model.LangMap; import com.yetanalytics.xapi.model.Verb; -import jakarta.validation.Validation; import jakarta.validation.Validator; public class VerbTest { @@ -18,8 +16,8 @@ public class VerbTest { private Verb verb; @Before - public void initValidator() { - validator = Validation.buildDefaultValidatorFactory().getValidator(); + public void init() { + validator = ValidationUtils.getValidator(); verb = new Verb(); } @@ -30,12 +28,11 @@ public void testVerb() { verb.setId("http://example.com/verb"); verb.setDisplay(display); - assertTrue(validator.validate(verb).isEmpty()); + ValidationUtils.assertValid(validator, verb); } @Test public void testEmptyVerb() { - var violations = validator.validate(verb); - assertEquals(1, violations.size()); + ValidationUtils.assertInvalid(validator, verb); } } diff --git a/src/test/java/com/yetanalytics/util/ValidationUtils.java b/src/test/java/com/yetanalytics/util/ValidationUtils.java new file mode 100644 index 0000000..e3f7b5f --- /dev/null +++ b/src/test/java/com/yetanalytics/util/ValidationUtils.java @@ -0,0 +1,26 @@ +package com.yetanalytics.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class ValidationUtils { + + public static Validator getValidator() { + return Validation.buildDefaultValidatorFactory().getValidator(); + } + + public static void assertValid(Validator validator, Object object) { + assertTrue(validator.validate(object).isEmpty()); + } + + public static void assertInvalid(Validator validator, Object object) { + assertInvalid(validator, object, 1); + } + + public static void assertInvalid(Validator validator, Object object, int numErrors) { + assertEquals(numErrors, validator.validate(object).size()); + } +} From 131c3804b2986107767f51bf003b75de5c976e79 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Mon, 9 Dec 2024 17:18:21 -0500 Subject: [PATCH 20/28] Add isEmpty function to validate that JSON objects are not empty --- .../xapi/model/AbstractActor.java | 8 ++++++++ .../xapi/model/AbstractObject.java | 4 +++- .../com/yetanalytics/xapi/model/Account.java | 9 ++++++++- .../com/yetanalytics/xapi/model/Activity.java | 7 +++++++ .../xapi/model/ActivityDefinition.java | 13 +++++++++++- .../com/yetanalytics/xapi/model/Agent.java | 7 +++++++ .../yetanalytics/xapi/model/Attachment.java | 17 ++++++++++++++-- .../com/yetanalytics/xapi/model/Context.java | 20 ++++++++++++++++++- .../xapi/model/ContextActivities.java | 13 +++++++++++- .../yetanalytics/xapi/model/Extensions.java | 10 +++++++++- .../com/yetanalytics/xapi/model/Group.java | 8 ++++++++ .../xapi/model/InteractionComponent.java | 9 +++++++-- .../yetanalytics/xapi/model/JSONObject.java | 5 +++++ .../com/yetanalytics/xapi/model/LangMap.java | 10 +++++++++- .../com/yetanalytics/xapi/model/Result.java | 16 ++++++++++++++- .../com/yetanalytics/xapi/model/Score.java | 9 ++++++++- .../yetanalytics/xapi/model/Statement.java | 19 ++++++++++++++++++ .../yetanalytics/xapi/model/StatementRef.java | 7 +++++++ .../xapi/model/StatementResult.java | 9 ++++++++- .../com/yetanalytics/xapi/model/Verb.java | 10 ++++++++-- .../yetanalytics/XapiSerializationTest.java | 5 +++-- .../com/yetanalytics/model/AccountTest.java | 2 +- .../model/ActivityDefinitionTest.java | 2 +- .../com/yetanalytics/model/ActivityTest.java | 6 +++--- .../com/yetanalytics/model/AgentTest.java | 6 ++++-- .../yetanalytics/model/AttachmentTest.java | 3 ++- .../com/yetanalytics/model/GroupTest.java | 6 ++++-- .../model/InteractionComponentTest.java | 3 ++- .../com/yetanalytics/model/ScoreTest.java | 7 +++++-- .../yetanalytics/model/StatementRefTest.java | 3 ++- .../model/StatementResultTest.java | 3 ++- .../com/yetanalytics/model/StatementTest.java | 3 ++- .../java/com/yetanalytics/model/VerbTest.java | 3 ++- 33 files changed, 227 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/yetanalytics/xapi/model/JSONObject.java diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java index 7e96a6b..0f18e1d 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java @@ -79,4 +79,12 @@ protected int countIFIs() { } public abstract boolean isValidAuthority(); + + @Override + public boolean isEmpty() { + return ( + mbox == null && mbox_sha1sum == null && + openid == null && account == null + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java b/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java index 068a861..69ebebb 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java @@ -7,7 +7,7 @@ * Abstract Class for serialization and deserialization of xAPI Objects */ @JsonDeserialize(using = AbstractObjectDeserializer.class) -public class AbstractObject { +public abstract class AbstractObject implements JSONObject { private ObjectType objectType; @@ -18,4 +18,6 @@ public void setObjectType(ObjectType objectType) { this.objectType = objectType; } + @Override + public abstract boolean isEmpty(); } diff --git a/src/main/java/com/yetanalytics/xapi/model/Account.java b/src/main/java/com/yetanalytics/xapi/model/Account.java index 5470ad3..eff18d8 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Account.java +++ b/src/main/java/com/yetanalytics/xapi/model/Account.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** @@ -10,7 +11,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class Account { +public class Account implements JSONObject { @NotNull private String homePage; @@ -30,4 +31,10 @@ public String getName() { public void setName(String name) { this.name = name; } + + @Override + @AssertFalse + public boolean isEmpty() { + return homePage == null && name == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Activity.java b/src/main/java/com/yetanalytics/xapi/model/Activity.java index b730b25..4385d96 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Activity.java +++ b/src/main/java/com/yetanalytics/xapi/model/Activity.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** @@ -33,4 +34,10 @@ public ActivityDefinition getDefinition() { public void setDefinition(ActivityDefinition definition) { this.definition = definition; } + + @Override + @AssertFalse + public boolean isEmpty() { + return id == null && definition == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java index b83e473..5439f8d 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java +++ b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.AssertTrue; /** @@ -12,7 +13,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class ActivityDefinition { +public class ActivityDefinition implements JSONObject { private LangMap name; private LangMap description; @@ -162,6 +163,16 @@ private boolean isNoInteraction() { ); } + @Override + @AssertFalse + public boolean isEmpty() { + return ( + name == null && description == null && + type == null && moreInfo == null && extensions == null && + interactionType == null && isNoInteraction() + ); + } + @AssertTrue public boolean isValidInteractionActivity() { if (interactionType == null) { diff --git a/src/main/java/com/yetanalytics/xapi/model/Agent.java b/src/main/java/com/yetanalytics/xapi/model/Agent.java index a234efb..a7ae389 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Agent.java +++ b/src/main/java/com/yetanalytics/xapi/model/Agent.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.AssertTrue; /** @@ -17,6 +18,12 @@ public class Agent extends AbstractActor { // Validation + @Override + @AssertFalse + public boolean isEmpty() { + return super.isEmpty(); + } + /** * Assertion that the Agent has only 1 Inverse Functional Identifier (IFI). * @return true if the Agent has exactly 1 IFI, false otherwise diff --git a/src/main/java/com/yetanalytics/xapi/model/Attachment.java b/src/main/java/com/yetanalytics/xapi/model/Attachment.java index 149f05a..d42c66a 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Attachment.java +++ b/src/main/java/com/yetanalytics/xapi/model/Attachment.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** @@ -10,7 +11,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class Attachment { +public class Attachment implements JSONObject { @NotNull private String usageType; @@ -68,5 +69,17 @@ public void setFileUrl(String fileUrl) { this.fileUrl = fileUrl; } - + @Override + @AssertFalse + public boolean isEmpty() { + return ( + usageType == null && + display == null && + description == null && + contentType == null && + length == null && + sha2 == null && + fileUrl == null + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Context.java b/src/main/java/com/yetanalytics/xapi/model/Context.java index da73c63..0aaee79 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Context.java +++ b/src/main/java/com/yetanalytics/xapi/model/Context.java @@ -1,15 +1,18 @@ package com.yetanalytics.xapi.model; import java.util.UUID; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; + /** * Class representation of the Context Component of the * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class Context { +public class Context implements JSONObject { private UUID registration; private AbstractActor instructor; @@ -76,4 +79,19 @@ public void setExtensions(Extensions extensions) { this.extensions = extensions; } + @Override + @AssertFalse + public boolean isEmpty() { + return ( + registration == null && + instructor == null && + team == null && + contextActivities == null && + revision == null && + platform == null && + language == null && + statement == null && + extensions == null + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java index eb65d31..47f6104 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java +++ b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.ContextActivityListDeserializer; + +import jakarta.validation.constraints.AssertFalse; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -12,7 +15,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class ContextActivities { +public class ContextActivities implements JSONObject { @JsonDeserialize(using = ContextActivityListDeserializer.class) private List parent; @@ -48,4 +51,12 @@ public void setOther(List other) { this.other = other; } + @Override + @AssertFalse + public boolean isEmpty() { + return ( + parent == null && grouping == null && + category == null && other == null + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Extensions.java b/src/main/java/com/yetanalytics/xapi/model/Extensions.java index 09ee477..0156355 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Extensions.java +++ b/src/main/java/com/yetanalytics/xapi/model/Extensions.java @@ -13,6 +13,8 @@ import com.yetanalytics.xapi.model.serializers.ExtensionSerializer; import com.yetanalytics.xapi.util.Mapper; +import jakarta.validation.constraints.AssertFalse; + /** * A wrapper object for using xAPI Extensions. * @@ -22,7 +24,7 @@ */ @JsonDeserialize(using = ExtensionDeserializer.class) @JsonSerialize(using = ExtensionSerializer.class) -public class Extensions { +public class Extensions implements JSONObject { private Map extMap = new HashMap<>(); @@ -96,4 +98,10 @@ public Set getKeys() { public Map getMap() { return extMap; } + + @Override + @AssertFalse + public boolean isEmpty() { + return extMap.isEmpty(); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Group.java b/src/main/java/com/yetanalytics/xapi/model/Group.java index 333476e..7baae6e 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Group.java +++ b/src/main/java/com/yetanalytics/xapi/model/Group.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.AssertTrue; /** @@ -48,4 +49,11 @@ public boolean isValidAuthority() { ) ); } + + @Override + @AssertFalse + public boolean isEmpty() { + // zero-length member arrays still count as non-empty + return super.isEmpty() && member == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java index 0966dc4..6dd3cfd 100644 --- a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java +++ b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** @@ -9,7 +10,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class InteractionComponent { +public class InteractionComponent implements JSONObject { @NotNull private String id; @@ -32,5 +33,9 @@ public void setDescription(LangMap description) { this.description = description; } - + @Override + @AssertFalse + public boolean isEmpty() { + return id == null && description == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/JSONObject.java b/src/main/java/com/yetanalytics/xapi/model/JSONObject.java new file mode 100644 index 0000000..157f6db --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/model/JSONObject.java @@ -0,0 +1,5 @@ +package com.yetanalytics.xapi.model; + +public interface JSONObject { + public boolean isEmpty(); +} diff --git a/src/main/java/com/yetanalytics/xapi/model/LangMap.java b/src/main/java/com/yetanalytics/xapi/model/LangMap.java index cb451a4..6c964f5 100644 --- a/src/main/java/com/yetanalytics/xapi/model/LangMap.java +++ b/src/main/java/com/yetanalytics/xapi/model/LangMap.java @@ -9,6 +9,8 @@ import com.yetanalytics.xapi.model.deserializers.LangMapDeserializer; import com.yetanalytics.xapi.model.serializers.LangMapSerializer; +import jakarta.validation.constraints.AssertFalse; + /** * Java wrapper object for the * xAPI Language Map object. @@ -18,7 +20,7 @@ */ @JsonDeserialize(using = LangMapDeserializer.class) @JsonSerialize(using = LangMapSerializer.class) -public class LangMap { +public class LangMap implements JSONObject { private HashMap languageHashMap = new HashMap<>(); @@ -71,4 +73,10 @@ public Set getLanguageCodes() { public Map getMap() { return languageHashMap; } + + @Override + @AssertFalse + public boolean isEmpty() { + return languageHashMap.isEmpty(); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Result.java b/src/main/java/com/yetanalytics/xapi/model/Result.java index b4e47db..8df9f04 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Result.java +++ b/src/main/java/com/yetanalytics/xapi/model/Result.java @@ -4,13 +4,14 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertFalse; /** * Class representation of the Result component of the * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class Result { +public class Result implements JSONObject { @Valid private Score score; @@ -56,4 +57,17 @@ public Extensions getExtensions() { public void setExtensions(Extensions extensions) { this.extensions = extensions; } + + @Override + @AssertFalse + public boolean isEmpty() { + return ( + score == null && + success == null && + completion == null && + response == null && + duration == null && + extensions == null + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Score.java b/src/main/java/com/yetanalytics/xapi/model/Score.java index 1388a56..043af7d 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Score.java +++ b/src/main/java/com/yetanalytics/xapi/model/Score.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; @@ -14,7 +15,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class Score { +public class Score implements JSONObject { private BigDecimal raw; private BigDecimal min; @@ -77,4 +78,10 @@ public boolean isMinLessThanMax() { return true; } } + + @Override + @AssertFalse + public boolean isEmpty() { + return raw == null && min == null && max == null && scaled == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Statement.java b/src/main/java/com/yetanalytics/xapi/model/Statement.java index 7031777..2465dc4 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Statement.java +++ b/src/main/java/com/yetanalytics/xapi/model/Statement.java @@ -11,6 +11,7 @@ import com.yetanalytics.xapi.model.serializers.DateTimeSerializer; import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotNull; @@ -204,4 +205,22 @@ public boolean isValidSubStatement() { return true; } } + + @Override + @AssertFalse + public boolean isEmpty() { + return ( + id == null && + actor == null && + verb == null && + object == null && + context == null && + result == null && + authority == null && + timestamp == null && + stored == null && + version == null && + attachments == null + ); + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java index 477a7f5..2044fdd 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** @@ -25,4 +26,10 @@ public UUID getId() { public void setId(UUID id) { this.id = id; } + + @Override + @AssertFalse + public boolean isEmpty() { + return id == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java index 6272807..e29efb5 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java @@ -5,13 +5,14 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** * Class representation of a StatementResult from the 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class StatementResult { +public class StatementResult implements JSONObject { @NotNull private List statements; @@ -33,4 +34,10 @@ public String getMore() { public void setMore(String more) { this.more = more; } + + @Override + @AssertFalse + public boolean isEmpty() { + return statements == null && more == null; + } } diff --git a/src/main/java/com/yetanalytics/xapi/model/Verb.java b/src/main/java/com/yetanalytics/xapi/model/Verb.java index e6d1178..cd8c0ab 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Verb.java +++ b/src/main/java/com/yetanalytics/xapi/model/Verb.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.LangMapDeserializer; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.NotNull; /** @@ -12,7 +13,7 @@ * 9274.1.1 xAPI Specification. */ @JsonInclude(Include.NON_NULL) -public class Verb { +public class Verb implements JSONObject { public static final String VOIDING_VERB_IRI = "http://adlnet.gov/expapi/verbs/voided"; @@ -41,5 +42,10 @@ public void setDisplay(LangMap display) { public boolean isVoiding() { return id == VOIDING_VERB_IRI; } - + + @Override + @AssertFalse + public boolean isEmpty() { + return id == null && display == null; + } } diff --git a/src/test/java/com/yetanalytics/XapiSerializationTest.java b/src/test/java/com/yetanalytics/XapiSerializationTest.java index cedc222..30dc13f 100644 --- a/src/test/java/com/yetanalytics/XapiSerializationTest.java +++ b/src/test/java/com/yetanalytics/XapiSerializationTest.java @@ -1,7 +1,7 @@ package com.yetanalytics; -import java.io.IOException; import java.io.File; +import java.io.IOException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,7 +9,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.flipkart.zjsonpatch.JsonDiff; import com.yetanalytics.util.TestFileUtils; -import com.yetanalytics.xapi.model.*; +import com.yetanalytics.xapi.model.Statement; +import com.yetanalytics.xapi.model.StatementResult; import com.yetanalytics.xapi.util.Mapper; import junit.framework.Test; diff --git a/src/test/java/com/yetanalytics/model/AccountTest.java b/src/test/java/com/yetanalytics/model/AccountTest.java index fa54cb3..bab6f03 100644 --- a/src/test/java/com/yetanalytics/model/AccountTest.java +++ b/src/test/java/com/yetanalytics/model/AccountTest.java @@ -27,6 +27,6 @@ public void testValidAccount() { @Test public void testEmptyAccount() { - ValidationUtils.assertInvalid(validator, account, 2); + ValidationUtils.assertInvalid(validator, account, 3); } } diff --git a/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java b/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java index 19ab497..2baeb90 100644 --- a/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java +++ b/src/test/java/com/yetanalytics/model/ActivityDefinitionTest.java @@ -43,7 +43,7 @@ public void init() { @Test public void testEmptyDefinition() { - ValidationUtils.assertValid(validator, definition); + ValidationUtils.assertInvalid(validator, definition); } @Test diff --git a/src/test/java/com/yetanalytics/model/ActivityTest.java b/src/test/java/com/yetanalytics/model/ActivityTest.java index 32c8082..e7a226c 100644 --- a/src/test/java/com/yetanalytics/model/ActivityTest.java +++ b/src/test/java/com/yetanalytics/model/ActivityTest.java @@ -26,14 +26,14 @@ public void testActivity() { activity.setId("http://example.org/activity"); ValidationUtils.assertValid(validator, activity); - // TODO: Enforce that definitions, as JSON objects, can't be empty - // For now we allow them though activity.setDefinition(definition); + definition.setMoreInfo("https://yetanalytics.com"); ValidationUtils.assertValid(validator, activity); } @Test public void testEmptyActivity() { - ValidationUtils.assertInvalid(validator, activity); + // One error for empty object, one error for missing ID + ValidationUtils.assertInvalid(validator, activity, 2); } } diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java index 17bfd38..9a93650 100644 --- a/src/test/java/com/yetanalytics/model/AgentTest.java +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -51,12 +51,14 @@ public void testAccount() { public void testInvalidAccount() { Account account = new Account(); agent.setAccount(account); - ValidationUtils.assertInvalid(validator, agent, 2); + // One error for empty account, one error each for missing properties + ValidationUtils.assertInvalid(validator, agent, 3); } @Test public void testNoIFI() { - ValidationUtils.assertInvalid(validator, agent); + // One error for empty agent, one error for missing IFI + ValidationUtils.assertInvalid(validator, agent, 2); } @Test diff --git a/src/test/java/com/yetanalytics/model/AttachmentTest.java b/src/test/java/com/yetanalytics/model/AttachmentTest.java index fe71f99..e03e389 100644 --- a/src/test/java/com/yetanalytics/model/AttachmentTest.java +++ b/src/test/java/com/yetanalytics/model/AttachmentTest.java @@ -47,6 +47,7 @@ public void testAttachment() { @Test public void testEmptyAttachment() { - ValidationUtils.assertInvalid(validator, attachment, 5); + // One error for empty attachment, one error each for null properties + ValidationUtils.assertInvalid(validator, attachment, 6); } } diff --git a/src/test/java/com/yetanalytics/model/GroupTest.java b/src/test/java/com/yetanalytics/model/GroupTest.java index 691daf4..a2c11da 100644 --- a/src/test/java/com/yetanalytics/model/GroupTest.java +++ b/src/test/java/com/yetanalytics/model/GroupTest.java @@ -65,12 +65,14 @@ public void testAccount() { public void testInvalidAccount() { Account account = new Account(); group.setAccount(account); - ValidationUtils.assertInvalid(validator, group, 2); + // One error for empty account, one error each for missing properties + ValidationUtils.assertInvalid(validator, group, 3); } @Test public void testNoIFI() { // No member array => identified group - ValidationUtils.assertInvalid(validator, group); + // One error for empty group object, one error for no IFI + ValidationUtils.assertInvalid(validator, group, 2); } @Test diff --git a/src/test/java/com/yetanalytics/model/InteractionComponentTest.java b/src/test/java/com/yetanalytics/model/InteractionComponentTest.java index 4183b5b..e74a1ad 100644 --- a/src/test/java/com/yetanalytics/model/InteractionComponentTest.java +++ b/src/test/java/com/yetanalytics/model/InteractionComponentTest.java @@ -32,6 +32,7 @@ public void testInteractionComponent() { @Test public void testEmpytInteractionComponent() { - ValidationUtils.assertInvalid(validator, interactionComponent); + // One error for empty component, one error for missing ID + ValidationUtils.assertInvalid(validator, interactionComponent, 2); } } diff --git a/src/test/java/com/yetanalytics/model/ScoreTest.java b/src/test/java/com/yetanalytics/model/ScoreTest.java index a6e856f..0cb4f9b 100644 --- a/src/test/java/com/yetanalytics/model/ScoreTest.java +++ b/src/test/java/com/yetanalytics/model/ScoreTest.java @@ -22,8 +22,6 @@ public void init() { @Test public void testScore() { - ValidationUtils.assertValid(validator, score); // TODO: Make invalid - score.setMin(new BigDecimal(0)); score.setMax(new BigDecimal(100)); score.setRaw(new BigDecimal(50)); @@ -31,6 +29,11 @@ public void testScore() { ValidationUtils.assertValid(validator, score); } + @Test + public void testEmptyScore() { + ValidationUtils.assertInvalid(validator, score); + } + @Test public void testScaledTooSmall() { score.setScaled(new BigDecimal(-2.0)); diff --git a/src/test/java/com/yetanalytics/model/StatementRefTest.java b/src/test/java/com/yetanalytics/model/StatementRefTest.java index c20fd05..bca7432 100644 --- a/src/test/java/com/yetanalytics/model/StatementRefTest.java +++ b/src/test/java/com/yetanalytics/model/StatementRefTest.java @@ -29,6 +29,7 @@ public void testStatementRef() { @Test public void testEmptyStatementRef() { - ValidationUtils.assertInvalid(validator, statementRef); + // One error for empty object, one error for missing ID + ValidationUtils.assertInvalid(validator, statementRef, 2); } } diff --git a/src/test/java/com/yetanalytics/model/StatementResultTest.java b/src/test/java/com/yetanalytics/model/StatementResultTest.java index f39a3d6..167f3e9 100644 --- a/src/test/java/com/yetanalytics/model/StatementResultTest.java +++ b/src/test/java/com/yetanalytics/model/StatementResultTest.java @@ -50,6 +50,7 @@ public void testStatementResult() { @Test public void testEmptyStatementResult() { - ValidationUtils.assertInvalid(validator, statementResult); + // One error for empty statement res, one error for missing statement + ValidationUtils.assertInvalid(validator, statementResult, 2); } } diff --git a/src/test/java/com/yetanalytics/model/StatementTest.java b/src/test/java/com/yetanalytics/model/StatementTest.java index 33445e2..7be6481 100644 --- a/src/test/java/com/yetanalytics/model/StatementTest.java +++ b/src/test/java/com/yetanalytics/model/StatementTest.java @@ -61,7 +61,8 @@ public void testEmptyStatement() { statement.setActor(null); statement.setVerb(null); statement.setObject(null); - ValidationUtils.assertInvalid(validator, statement, 3); + // One error for empty statement, one error each for missing property + ValidationUtils.assertInvalid(validator, statement, 4); } @Test diff --git a/src/test/java/com/yetanalytics/model/VerbTest.java b/src/test/java/com/yetanalytics/model/VerbTest.java index d8920ca..88bd0f1 100644 --- a/src/test/java/com/yetanalytics/model/VerbTest.java +++ b/src/test/java/com/yetanalytics/model/VerbTest.java @@ -33,6 +33,7 @@ public void testVerb() { @Test public void testEmptyVerb() { - ValidationUtils.assertInvalid(validator, verb); + // One error for empty verb, one error for missing ID + ValidationUtils.assertInvalid(validator, verb, 2); } } From 90704aa5d9ce8f4e414c05d286cc8a7e3083d2ac Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Tue, 10 Dec 2024 10:06:19 -0500 Subject: [PATCH 21/28] Make statement's own isValidSubStmt method --- .../com/yetanalytics/xapi/model/Statement.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/yetanalytics/xapi/model/Statement.java b/src/main/java/com/yetanalytics/xapi/model/Statement.java index 2465dc4..1150e3d 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Statement.java +++ b/src/main/java/com/yetanalytics/xapi/model/Statement.java @@ -186,6 +186,16 @@ public boolean isValidAuthority() { return authority == null || authority.isValidAuthority(); } + private boolean isValidSubStmt() { + return ( + id == null && + stored == null && + version == null && + authority == null && + !(object instanceof Statement) + ); + } + // TODO: Validate this on the SubStatement itself // (e.g. setting the objectType field) @AssertTrue @@ -194,13 +204,7 @@ public boolean isValidSubStatement() { // TODO: If object is true... if (object instanceof Statement) { Statement subStatement = (Statement) object; - return ( - subStatement.getId() == null && - subStatement.getStored() == null && - subStatement.getVersion() == null && - subStatement.getAuthority() == null && - !(subStatement.getObject() instanceof Statement) - ); + return subStatement.isValidSubStmt(); } else { return true; } From 46b9f84b1f3b1c75ab4e3ee9845dd818f148b0e5 Mon Sep 17 00:00:00 2001 From: kelvinqian00 Date: Tue, 10 Dec 2024 10:24:33 -0500 Subject: [PATCH 22/28] Apply JsonIgnore annotation to prevent Jackson for serializing extra methods --- .../java/com/yetanalytics/xapi/model/AbstractActor.java | 2 ++ .../java/com/yetanalytics/xapi/model/AbstractObject.java | 2 ++ src/main/java/com/yetanalytics/xapi/model/Account.java | 2 ++ src/main/java/com/yetanalytics/xapi/model/Activity.java | 2 ++ .../com/yetanalytics/xapi/model/ActivityDefinition.java | 3 +++ src/main/java/com/yetanalytics/xapi/model/Agent.java | 4 ++++ src/main/java/com/yetanalytics/xapi/model/Attachment.java | 4 ++++ src/main/java/com/yetanalytics/xapi/model/Context.java | 2 ++ .../com/yetanalytics/xapi/model/ContextActivities.java | 7 ++++--- src/main/java/com/yetanalytics/xapi/model/Extensions.java | 2 ++ src/main/java/com/yetanalytics/xapi/model/Group.java | 4 ++++ .../com/yetanalytics/xapi/model/InteractionComponent.java | 4 ++++ src/main/java/com/yetanalytics/xapi/model/LangMap.java | 2 ++ src/main/java/com/yetanalytics/xapi/model/Result.java | 4 ++++ src/main/java/com/yetanalytics/xapi/model/Score.java | 5 +++++ src/main/java/com/yetanalytics/xapi/model/Statement.java | 7 +++++++ .../java/com/yetanalytics/xapi/model/StatementRef.java | 2 ++ .../java/com/yetanalytics/xapi/model/StatementResult.java | 2 ++ src/main/java/com/yetanalytics/xapi/model/Verb.java | 5 +++++ 19 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java index 0f18e1d..c8285c6 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.AbstractActorDeserializer; @@ -81,6 +82,7 @@ protected int countIFIs() { public abstract boolean isValidAuthority(); @Override + @JsonIgnore public boolean isEmpty() { return ( mbox == null && mbox_sha1sum == null && diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java b/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java index 69ebebb..c96899f 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractObject.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.AbstractObjectDeserializer; @@ -19,5 +20,6 @@ public void setObjectType(ObjectType objectType) { } @Override + @JsonIgnore public abstract boolean isEmpty(); } diff --git a/src/main/java/com/yetanalytics/xapi/model/Account.java b/src/main/java/com/yetanalytics/xapi/model/Account.java index eff18d8..eaf033a 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Account.java +++ b/src/main/java/com/yetanalytics/xapi/model/Account.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -33,6 +34,7 @@ public void setName(String name) { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return homePage == null && name == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/Activity.java b/src/main/java/com/yetanalytics/xapi/model/Activity.java index 4385d96..28aff1c 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Activity.java +++ b/src/main/java/com/yetanalytics/xapi/model/Activity.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -36,6 +37,7 @@ public void setDefinition(ActivityDefinition definition) { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return id == null && definition == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java index 5439f8d..de24997 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java +++ b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java @@ -2,6 +2,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -164,6 +165,7 @@ private boolean isNoInteraction() { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return ( @@ -173,6 +175,7 @@ public boolean isEmpty() { ); } + @JsonIgnore @AssertTrue public boolean isValidInteractionActivity() { if (interactionType == null) { diff --git a/src/main/java/com/yetanalytics/xapi/model/Agent.java b/src/main/java/com/yetanalytics/xapi/model/Agent.java index a7ae389..f4c8170 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Agent.java +++ b/src/main/java/com/yetanalytics/xapi/model/Agent.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -19,6 +20,7 @@ public class Agent extends AbstractActor { // Validation @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return super.isEmpty(); @@ -28,12 +30,14 @@ public boolean isEmpty() { * Assertion that the Agent has only 1 Inverse Functional Identifier (IFI). * @return true if the Agent has exactly 1 IFI, false otherwise */ + @JsonIgnore @AssertTrue public boolean isIdentifiedAgent() { return countIFIs() == 1; } @Override + @JsonIgnore public boolean isValidAuthority() { return true; } diff --git a/src/main/java/com/yetanalytics/xapi/model/Attachment.java b/src/main/java/com/yetanalytics/xapi/model/Attachment.java index d42c66a..84fa87c 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Attachment.java +++ b/src/main/java/com/yetanalytics/xapi/model/Attachment.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -69,7 +70,10 @@ public void setFileUrl(String fileUrl) { this.fileUrl = fileUrl; } + // Validation + @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return ( diff --git a/src/main/java/com/yetanalytics/xapi/model/Context.java b/src/main/java/com/yetanalytics/xapi/model/Context.java index 0aaee79..6b1ff41 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Context.java +++ b/src/main/java/com/yetanalytics/xapi/model/Context.java @@ -2,6 +2,7 @@ import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -80,6 +81,7 @@ public void setExtensions(Extensions extensions) { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return ( diff --git a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java index 47f6104..6a6cb37 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java +++ b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java @@ -2,14 +2,14 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.ContextActivityListDeserializer; import jakarta.validation.constraints.AssertFalse; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; - /** * Class representation of the Context Activities Component of the * 9274.1.1 xAPI Specification. @@ -52,6 +52,7 @@ public void setOther(List other) { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return ( diff --git a/src/main/java/com/yetanalytics/xapi/model/Extensions.java b/src/main/java/com/yetanalytics/xapi/model/Extensions.java index 0156355..72966e7 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Extensions.java +++ b/src/main/java/com/yetanalytics/xapi/model/Extensions.java @@ -4,6 +4,7 @@ import java.util.Map; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -100,6 +101,7 @@ public Map getMap() { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return extMap.isEmpty(); diff --git a/src/main/java/com/yetanalytics/xapi/model/Group.java b/src/main/java/com/yetanalytics/xapi/model/Group.java index 7baae6e..bb3a8e6 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Group.java +++ b/src/main/java/com/yetanalytics/xapi/model/Group.java @@ -2,6 +2,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -27,6 +28,7 @@ public void setMember(List member) { this.member = member; } + @JsonIgnore @AssertTrue public boolean isAnonymousOrIdentifiedGroup() { return ( @@ -40,6 +42,7 @@ private boolean isValidConsumer(Agent consumer) { } @Override + @JsonIgnore public boolean isValidAuthority() { return ( member.size() == 2 && @@ -51,6 +54,7 @@ public boolean isValidAuthority() { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { // zero-length member arrays still count as non-empty diff --git a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java index 6dd3cfd..2bbcbb9 100644 --- a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java +++ b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java @@ -1,4 +1,5 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -33,7 +34,10 @@ public void setDescription(LangMap description) { this.description = description; } + // Validation + @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return id == null && description == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/LangMap.java b/src/main/java/com/yetanalytics/xapi/model/LangMap.java index 6c964f5..2a94575 100644 --- a/src/main/java/com/yetanalytics/xapi/model/LangMap.java +++ b/src/main/java/com/yetanalytics/xapi/model/LangMap.java @@ -4,6 +4,7 @@ import java.util.Map; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.yetanalytics.xapi.model.deserializers.LangMapDeserializer; @@ -75,6 +76,7 @@ public Map getMap() { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return languageHashMap.isEmpty(); diff --git a/src/main/java/com/yetanalytics/xapi/model/Result.java b/src/main/java/com/yetanalytics/xapi/model/Result.java index 8df9f04..a0dee7e 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Result.java +++ b/src/main/java/com/yetanalytics/xapi/model/Result.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -58,7 +59,10 @@ public void setExtensions(Extensions extensions) { this.extensions = extensions; } + // Validation + @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return ( diff --git a/src/main/java/com/yetanalytics/xapi/model/Score.java b/src/main/java/com/yetanalytics/xapi/model/Score.java index 043af7d..9a73eac 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Score.java +++ b/src/main/java/com/yetanalytics/xapi/model/Score.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -52,6 +53,7 @@ public void setScaled(BigDecimal scaled) { // Validation + @JsonIgnore @AssertTrue public boolean isMinLessThanRaw() { if (raw != null && min != null) { @@ -61,6 +63,7 @@ public boolean isMinLessThanRaw() { } } + @JsonIgnore @AssertTrue public boolean isRawLessThanMax() { if (raw != null && max != null) { @@ -70,6 +73,7 @@ public boolean isRawLessThanMax() { } } + @JsonIgnore @AssertTrue public boolean isMinLessThanMax() { if (min != null && max != null) { @@ -80,6 +84,7 @@ public boolean isMinLessThanMax() { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return raw == null && min == null && max == null && scaled == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/Statement.java b/src/main/java/com/yetanalytics/xapi/model/Statement.java index 1150e3d..af8421e 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Statement.java +++ b/src/main/java/com/yetanalytics/xapi/model/Statement.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -145,6 +146,7 @@ public void setAttachments(List attachments) { // Validation + @JsonIgnore @AssertTrue public boolean isValidVoidingStatement() { if (verb != null && verb.isVoiding()) { @@ -162,6 +164,7 @@ private boolean isObjectActivity() { } } + @JsonIgnore @AssertTrue public boolean isValidContextRevision() { return ( @@ -171,6 +174,7 @@ public boolean isValidContextRevision() { ); } + @JsonIgnore @AssertTrue public boolean isValidContextPlatform() { return ( @@ -181,6 +185,7 @@ public boolean isValidContextPlatform() { } // TODO: Somehow validate this on the Authority object itself + @JsonIgnore @AssertTrue public boolean isValidAuthority() { return authority == null || authority.isValidAuthority(); @@ -198,6 +203,7 @@ private boolean isValidSubStmt() { // TODO: Validate this on the SubStatement itself // (e.g. setting the objectType field) + @JsonIgnore @AssertTrue public boolean isValidSubStatement() { // System.out.println("Object is Statement: " + (object instanceof Statement)); @@ -211,6 +217,7 @@ public boolean isValidSubStatement() { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return ( diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java index 2044fdd..21eeb45 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java @@ -2,6 +2,7 @@ import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -28,6 +29,7 @@ public void setId(UUID id) { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return id == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java index e29efb5..8fb21c4 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java @@ -2,6 +2,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -36,6 +37,7 @@ public void setMore(String more) { } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return statements == null && more == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/Verb.java b/src/main/java/com/yetanalytics/xapi/model/Verb.java index cd8c0ab..4da6035 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Verb.java +++ b/src/main/java/com/yetanalytics/xapi/model/Verb.java @@ -1,5 +1,6 @@ package com.yetanalytics.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -39,11 +40,15 @@ public void setDisplay(LangMap display) { this.display = display; } + // Validation + + @JsonIgnore public boolean isVoiding() { return id == VOIDING_VERB_IRI; } @Override + @JsonIgnore @AssertFalse public boolean isEmpty() { return id == null && display == null; From d2b51296abcea7a492448a6c8d1a488d93b107dd Mon Sep 17 00:00:00 2001 From: Cliff Casey Date: Tue, 7 Jul 2026 19:07:31 -0400 Subject: [PATCH 23/28] new validation for sha1 and mbox and tests. result extensions in tests --- .../xapi/model/AbstractActor.java | 8 +++-- .../com/yetanalytics/xapi/util/HashUtils.java | 13 ++++++++ .../yetanalytics/xapi/validation/MboxUri.java | 22 +++++++++++++ .../xapi/validation/MboxUriValidator.java | 31 +++++++++++++++++++ .../yetanalytics/xapi/validation/Sha1Sum.java | 22 +++++++++++++ .../xapi/validation/Sha1SumValidator.java | 14 +++++++++ .../com/yetanalytics/model/AgentTest.java | 24 ++++++++++++++ .../com/yetanalytics/model/GroupTest.java | 12 +++++++ .../com/yetanalytics/model/ResultTest.java | 14 +++++++-- 9 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/yetanalytics/xapi/util/HashUtils.java create mode 100644 src/main/java/com/yetanalytics/xapi/validation/MboxUri.java create mode 100644 src/main/java/com/yetanalytics/xapi/validation/MboxUriValidator.java create mode 100644 src/main/java/com/yetanalytics/xapi/validation/Sha1Sum.java create mode 100644 src/main/java/com/yetanalytics/xapi/validation/Sha1SumValidator.java diff --git a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java index 73739c0..2dbe58b 100644 --- a/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java +++ b/src/main/java/com/yetanalytics/xapi/model/AbstractActor.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.AbstractActorDeserializer; +import com.yetanalytics.xapi.validation.MboxUri; +import com.yetanalytics.xapi.validation.Sha1Sum; import jakarta.validation.Valid; @@ -18,11 +20,11 @@ public abstract class AbstractActor extends AbstractObject { // IFIs - // TODO: Validate mbox is a valid mailto IRI + @MboxUri private URI mbox; - // TODO: Validate mbox_sha1sum is a valid hash string + @Sha1Sum private String mbox_sha1sum; - // TODO: Validate openid is a valid IRI + private URI openid; @Valid diff --git a/src/main/java/com/yetanalytics/xapi/util/HashUtils.java b/src/main/java/com/yetanalytics/xapi/util/HashUtils.java new file mode 100644 index 0000000..9a75b27 --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/util/HashUtils.java @@ -0,0 +1,13 @@ +package com.yetanalytics.xapi.util; + +import java.util.regex.Pattern; + +public final class HashUtils { + private static final Pattern SHA1_HEX_PATTERN = Pattern.compile("^[0-9a-fA-F]{40}$"); + + private HashUtils() {} + + public static boolean isSha1Hex(String value) { + return value != null && SHA1_HEX_PATTERN.matcher(value).matches(); + } +} diff --git a/src/main/java/com/yetanalytics/xapi/validation/MboxUri.java b/src/main/java/com/yetanalytics/xapi/validation/MboxUri.java new file mode 100644 index 0000000..e944a15 --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/validation/MboxUri.java @@ -0,0 +1,22 @@ +package com.yetanalytics.xapi.validation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Documented +@Constraint(validatedBy = MboxUriValidator.class) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface MboxUri { + String message() default "must be a mailto URI containing an email address"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/yetanalytics/xapi/validation/MboxUriValidator.java b/src/main/java/com/yetanalytics/xapi/validation/MboxUriValidator.java new file mode 100644 index 0000000..152e66a --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/validation/MboxUriValidator.java @@ -0,0 +1,31 @@ +package com.yetanalytics.xapi.validation; + +import java.net.URI; +import java.util.regex.Pattern; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class MboxUriValidator implements ConstraintValidator { + private static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile( + "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" + ); + + @Override + public boolean isValid(URI value, ConstraintValidatorContext context) { + if (value == null) { + return true; + } + + String scheme = value.getScheme(); + String address = value.getSchemeSpecificPart(); + + return ( + "mailto".equalsIgnoreCase(scheme) && + value.getFragment() == null && + value.getQuery() == null && + address != null && + EMAIL_ADDRESS_PATTERN.matcher(address).matches() + ); + } +} diff --git a/src/main/java/com/yetanalytics/xapi/validation/Sha1Sum.java b/src/main/java/com/yetanalytics/xapi/validation/Sha1Sum.java new file mode 100644 index 0000000..81bc5ff --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/validation/Sha1Sum.java @@ -0,0 +1,22 @@ +package com.yetanalytics.xapi.validation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Documented +@Constraint(validatedBy = Sha1SumValidator.class) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Sha1Sum { + String message() default "must be a 40-character SHA-1 hex string"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/yetanalytics/xapi/validation/Sha1SumValidator.java b/src/main/java/com/yetanalytics/xapi/validation/Sha1SumValidator.java new file mode 100644 index 0000000..c8bf855 --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/validation/Sha1SumValidator.java @@ -0,0 +1,14 @@ +package com.yetanalytics.xapi.validation; + +import com.yetanalytics.xapi.util.HashUtils; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class Sha1SumValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return value == null || HashUtils.isSha1Hex(value); + } +} diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java index bd77065..5a8a891 100644 --- a/src/test/java/com/yetanalytics/model/AgentTest.java +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -28,12 +28,36 @@ public void testMbox() throws URISyntaxException { ValidationUtils.assertValid(validator, agent); } + @Test + public void testInvalidMboxScheme() throws URISyntaxException { + agent.setMbox(new URI("http://foo@example.com")); + ValidationUtils.assertInvalid(validator, agent); + } + + @Test + public void testInvalidMboxAddress() throws URISyntaxException { + agent.setMbox(new URI("mailto:not-an-email")); + ValidationUtils.assertInvalid(validator, agent); + } + @Test public void testMboxSha1Sum() { agent.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); ValidationUtils.assertValid(validator, agent); } + @Test + public void testInvalidMboxSha1SumLength() { + agent.setMbox_sha1sum("767e74eab7081c41e0b83630511139d13024966"); + ValidationUtils.assertInvalid(validator, agent); + } + + @Test + public void testInvalidMboxSha1SumHex() { + agent.setMbox_sha1sum("z67e74eab7081c41e0b83630511139d130249666"); + ValidationUtils.assertInvalid(validator, agent); + } + @Test public void testOpenid() throws URISyntaxException { agent.setOpenid(new URI("http://openid.example.com")); diff --git a/src/test/java/com/yetanalytics/model/GroupTest.java b/src/test/java/com/yetanalytics/model/GroupTest.java index 7088413..e86886a 100644 --- a/src/test/java/com/yetanalytics/model/GroupTest.java +++ b/src/test/java/com/yetanalytics/model/GroupTest.java @@ -41,12 +41,24 @@ public void testMbox() throws URISyntaxException { ValidationUtils.assertValid(validator, group); } + @Test + public void testInvalidMbox() throws URISyntaxException { + group.setMbox(new URI("mailto:not-an-email")); + ValidationUtils.assertInvalid(validator, group); + } + @Test public void testMboxSha1Sum() { group.setMbox_sha1sum("767e74eab7081c41e0b83630511139d130249666"); ValidationUtils.assertValid(validator, group); } + @Test + public void testInvalidMboxSha1Sum() { + group.setMbox_sha1sum("not-a-sha1sum"); + ValidationUtils.assertInvalid(validator, group); + } + @Test public void testOpenid() throws URISyntaxException { group.setOpenid(new URI("http://openid.example.com")); diff --git a/src/test/java/com/yetanalytics/model/ResultTest.java b/src/test/java/com/yetanalytics/model/ResultTest.java index b93f16f..29ae778 100644 --- a/src/test/java/com/yetanalytics/model/ResultTest.java +++ b/src/test/java/com/yetanalytics/model/ResultTest.java @@ -1,13 +1,18 @@ package com.yetanalytics.model; import java.math.BigDecimal; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.yetanalytics.xapi.util.ValidationUtils; +import com.yetanalytics.xapi.model.Extensions; import com.yetanalytics.xapi.model.Result; import com.yetanalytics.xapi.model.Score; +import com.yetanalytics.xapi.model.XapiDuration; import jakarta.validation.Validator; @@ -24,14 +29,17 @@ public void init() { } @Test - public void testResult() { + public void testResult() throws URISyntaxException { score.setScaled(new BigDecimal(0.5)); result.setScore(score); result.setSuccess(false); result.setCompletion(true); result.setResponse("myResponse"); - // TODO: setExtensions - // TODO: setDuration + Map extMap = Map.of( + new URI("http://yetanalytics.com/exts/1"), 1 + ); + result.setExtensions(new Extensions(extMap)); + result.setDuration(new XapiDuration("PT1H")); ValidationUtils.assertValid(validator, result); } From 55a60ed666e88ce41a8b6faa72aeb7f112e74156 Mon Sep 17 00:00:00 2001 From: Cliff Casey Date: Tue, 7 Jul 2026 19:12:08 -0400 Subject: [PATCH 24/28] sha256 support --- .../yetanalytics/xapi/model/Attachment.java | 3 ++- .../com/yetanalytics/xapi/util/HashUtils.java | 5 +++++ .../xapi/validation/Sha256Sum.java | 22 +++++++++++++++++++ .../xapi/validation/Sha256SumValidator.java | 14 ++++++++++++ .../yetanalytics/model/AttachmentTest.java | 21 ++++++++++++++++-- 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/yetanalytics/xapi/validation/Sha256Sum.java create mode 100644 src/main/java/com/yetanalytics/xapi/validation/Sha256SumValidator.java diff --git a/src/main/java/com/yetanalytics/xapi/model/Attachment.java b/src/main/java/com/yetanalytics/xapi/model/Attachment.java index bb48cbf..d8fc1fb 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Attachment.java +++ b/src/main/java/com/yetanalytics/xapi/model/Attachment.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.yetanalytics.xapi.model.deserializers.MimeTypeDeserializer; +import com.yetanalytics.xapi.validation.Sha256Sum; import jakarta.activation.MimeType; @@ -35,8 +36,8 @@ public class Attachment implements JSONObject { @NotNull private Integer length; - // TODO: Validate that sha2 is a SHA256, 64-char hex string @NotNull + @Sha256Sum private String sha2; private URI fileUrl; diff --git a/src/main/java/com/yetanalytics/xapi/util/HashUtils.java b/src/main/java/com/yetanalytics/xapi/util/HashUtils.java index 9a75b27..b9ed6bb 100644 --- a/src/main/java/com/yetanalytics/xapi/util/HashUtils.java +++ b/src/main/java/com/yetanalytics/xapi/util/HashUtils.java @@ -4,10 +4,15 @@ public final class HashUtils { private static final Pattern SHA1_HEX_PATTERN = Pattern.compile("^[0-9a-fA-F]{40}$"); + private static final Pattern SHA256_HEX_PATTERN = Pattern.compile("^[0-9a-fA-F]{64}$"); private HashUtils() {} public static boolean isSha1Hex(String value) { return value != null && SHA1_HEX_PATTERN.matcher(value).matches(); } + + public static boolean isSha256Hex(String value) { + return value != null && SHA256_HEX_PATTERN.matcher(value).matches(); + } } diff --git a/src/main/java/com/yetanalytics/xapi/validation/Sha256Sum.java b/src/main/java/com/yetanalytics/xapi/validation/Sha256Sum.java new file mode 100644 index 0000000..80b3486 --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/validation/Sha256Sum.java @@ -0,0 +1,22 @@ +package com.yetanalytics.xapi.validation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Documented +@Constraint(validatedBy = Sha256SumValidator.class) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Sha256Sum { + String message() default "must be a 64-character SHA-256 hex string"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/yetanalytics/xapi/validation/Sha256SumValidator.java b/src/main/java/com/yetanalytics/xapi/validation/Sha256SumValidator.java new file mode 100644 index 0000000..91238fc --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/validation/Sha256SumValidator.java @@ -0,0 +1,14 @@ +package com.yetanalytics.xapi.validation; + +import com.yetanalytics.xapi.util.HashUtils; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class Sha256SumValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return value == null || HashUtils.isSha256Hex(value); + } +} diff --git a/src/test/java/com/yetanalytics/model/AttachmentTest.java b/src/test/java/com/yetanalytics/model/AttachmentTest.java index 57adbc0..c3cf99d 100644 --- a/src/test/java/com/yetanalytics/model/AttachmentTest.java +++ b/src/test/java/com/yetanalytics/model/AttachmentTest.java @@ -25,8 +25,7 @@ public void init() { attachment = new Attachment(); } - @Test - public void testAttachment() throws URISyntaxException, MimeTypeParseException { + private void populateValidAttachment() throws URISyntaxException, MimeTypeParseException { LangMap display = new LangMap(new HashMap<>()); display.put("en-US", "Display"); @@ -45,10 +44,28 @@ public void testAttachment() throws URISyntaxException, MimeTypeParseException { attachment.setLength(length); attachment.setSha2(sha2); attachment.setFileUrl(fileUrl); + } + @Test + public void testAttachment() throws URISyntaxException, MimeTypeParseException { + populateValidAttachment(); ValidationUtils.assertValid(validator, attachment); } + @Test + public void testInvalidSha2Length() throws URISyntaxException, MimeTypeParseException { + populateValidAttachment(); + attachment.setSha2("426cf3a8b2864dd91201b989ba5728181da52bfff9a0489670e54cd8ec8b3a5"); + ValidationUtils.assertInvalid(validator, attachment); + } + + @Test + public void testInvalidSha2Hex() throws URISyntaxException, MimeTypeParseException { + populateValidAttachment(); + attachment.setSha2("z26cf3a8b2864dd91201b989ba5728181da52bfff9a0489670e54cd8ec8b3a50"); + ValidationUtils.assertInvalid(validator, attachment); + } + @Test public void testEmptyAttachment() { // One error for empty attachment, one error each for null properties From c7cb7b01607213499913a54264497cf8b275caf9 Mon Sep 17 00:00:00 2001 From: Cliff Casey Date: Tue, 7 Jul 2026 19:33:13 -0400 Subject: [PATCH 25/28] message specificity for constraint violations --- src/main/java/com/yetanalytics/xapi/model/Account.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Activity.java | 2 +- .../com/yetanalytics/xapi/model/ActivityDefinition.java | 4 ++-- src/main/java/com/yetanalytics/xapi/model/Agent.java | 4 ++-- src/main/java/com/yetanalytics/xapi/model/Attachment.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Context.java | 2 +- .../com/yetanalytics/xapi/model/ContextActivities.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Extensions.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Group.java | 4 ++-- .../com/yetanalytics/xapi/model/InteractionComponent.java | 2 +- src/main/java/com/yetanalytics/xapi/model/LangMap.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Result.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Score.java | 8 ++++---- src/main/java/com/yetanalytics/xapi/model/Statement.java | 8 ++++---- .../java/com/yetanalytics/xapi/model/StatementRef.java | 2 +- .../java/com/yetanalytics/xapi/model/StatementResult.java | 2 +- src/main/java/com/yetanalytics/xapi/model/Verb.java | 2 +- src/test/java/com/yetanalytics/model/AgentTest.java | 3 +++ src/test/java/com/yetanalytics/model/StatementTest.java | 1 + 19 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/yetanalytics/xapi/model/Account.java b/src/main/java/com/yetanalytics/xapi/model/Account.java index b8de3ac..845057a 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Account.java +++ b/src/main/java/com/yetanalytics/xapi/model/Account.java @@ -38,7 +38,7 @@ public void setName(String name) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Account must not be empty") public boolean isEmpty() { return homePage == null && name == null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/Activity.java b/src/main/java/com/yetanalytics/xapi/model/Activity.java index e4ba939..e1ef85b 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Activity.java +++ b/src/main/java/com/yetanalytics/xapi/model/Activity.java @@ -40,7 +40,7 @@ public void setDefinition(ActivityDefinition definition) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Activity must not be empty") public boolean isEmpty() { return id == null && definition == null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java index 063671e..91504a8 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java +++ b/src/main/java/com/yetanalytics/xapi/model/ActivityDefinition.java @@ -167,7 +167,7 @@ private boolean isNoInteraction() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "ActivityDefinition must not be empty") public boolean isEmpty() { return ( name == null && description == null && @@ -177,7 +177,7 @@ public boolean isEmpty() { } @JsonIgnore - @AssertTrue + @AssertTrue(message = "ActivityDefinition must be a valid interaction activity") public boolean isValidInteractionActivity() { if (interactionType == null) { return isNoInteraction(); diff --git a/src/main/java/com/yetanalytics/xapi/model/Agent.java b/src/main/java/com/yetanalytics/xapi/model/Agent.java index f4c8170..321c78c 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Agent.java +++ b/src/main/java/com/yetanalytics/xapi/model/Agent.java @@ -21,7 +21,7 @@ public class Agent extends AbstractActor { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Agent must not be empty") public boolean isEmpty() { return super.isEmpty(); } @@ -31,7 +31,7 @@ public boolean isEmpty() { * @return true if the Agent has exactly 1 IFI, false otherwise */ @JsonIgnore - @AssertTrue + @AssertTrue(message = "Agent must have exactly 1 Inverse Functional Identifier (IFI)") public boolean isIdentifiedAgent() { return countIFIs() == 1; } diff --git a/src/main/java/com/yetanalytics/xapi/model/Attachment.java b/src/main/java/com/yetanalytics/xapi/model/Attachment.java index d8fc1fb..7dcfbe8 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Attachment.java +++ b/src/main/java/com/yetanalytics/xapi/model/Attachment.java @@ -89,7 +89,7 @@ public void setFileUrl(URI fileUrl) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Attachment must not be empty") public boolean isEmpty() { return ( usageType == null && diff --git a/src/main/java/com/yetanalytics/xapi/model/Context.java b/src/main/java/com/yetanalytics/xapi/model/Context.java index ba12d3d..c4537c5 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Context.java +++ b/src/main/java/com/yetanalytics/xapi/model/Context.java @@ -82,7 +82,7 @@ public void setExtensions(Extensions extensions) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Context must not be empty") public boolean isEmpty() { return ( registration == null && diff --git a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java index 6a6cb37..c6399bc 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java +++ b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java @@ -53,7 +53,7 @@ public void setOther(List other) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "ContextActivities must not be empty") public boolean isEmpty() { return ( parent == null && grouping == null && diff --git a/src/main/java/com/yetanalytics/xapi/model/Extensions.java b/src/main/java/com/yetanalytics/xapi/model/Extensions.java index f564562..31c4836 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Extensions.java +++ b/src/main/java/com/yetanalytics/xapi/model/Extensions.java @@ -157,7 +157,7 @@ public Map getMap() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Extensions must not be empty") public boolean isEmpty() { return extMap.isEmpty(); } diff --git a/src/main/java/com/yetanalytics/xapi/model/Group.java b/src/main/java/com/yetanalytics/xapi/model/Group.java index bb3a8e6..5cee30e 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Group.java +++ b/src/main/java/com/yetanalytics/xapi/model/Group.java @@ -29,7 +29,7 @@ public void setMember(List member) { } @JsonIgnore - @AssertTrue + @AssertTrue(message = "Anonymous Group must have member property") public boolean isAnonymousOrIdentifiedGroup() { return ( (countIFIs() == 0 && member != null) || @@ -55,7 +55,7 @@ public boolean isValidAuthority() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Group must not be empty") public boolean isEmpty() { // zero-length member arrays still count as non-empty return super.isEmpty() && member == null; diff --git a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java index 2bbcbb9..0b8a901 100644 --- a/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java +++ b/src/main/java/com/yetanalytics/xapi/model/InteractionComponent.java @@ -38,7 +38,7 @@ public void setDescription(LangMap description) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "InteractionComponent must not be empty") public boolean isEmpty() { return id == null && description == null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/LangMap.java b/src/main/java/com/yetanalytics/xapi/model/LangMap.java index 5b935ef..05e012c 100644 --- a/src/main/java/com/yetanalytics/xapi/model/LangMap.java +++ b/src/main/java/com/yetanalytics/xapi/model/LangMap.java @@ -115,7 +115,7 @@ public Map getMap() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Language Map must not be empty") public boolean isEmpty() { return languageHashMap.isEmpty(); } diff --git a/src/main/java/com/yetanalytics/xapi/model/Result.java b/src/main/java/com/yetanalytics/xapi/model/Result.java index a0dee7e..20dd137 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Result.java +++ b/src/main/java/com/yetanalytics/xapi/model/Result.java @@ -63,7 +63,7 @@ public void setExtensions(Extensions extensions) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Result must not be empty") public boolean isEmpty() { return ( score == null && diff --git a/src/main/java/com/yetanalytics/xapi/model/Score.java b/src/main/java/com/yetanalytics/xapi/model/Score.java index 9a73eac..923ed05 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Score.java +++ b/src/main/java/com/yetanalytics/xapi/model/Score.java @@ -54,7 +54,7 @@ public void setScaled(BigDecimal scaled) { // Validation @JsonIgnore - @AssertTrue + @AssertTrue(message = "Min Score must be less than Raw Score") public boolean isMinLessThanRaw() { if (raw != null && min != null) { return min.compareTo(raw) < 0; @@ -64,7 +64,7 @@ public boolean isMinLessThanRaw() { } @JsonIgnore - @AssertTrue + @AssertTrue(message = "Raw Score must be less than Max Score") public boolean isRawLessThanMax() { if (raw != null && max != null) { return raw.compareTo(max) < 0; @@ -74,7 +74,7 @@ public boolean isRawLessThanMax() { } @JsonIgnore - @AssertTrue + @AssertTrue(message = "Min Score must be less than Max Score") public boolean isMinLessThanMax() { if (min != null && max != null) { return min.compareTo(max) < 0; @@ -85,7 +85,7 @@ public boolean isMinLessThanMax() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Score must not be empty") public boolean isEmpty() { return raw == null && min == null && max == null && scaled == null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/Statement.java b/src/main/java/com/yetanalytics/xapi/model/Statement.java index daed1f7..fd3cf40 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Statement.java +++ b/src/main/java/com/yetanalytics/xapi/model/Statement.java @@ -151,7 +151,7 @@ public void setAttachments(List attachments) { // Validation @JsonIgnore - @AssertTrue + @AssertTrue(message = "Voiding statements must have a StatementRef as their object") public boolean isValidVoidingStatement() { if (verb != null && verb.isVoiding()) { return object instanceof StatementRef; @@ -169,7 +169,7 @@ private boolean isObjectActivity() { } @JsonIgnore - @AssertTrue + @AssertTrue(message = "Context.revision must not be present if object is not an Activity") public boolean isValidContextRevision() { return ( isObjectActivity() || @@ -179,7 +179,7 @@ public boolean isValidContextRevision() { } @JsonIgnore - @AssertTrue + @AssertTrue(message = "Context.platform must not be present if object is not an Activity") public boolean isValidContextPlatform() { return ( isObjectActivity() || @@ -222,7 +222,7 @@ public boolean isValidSubStatement() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Statement must not be empty") public boolean isEmpty() { return ( id == null && diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java index 21eeb45..d5966f8 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementRef.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementRef.java @@ -30,7 +30,7 @@ public void setId(UUID id) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "StatementRef must not be empty") public boolean isEmpty() { return id == null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java index 809cbaa..dfcb682 100644 --- a/src/main/java/com/yetanalytics/xapi/model/StatementResult.java +++ b/src/main/java/com/yetanalytics/xapi/model/StatementResult.java @@ -39,7 +39,7 @@ public void setMore(URI more) { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "StatementResult must not be empty") public boolean isEmpty() { return statements == null && more == null; } diff --git a/src/main/java/com/yetanalytics/xapi/model/Verb.java b/src/main/java/com/yetanalytics/xapi/model/Verb.java index 39d3873..d470e8b 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Verb.java +++ b/src/main/java/com/yetanalytics/xapi/model/Verb.java @@ -51,7 +51,7 @@ public boolean isVoiding() { @Override @JsonIgnore - @AssertFalse + @AssertFalse(message = "Verb must not be empty") public boolean isEmpty() { return id == null && display == null; } diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java index 5a8a891..3f0983e 100644 --- a/src/test/java/com/yetanalytics/model/AgentTest.java +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -2,6 +2,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import com.yetanalytics.xapi.model.Account; import com.yetanalytics.xapi.model.Agent; +import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; public class AgentTest { @@ -84,6 +86,7 @@ public void testInvalidAccount() { @Test public void testNoIFI() { + Set> violations = validator.validate(agent); // One error for empty agent, one error for missing IFI ValidationUtils.assertInvalid(validator, agent, 2); } diff --git a/src/test/java/com/yetanalytics/model/StatementTest.java b/src/test/java/com/yetanalytics/model/StatementTest.java index fea45fd..bd8fc04 100644 --- a/src/test/java/com/yetanalytics/model/StatementTest.java +++ b/src/test/java/com/yetanalytics/model/StatementTest.java @@ -65,6 +65,7 @@ public void testEmptyStatement() { statement.setVerb(null); statement.setObject(null); // One error for empty statement, one error each for missing property + ValidationUtils.assertInvalid(validator, statement, 4); } From c1439be1ccd09b5f525ab63380a6557bafbb9795 Mon Sep 17 00:00:00 2001 From: Cliff Casey Date: Wed, 8 Jul 2026 09:52:35 -0400 Subject: [PATCH 26/28] added validation helper, and tests, and some additional validation spec tests --- README.md | 15 ++++- .../xapi/util/StatementValidator.java | 64 ++++++++++++++++++ .../com/yetanalytics/model/AgentTest.java | 3 - .../xapi/client/StatementClientTest.java | 1 + .../client/filters/StatementFiltersTest.java | 6 -- .../xapi/util/StatementValidatorTest.java | 65 +++++++++++++++++++ .../yetanalytics/xapi/util/TestFileUtils.java | 39 ++++++++++- src/test/resources/statements/basic.json | 2 + .../resources/statements/invalid-field.json | 18 +++++ .../statements/invalid-json.json.err | 32 +++++++++ .../resources/statements/invalid-spec.json | 18 +++++ 11 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/yetanalytics/xapi/util/StatementValidator.java create mode 100644 src/test/java/com/yetanalytics/xapi/util/StatementValidatorTest.java create mode 100644 src/test/resources/statements/invalid-field.json create mode 100644 src/test/resources/statements/invalid-json.json.err create mode 100644 src/test/resources/statements/invalid-spec.json diff --git a/README.md b/README.md index 56dbc5a..ddac342 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,20 @@ More methods will be added in future to support other resources and also attachm ## xAPI Validation -Coming Soon... +The xAPI model in this library can be used for statement validation using the `jakarta.validation` standard. You can either validate specific parts of a statement using the `jakarta.validation` API directly, or you can use the included helper which takes either a `String` or `Statement` argument and returns a validation object (including the `Statement` object, if valid): + + +``` +String statement = "..."; +StatementValidator validator = new StatementValidator(); + +// validate by string +StatementValidationResult result = validator.validateStatement(stmt); + +result.isValid(); // boolean +result.getErrors(); // Set containing the violation messages +result.getStatement(); // valid Statement object (if isValid() is true) +``` ## License diff --git a/src/main/java/com/yetanalytics/xapi/util/StatementValidator.java b/src/main/java/com/yetanalytics/xapi/util/StatementValidator.java new file mode 100644 index 0000000..cc9f5fd --- /dev/null +++ b/src/main/java/com/yetanalytics/xapi/util/StatementValidator.java @@ -0,0 +1,64 @@ +package com.yetanalytics.xapi.util; + +import java.util.Set; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.yetanalytics.xapi.model.Statement; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +public class StatementValidator { + + private Validator validator; + + public StatementValidator() { + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + public Validator getValidator() { + return this.validator; + } + + public StatementValidationResult validateStatement(String statementJson) { + try { + Statement statement = Mapper.getMapper().readValue(statementJson,Statement.class); + return validateStatement(statement); + } catch (JsonProcessingException e) { + Set errors = Set.of("Invalid JSON: " + e.getMessage()); + return new StatementValidationResult(false, errors, null); + } + } + + public StatementValidationResult validateStatement(Statement statement) { + Set errors = this.validator.validate(statement).stream() + .map(v -> v.getPropertyPath() + " " + v.getMessage()) + .collect(java.util.stream.Collectors.toSet()); + boolean valid = errors.isEmpty(); + return new StatementValidationResult(valid, errors, valid ? statement : null); + } + + public class StatementValidationResult { + private boolean valid; + private Set errors; + private Statement statement; + + public StatementValidationResult(boolean valid, Set errors, Statement statement) { + this.valid = valid; + this.errors = errors; + this.statement = statement; + } + + public boolean isValid() { + return valid; + } + + public Set getErrors() { + return errors; + } + + public Statement getStatement() { + return statement; + } + } +} diff --git a/src/test/java/com/yetanalytics/model/AgentTest.java b/src/test/java/com/yetanalytics/model/AgentTest.java index 3f0983e..5a8a891 100644 --- a/src/test/java/com/yetanalytics/model/AgentTest.java +++ b/src/test/java/com/yetanalytics/model/AgentTest.java @@ -2,7 +2,6 @@ import java.net.URI; import java.net.URISyntaxException; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -11,7 +10,6 @@ import com.yetanalytics.xapi.model.Account; import com.yetanalytics.xapi.model.Agent; -import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; public class AgentTest { @@ -86,7 +84,6 @@ public void testInvalidAccount() { @Test public void testNoIFI() { - Set> violations = validator.validate(agent); // One error for empty agent, one error for missing IFI ValidationUtils.assertInvalid(validator, agent, 2); } diff --git a/src/test/java/com/yetanalytics/xapi/client/StatementClientTest.java b/src/test/java/com/yetanalytics/xapi/client/StatementClientTest.java index 3ed1e6d..477ce8d 100644 --- a/src/test/java/com/yetanalytics/xapi/client/StatementClientTest.java +++ b/src/test/java/com/yetanalytics/xapi/client/StatementClientTest.java @@ -50,6 +50,7 @@ private static Map getContainerEnv(){ return map; } + @SuppressWarnings({ "rawtypes", "unchecked", "resource" }) private static GenericContainer lrs = new GenericContainer("yetanalytics/lrsql:latest") .withEnv(getContainerEnv()) diff --git a/src/test/java/com/yetanalytics/xapi/client/filters/StatementFiltersTest.java b/src/test/java/com/yetanalytics/xapi/client/filters/StatementFiltersTest.java index 4a19df9..b52d2b9 100644 --- a/src/test/java/com/yetanalytics/xapi/client/filters/StatementFiltersTest.java +++ b/src/test/java/com/yetanalytics/xapi/client/filters/StatementFiltersTest.java @@ -3,19 +3,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import java.io.IOException; import java.net.URI; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.util.UUID; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.exc.StreamReadException; -import com.fasterxml.jackson.databind.DatabindException; import com.yetanalytics.xapi.model.Agent; diff --git a/src/test/java/com/yetanalytics/xapi/util/StatementValidatorTest.java b/src/test/java/com/yetanalytics/xapi/util/StatementValidatorTest.java new file mode 100644 index 0000000..a016f42 --- /dev/null +++ b/src/test/java/com/yetanalytics/xapi/util/StatementValidatorTest.java @@ -0,0 +1,65 @@ +package com.yetanalytics.xapi.util; + +import static org.junit.Assert.assertTrue; + +import java.util.Set; + +import org.junit.Test; + +import com.yetanalytics.xapi.model.Activity; +import com.yetanalytics.xapi.model.LangTag; +import com.yetanalytics.xapi.model.Statement; + +public class StatementValidatorTest { + + private StatementValidator validator = new StatementValidator(); + + @Test + public void testValidStatement() { + String validStatementJson = TestFileUtils.getJsonTestFileString("basic"); + StatementValidator.StatementValidationResult result = validator.validateStatement(validStatementJson); + assert (result.isValid()); + Statement statement = result.getStatement(); + assert (statement != null); + assertTrue(statement.getId() != null); + Activity object = (Activity) statement.getObject(); + assert (object != null); + assertTrue(object.getDefinition().getDescription().get(LangTag.parse("en-us")) + .equals("The First Activity")); + } + + @Test + public void testInvalidJSONStatement() { + String invalidStatementJson = TestFileUtils.getJsonTestFileString("invalid-json"); + StatementValidator.StatementValidationResult result = validator.validateStatement(invalidStatementJson); + assert (!result.isValid()); + assert (result.getErrors().size() > 0); + result.getErrors().stream().findFirst().ifPresent(error -> assertTrue(error.startsWith("Invalid JSON:"))); + } + + @Test + public void testInvalidFieldStatement() { + String invalidStatementJson = TestFileUtils.getJsonTestFileString("invalid-field"); + StatementValidator.StatementValidationResult result = validator.validateStatement(invalidStatementJson); + assert (!result.isValid()); + assert (result.getStatement() == null); + assert (result.getErrors().size() > 0); + result.getErrors().stream().findFirst().ifPresent(error -> assertTrue(error.contains("descriptionz"))); + } + + @Test + public void testInvalidSpecStatement() { + String invalidStatementJson = TestFileUtils.getJsonTestFileString("invalid-spec"); + StatementValidator.StatementValidationResult result = validator.validateStatement(invalidStatementJson); + assert (!result.isValid()); + assert (result.getStatement() == null); + assert (result.getErrors().size() > 0); + assertTrue(errorsContain(result.getErrors(), "verb may not be null")); + assertTrue(errorsContain(result.getErrors(), "actor.identifiedAgent Agent must have exactly 1 Inverse Functional Identifier (IFI)")); + assertTrue(errorsContain(result.getErrors(), "actor.empty Agent must not be empty")); + } + + private boolean errorsContain(Set errors, String substring) { + return errors.stream().anyMatch(error -> error.contains(substring)); + } +} diff --git a/src/test/java/com/yetanalytics/xapi/util/TestFileUtils.java b/src/test/java/com/yetanalytics/xapi/util/TestFileUtils.java index 7a59117..e04cd18 100644 --- a/src/test/java/com/yetanalytics/xapi/util/TestFileUtils.java +++ b/src/test/java/com/yetanalytics/xapi/util/TestFileUtils.java @@ -1,10 +1,43 @@ package com.yetanalytics.xapi.util; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TestFileUtils { - public static File getJsonTestFile(String filename){ - String path = String.format("src/test/resources/statements/%s.json", filename); - return new File(path); + + private static final Logger log = LoggerFactory.getLogger(TestFileUtils.class); + + public static File getJsonTestFile(String filename) throws IOException { + Path filepath = getTestFilePath(filename); + + return filepath.toFile(); + } + + public static String getJsonTestFileString(String filename){ + Path filepath = getTestFilePath(filename); + try { + return Files.readString(filepath); + } catch (Exception e) { + log.error("Failed to read test file: " + filepath.toString(), e); + return null; + } } + + private static Path getTestFilePath(String filename) { + String filePathString = String.format("src/test/resources/statements/%s.json", filename); + Path filepath = Path.of(filePathString); + + // handle badly formatted json in a way that doesnt make them flag errors in IDEs + if (filepath == null || !Files.exists(filepath)) + filepath = Path.of(String.format("%s.err", filePathString)); + + return filepath; + } + + } diff --git a/src/test/resources/statements/basic.json b/src/test/resources/statements/basic.json index 96da417..8dd03bf 100644 --- a/src/test/resources/statements/basic.json +++ b/src/test/resources/statements/basic.json @@ -1,3 +1,5 @@ + + { "id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8", "actor": { diff --git a/src/test/resources/statements/invalid-field.json b/src/test/resources/statements/invalid-field.json new file mode 100644 index 0000000..406457c --- /dev/null +++ b/src/test/resources/statements/invalid-field.json @@ -0,0 +1,18 @@ +{ + "id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8", + "actor": { + "name": "Cliff Casey" + }, + "object": { + "id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1", + "definition": { + "name": { + "en-us": "Activity 1" + }, + "descriptionz": { + "en-us": "The First Activity" + } + } + }, + "timestamp": "2023-10-27T09:03:21.723Z" +} diff --git a/src/test/resources/statements/invalid-json.json.err b/src/test/resources/statements/invalid-json.json.err new file mode 100644 index 0000000..a7b4fa8 --- /dev/null +++ b/src/test/resources/statements/invalid-json.json.err @@ -0,0 +1,32 @@ +{ + "id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8", + "actor": { + "name": "Cliff Casey", + "account": { + "homePage": "https://users.training.com", + "name": "23897525" + } + }, + "verb": { + "id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/verbs/set", + "display": { "en-us": "Set" } + }, + "object": { + "id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1", + "definition": { + "name": { + "en-us": "Activity 1" + }, + "description": { + "en-us": "The First Activity" + } + } + }, + "authority": { + "name": "Yet Analytics Inc", + "mbox": "mailto:authority@yetanalytics.com" + }, + "version": "1.0.3", + "stored": "2023-10-27T09:03:21.722Z" + "timestamp": "2023-10-27T09:03:21.723Z" + diff --git a/src/test/resources/statements/invalid-spec.json b/src/test/resources/statements/invalid-spec.json new file mode 100644 index 0000000..b3daf40 --- /dev/null +++ b/src/test/resources/statements/invalid-spec.json @@ -0,0 +1,18 @@ +{ + "id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8", + "actor": { + "name": "Cliff Casey" + }, + "object": { + "id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1", + "definition": { + "name": { + "en-us": "Activity 1" + }, + "description": { + "en-us": "The First Activity" + } + } + }, + "timestamp": "2023-10-27T09:03:21.723Z" +} From cac79ef3aa23e2b9cbbe54358ebb8c84859b63d4 Mon Sep 17 00:00:00 2001 From: Cliff Casey Date: Wed, 8 Jul 2026 10:48:45 -0400 Subject: [PATCH 27/28] version + some missing valid enforcement for children --- pom.xml | 2 +- src/main/java/com/yetanalytics/xapi/model/Context.java | 6 ++++++ .../java/com/yetanalytics/xapi/model/ContextActivities.java | 5 +++++ src/main/java/com/yetanalytics/xapi/model/Statement.java | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c53e290..dba4927 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.yetanalytics xapi-tools jar - 0.0.4 + 0.0.5 xAPI Tools Java Serialization Model and Tools for xAPI Standard (IEEE 9274.1.1) https://github.com/yetanalytics/java-xapi-tools diff --git a/src/main/java/com/yetanalytics/xapi/model/Context.java b/src/main/java/com/yetanalytics/xapi/model/Context.java index c4537c5..692430d 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Context.java +++ b/src/main/java/com/yetanalytics/xapi/model/Context.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.Valid; import jakarta.validation.constraints.AssertFalse; /** @@ -16,13 +17,18 @@ public class Context implements JSONObject { private UUID registration; + + @Valid private AbstractActor instructor; + @Valid private Group team; + @Valid private ContextActivities contextActivities; private String revision; private String platform; private LangTag language; private StatementRef statement; + @Valid private Extensions extensions; public UUID getRegistration() { diff --git a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java index c6399bc..705be71 100644 --- a/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java +++ b/src/main/java/com/yetanalytics/xapi/model/ContextActivities.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.yetanalytics.xapi.model.deserializers.ContextActivityListDeserializer; +import jakarta.validation.Valid; import jakarta.validation.constraints.AssertFalse; /** @@ -17,12 +18,16 @@ @JsonInclude(Include.NON_NULL) public class ContextActivities implements JSONObject { + @Valid @JsonDeserialize(using = ContextActivityListDeserializer.class) private List parent; + @Valid @JsonDeserialize(using = ContextActivityListDeserializer.class) private List grouping; + @Valid @JsonDeserialize(using = ContextActivityListDeserializer.class) private List category; + @Valid @JsonDeserialize(using = ContextActivityListDeserializer.class) private List other; diff --git a/src/main/java/com/yetanalytics/xapi/model/Statement.java b/src/main/java/com/yetanalytics/xapi/model/Statement.java index fd3cf40..baffb46 100644 --- a/src/main/java/com/yetanalytics/xapi/model/Statement.java +++ b/src/main/java/com/yetanalytics/xapi/model/Statement.java @@ -58,6 +58,7 @@ public class Statement extends AbstractObject { @JsonSerialize(using = SemverSerializer.class) private Semver version; + @Valid private List attachments; public UUID getId() { From 252704e308f4c39536c30503f73f59a8fa369245 Mon Sep 17 00:00:00 2001 From: Cliff Casey Date: Wed, 8 Jul 2026 10:54:54 -0400 Subject: [PATCH 28/28] whitespace --- src/test/resources/statements/basic.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/resources/statements/basic.json b/src/test/resources/statements/basic.json index 8dd03bf..96da417 100644 --- a/src/test/resources/statements/basic.json +++ b/src/test/resources/statements/basic.json @@ -1,5 +1,3 @@ - - { "id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8", "actor": {