Skip to content

Commit

Permalink
Fixed 6 flaky tests caused by unordered non-deterministic data-struct…
Browse files Browse the repository at this point in the history
…ures in lti/tsugi-util
  • Loading branch information
hermya committed Nov 11, 2024
1 parent a51a09f commit ea211da
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 43 deletions.
8 changes: 7 additions & 1 deletion lti/tsugi-util/src/java/org/tsugi/jackson/JacksonUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import lombok.extern.slf4j.Slf4j;

import java.util.Map;

/**
* Some Tsugi Utility code for to make using Jackson easier to use.
*/
Expand All @@ -35,7 +37,7 @@ public static String prettyPrint(Object obj)
throws com.fasterxml.jackson.core.JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();

// ***IMPORTANT!!!*** for Jackson 2.x use the line below instead of the one above:
// ***IMPORTANT!!!*** for Jackson 2.x use the line below instead of the one above:
// ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
// return mapper.writeValueAsString(obj);
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
Expand Down Expand Up @@ -73,4 +75,8 @@ public static ObjectMapper getLaxObjectMapper()
return mapper;
}

public static Map<String, Object> getHashMapFromJSONString(String keySetJSON) throws JsonProcessingException {
return new ObjectMapper().readValue(keySetJSON, Map.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Map;
import java.util.TreeMap;

import org.tsugi.jackson.JacksonUtil;
import org.tsugi.lti13.objects.GroupService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -38,7 +39,7 @@ public void testConstructor() throws com.fasterxml.jackson.core.JsonProcessingEx
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(gs);
String jsonTest = "{\"scope\":[\"https://purl.imsglobal.org/spec/lti-gs/scope/contextgroup.readonly\"],\"context_groups_url\":\"https://www.myuniv.example.com/2344/groups\",\"context_group_sets_url\":\"https://www.myuniv.example.com/2344/groups/sets\",\"service_versions\":[\"1.0\"]}";
assertEquals(jsonString, jsonTest);
assertEquals(JacksonUtil.getHashMapFromJSONString(jsonString), JacksonUtil.getHashMapFromJSONString(jsonTest));
}

@Test
Expand Down
19 changes: 15 additions & 4 deletions lti/tsugi-util/src/test/org/tsugi/lti13/LTI13KeySetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import java.util.TreeMap;
import static org.junit.Assert.*;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.Test;
import org.tsugi.jackson.JacksonUtil;

/**
*
Expand All @@ -24,7 +26,7 @@ public class LTI13KeySetTest {

@Test
public void testKeySets() throws
NoSuchAlgorithmException, NoSuchProviderException, java.security.spec.InvalidKeySpecException {
NoSuchAlgorithmException, NoSuchProviderException, java.security.spec.InvalidKeySpecException, JsonProcessingException {

String serialized = "-----BEGIN PUBLIC KEY-----\n"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApgviDRUN1Z6hIOBg5uj1k\n"
Expand All @@ -41,7 +43,7 @@ public void testKeySets() throws
RSAPublicKey rsaPublic = (RSAPublicKey) publicKey;

String keySetJSON = LTI13KeySetUtil.getKeySetJSON(rsaPublic);
boolean good = keySetJSON.contains("{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",");
boolean good = checkKeySetJSONGoodness(keySetJSON);
if (!good) {
System.out.println("keyset JSON is bad\n");
System.out.println(keySetJSON);
Expand All @@ -57,14 +59,23 @@ public void testKeySets() throws
keys.put(kid, rsaPublic);

String keySetJSON2 = LTI13KeySetUtil.getKeySetJSON(keys);
good = keySetJSON2.contains("{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",");
good = checkKeySetJSONGoodness(keySetJSON2);
if (!good) {
System.out.println("keyset JSON is bad\n");
System.out.println(keySetJSON);
}
assertTrue(good);

assertEquals(keySetJSON, keySetJSON2);
assertEquals(JacksonUtil.getHashMapFromJSONString(keySetJSON), JacksonUtil.getHashMapFromJSONString(keySetJSON2));
}

private boolean checkKeySetJSONGoodness(String keySetJSON){
boolean good = keySetJSON.contains("{\"keys\":");
if (!good) {
return false;
}
good = keySetJSON.contains("\"kty\":\"RSA\"") && keySetJSON.contains("\"e\":\"AQAB\"") ;
return good;
}

@Test
Expand Down
69 changes: 39 additions & 30 deletions lti/tsugi-util/src/test/org/tsugi/lti13/LTI13NimbusTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void testRSAPaulGray() throws
/*
{"kty":"RSA","e":"AQAB","n":"rTbpgFsy-cho8KY7j9AtbAdQcVU6d1lYGobsOYs_gdgObf-_2uSEiPfHcs9Lz_v41XP2XToCbna1ejHj0xRds2LY3MpOHxy3UV3bk5GQp4c8eg1ydHEF4DpcIdzP2P3QDFeSppJjO3bakA0Atp20iubNZNmO0x42fbrRgYQmkPrpE-ShbNIWhq0FaRDPDg_o2R0rB9IliAfilZgwiGpzvWmOnmaB1maE4WpnWAo4gul8nMBQL0YDbIdCHi3qUx1cnXFgZwufMR27ZZ6xgvt_AY94_KBuuAC1XrQpqEpO5i7t3_tT2_OfAnh6GjXluAa06Iv3JNGfBox81a0h6qxfsw"}
*/
boolean good = keyStr.contains("{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":");
boolean good = checkKeySetJSONGoodness(keyStr, false);
if (!good) {
System.out.println("rsaKey\n" + keyStr);
}
Expand Down Expand Up @@ -84,14 +84,23 @@ public void testRSAFromString() throws
/*
{"keys":[{"kty":"RSA","e":"AQAB","n":"pgviDRUN1Z6hIOBg5uj1kKSJjfJjayEJeJR7A06sm5K4QjYKYMve55LaD8CMqf98l_gnZ0vIaCuf4G9mkphc_yV0cgFY65wQmecPxv3IZ77wbJ-g5lL5vuCVTbh55nD--cj_hSBznXecQTXQNV9d51rCa65-PQ-YL1oRnrpUuLNPbdnc8kT_ZUq5Ic0WJM-NprN1tbbn2LafBY-igqbRQVoxIt75B8cd-35iQAUm8B4sw8zGs1bFpBy3A8rhCYcBAOdK2iSSudK2WEfW1E7RWnnNvw3ykMoVh1pq7zwL4P0IHXevvPnja-PmAT9zTwgU8WhiiIKl7YtJzkR9pEWtTw"}]}
*/
boolean good = keysetJSON.contains("{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",");
boolean good = checkKeySetJSONGoodness(keysetJSON, true);
if (!good) {
System.out.println("keyset JSON is bad\n");
System.out.println(keysetJSON);
}
assertTrue(good);
}

private boolean checkKeySetJSONGoodness(String keySetJSON, boolean withKeysAsParent){
boolean good = keySetJSON.contains("{\"keys\":");
if (!good && withKeysAsParent) {
return false;
}
good = keySetJSON.contains("\"kty\":\"RSA\"") && keySetJSON.contains("\"e\":\"AQAB\"") ;
return good;
}

@Test
public void testBrokenDeserialization() {
String bad_serialized = "-----BEGIN PUBLIC KEY-----\n"
Expand Down Expand Up @@ -122,44 +131,44 @@ public void testBrokenDeserialization() {

}

// https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/4.2/src-html/com/nimbusds/jose/jwk/JWKSet.html
@Test
public void testKeySetParse()
throws java.text.ParseException, com.nimbusds.jose.JOSEException
{
// https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/4.2/src-html/com/nimbusds/jose/jwk/JWKSet.html
@Test
public void testKeySetParse()
throws java.text.ParseException, com.nimbusds.jose.JOSEException
{
String kid = "49e4cfe6d3280fec019c92abe85b2747ffd98d19845b99373dbadc741286288c";

String json = "{\n" +
"\"keys\": [\n" +
"{\n" +
"\"kty\": \"RSA\",\n" +
"\"alg\": \"RS256\",\n" +
"\"e\": \"AQAB\",\n" +
"\"n\": \"sEhARJcwaQwI1FyzNLrGN1gUklL8Dwqte2TzHdNztskzdwXhca5HDMwIWmQ6oLoPaoyP10BzNUdV8iyrKncDPc2eZRIOwNhHF2mmWr1Ed2d2uK5ME0CYSV2XXgUyFV2dyB8IQmP9QoPgRyLE1HvwkovB7N87xv21ACOqMCad5EXJH4SIltdAoYKjuDfTTQJbnWwO6LLK0jK2-H-bdqj7_EBAHLFiOs5g9_ndts-oGndC75wCIdEgAG77ZLWVZ7ikhKhlMTirxW-tDgDoLpzUNiS2x4mTM8omxtP8yfThKW-wUUtgSA0KCuM_PCA55IZa1d9HQQyQVBZ7Dt-EMaAW9Q\",\n" +
"\"kid\": \"49e4cfe6d3280fec019c92abe85b2747ffd98d19845b99373dbadc741286288c\",\n" +
"\"use\": \"sig\"\n" +
"}\n" +
"]\n" +
"}\n";
String json = "{\n" +
"\"keys\": [\n" +
"{\n" +
"\"kty\": \"RSA\",\n" +
"\"alg\": \"RS256\",\n" +
"\"e\": \"AQAB\",\n" +
"\"n\": \"sEhARJcwaQwI1FyzNLrGN1gUklL8Dwqte2TzHdNztskzdwXhca5HDMwIWmQ6oLoPaoyP10BzNUdV8iyrKncDPc2eZRIOwNhHF2mmWr1Ed2d2uK5ME0CYSV2XXgUyFV2dyB8IQmP9QoPgRyLE1HvwkovB7N87xv21ACOqMCad5EXJH4SIltdAoYKjuDfTTQJbnWwO6LLK0jK2-H-bdqj7_EBAHLFiOs5g9_ndts-oGndC75wCIdEgAG77ZLWVZ7ikhKhlMTirxW-tDgDoLpzUNiS2x4mTM8omxtP8yfThKW-wUUtgSA0KCuM_PCA55IZa1d9HQQyQVBZ7Dt-EMaAW9Q\",\n" +
"\"kid\": \"49e4cfe6d3280fec019c92abe85b2747ffd98d19845b99373dbadc741286288c\",\n" +
"\"use\": \"sig\"\n" +
"}\n" +
"]\n" +
"}\n";

String actual_public_key_string = "-----BEGIN PUBLIC KEY-----\n"
+"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsEhARJcwaQwI1FyzNLrG\n"
+"N1gUklL8Dwqte2TzHdNztskzdwXhca5HDMwIWmQ6oLoPaoyP10BzNUdV8iyrKncD\n"
+"Pc2eZRIOwNhHF2mmWr1Ed2d2uK5ME0CYSV2XXgUyFV2dyB8IQmP9QoPgRyLE1Hvw\n"
+"kovB7N87xv21ACOqMCad5EXJH4SIltdAoYKjuDfTTQJbnWwO6LLK0jK2+H+bdqj7\n"
+"/EBAHLFiOs5g9/ndts+oGndC75wCIdEgAG77ZLWVZ7ikhKhlMTirxW+tDgDoLpzU\n"
+"NiS2x4mTM8omxtP8yfThKW+wUUtgSA0KCuM/PCA55IZa1d9HQQyQVBZ7Dt+EMaAW\n"
+"9QIDAQAB\n"
+"-----END PUBLIC KEY-----\n";
+"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsEhARJcwaQwI1FyzNLrG\n"
+"N1gUklL8Dwqte2TzHdNztskzdwXhca5HDMwIWmQ6oLoPaoyP10BzNUdV8iyrKncD\n"
+"Pc2eZRIOwNhHF2mmWr1Ed2d2uK5ME0CYSV2XXgUyFV2dyB8IQmP9QoPgRyLE1Hvw\n"
+"kovB7N87xv21ACOqMCad5EXJH4SIltdAoYKjuDfTTQJbnWwO6LLK0jK2+H+bdqj7\n"
+"/EBAHLFiOs5g9/ndts+oGndC75wCIdEgAG77ZLWVZ7ikhKhlMTirxW+tDgDoLpzU\n"
+"NiS2x4mTM8omxtP8yfThKW+wUUtgSA0KCuM/PCA55IZa1d9HQQyQVBZ7Dt+EMaAW\n"
+"9QIDAQAB\n"
+"-----END PUBLIC KEY-----\n";


// https://docs.oracle.com/javase/7/docs/api/java/security/interfaces/RSAPublicKey.html
Key publicKey = LTI13Util.string2PublicKey(actual_public_key_string);
java.security.interfaces.RSAKey rsaPublicKey = (java.security.interfaces.RSAKey) publicKey;
// System.out.println("rsaPublicKey="+rsaPublicKey);

com.nimbusds.jose.jwk.JWKSet localKeys = com.nimbusds.jose.jwk.JWKSet.parse(json);
// System.out.println("JWKSet="+localKeys);
com.nimbusds.jose.jwk.JWKSet localKeys = com.nimbusds.jose.jwk.JWKSet.parse(json);
// System.out.println("JWKSet="+localKeys);

com.nimbusds.jose.jwk.RSAKey nimbusPublic = (com.nimbusds.jose.jwk.RSAKey) localKeys.getKeyByKeyId(kid);
// System.out.println("nimbusPublic="+nimbusPublic);
Expand All @@ -170,5 +179,5 @@ public void testKeySetParse()

assertEquals(rsaPublicKey, rsaPublicKey2);

}
}
}
15 changes: 8 additions & 7 deletions lti/tsugi-util/src/test/org/tsugi/lti13/LTI13ObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.Assert.*;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.Test;

import org.tsugi.lti13.objects.LaunchJWT;
Expand Down Expand Up @@ -150,7 +151,7 @@ public void testOne() throws com.fasterxml.jackson.core.JsonProcessingException
}

@Test
public void testTwo() {
public void testTwo() throws JsonProcessingException {
LTIPlatformConfiguration lpc = new LTIPlatformConfiguration();
LTILaunchMessage mp = new LTILaunchMessage();
mp.type = LaunchJWT.MESSAGE_TYPE_LAUNCH;
Expand All @@ -172,7 +173,7 @@ public void testTwo() {
if ( ! expected.equals(pcs) ) {
System.out.println(pcs);
}
assertEquals(pcs, expected);
assertEquals(JacksonUtil.getHashMapFromJSONString(expected), JacksonUtil.getHashMapFromJSONString(pcs));
}

@Test
Expand All @@ -189,15 +190,15 @@ public void testThree() throws com.fasterxml.jackson.core.JsonProcessingExceptio
String expected =
"{\"https://purl.imsglobal.org/spec/lti/claim/message_type\":\"LtiResourceLinkRequest\",\"https://purl.imsglobal.org/spec/lti/claim/version\":\"1.3.0\",\"https://purl.imsglobal.org/spec/lti/claim/roles\":[],\"https://purl.imsglobal.org/spec/lti/claim/role_scope_mentor\":[],\"https://purl.imsglobal.org/spec/lti/claim/launch_presentation\":{\"document_target\":\"iframe\"}}";
String ljs = JacksonUtil.toString(lj);
assertEquals(expected,ljs);
assertEquals(JacksonUtil.getHashMapFromJSONString(expected),JacksonUtil.getHashMapFromJSONString(ljs));

lj = new LaunchJWT(LaunchJWT.MESSAGE_TYPE_LAUNCH);
lj.nonce = null; // Since we can't match random stuff
lj.expires = null; // Since we can't match random stuff
lj.issued = null; // Since we can't match random stuff
lj.jti = null; // Since we can't match random stuff
ljs = JacksonUtil.toString(lj);
assertEquals(expected,ljs);
assertEquals(JacksonUtil.getHashMapFromJSONString(expected), JacksonUtil.getHashMapFromJSONString(ljs));

lj = new LaunchJWT(LaunchJWT.MESSAGE_TYPE_DEEP_LINK);
lj.nonce = null; // Since we can't match random stuff
Expand All @@ -206,7 +207,7 @@ public void testThree() throws com.fasterxml.jackson.core.JsonProcessingExceptio
lj.jti = null; // Since we can't match random stuff
ljs = JacksonUtil.toString(lj);
String expected2 = expected.replaceAll("LtiResourceLinkRequest", "LtiDeepLinkingRequest");
assertEquals(expected2,ljs);
assertEquals(JacksonUtil.getHashMapFromJSONString(expected2), JacksonUtil.getHashMapFromJSONString(ljs));

lj = new LaunchJWT(LaunchJWT.MESSAGE_TYPE_LTI_DATA_PRIVACY_LAUNCH_REQUEST);
lj.nonce = null; // Since we can't match random stuff
Expand All @@ -215,7 +216,7 @@ public void testThree() throws com.fasterxml.jackson.core.JsonProcessingExceptio
lj.jti = null; // Since we can't match random stuff
ljs = JacksonUtil.toString(lj);
expected2 = expected.replaceAll("LtiResourceLinkRequest", "LtiDataPrivacyLaunchRequest");
assertEquals(expected2,ljs);
assertEquals(JacksonUtil.getHashMapFromJSONString(expected2), JacksonUtil.getHashMapFromJSONString(ljs));

lj = new LaunchJWT(LaunchJWT.MESSAGE_TYPE_LTI_SUBMISSION_REVIEW_REQUEST);
lj.nonce = null; // Since we can't match random stuff
Expand All @@ -224,7 +225,7 @@ public void testThree() throws com.fasterxml.jackson.core.JsonProcessingExceptio
lj.jti = null; // Since we can't match random stuff
ljs = JacksonUtil.toString(lj);
expected2 = expected.replaceAll("LtiResourceLinkRequest", "LtiSubmissionReviewRequest");
assertEquals(expected2,ljs);
assertEquals(JacksonUtil.getHashMapFromJSONString(expected2), JacksonUtil.getHashMapFromJSONString(ljs));
}

@Test
Expand Down

0 comments on commit ea211da

Please sign in to comment.