mirror of
https://codeberg.org/PGPainless/sop-java.git
synced 2025-09-07 17:29:44 +02:00
Add test for JSON data parsing and serializing using a dummy implementation
This commit is contained in:
parent
ebfde35422
commit
cdcbae7e5f
4 changed files with 208 additions and 29 deletions
|
@ -45,7 +45,8 @@ class SopVCLI {
|
|||
@JvmField var EXECUTABLE_NAME = "sopv"
|
||||
|
||||
@JvmField
|
||||
@CommandLine.Option(names = ["--stacktrace", "--debug"], scope = CommandLine.ScopeType.INHERIT)
|
||||
@CommandLine.Option(
|
||||
names = ["--stacktrace", "--debug"], scope = CommandLine.ScopeType.INHERIT)
|
||||
var stacktrace = false
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -10,6 +10,15 @@ import sop.enums.SignatureMode
|
|||
import sop.util.Optional
|
||||
import sop.util.UTCUtil
|
||||
|
||||
/**
|
||||
* Metadata about a verified signature.
|
||||
*
|
||||
* @param creationTime creation time of the signature
|
||||
* @param signingKeyFingerprint fingerprint of the (sub-)key that issued the signature
|
||||
* @param signingCertFingerprint fingerprint of the certificate that contains the signing key
|
||||
* @param signatureMode optional signature mode (text/binary)
|
||||
* @param jsonOrDescription arbitrary text or JSON data
|
||||
*/
|
||||
data class Verification(
|
||||
val creationTime: Date,
|
||||
val signingKeyFingerprint: String,
|
||||
|
@ -47,27 +56,28 @@ data class Verification(
|
|||
Optional.ofNullable(signatureMode),
|
||||
Optional.of(jsonSerializer.serialize(json)))
|
||||
|
||||
@Deprecated("Replaced by jsonOrDescription",
|
||||
replaceWith = ReplaceWith("jsonOrDescription")
|
||||
)
|
||||
@Deprecated("Replaced by jsonOrDescription", replaceWith = ReplaceWith("jsonOrDescription"))
|
||||
val description = jsonOrDescription
|
||||
|
||||
/** This value is `true` if the [Verification] contains extension JSON. */
|
||||
val containsJson: Boolean =
|
||||
jsonOrDescription.get()?.trim()?.let { it.startsWith("{") && it.endsWith("}") } ?: false
|
||||
|
||||
/**
|
||||
* Attempt to parse the [jsonOrDescription] field using the provided [JSONParser] and return the result.
|
||||
* This method returns `null` if parsing fails.
|
||||
* Attempt to parse the [jsonOrDescription] field using the provided [JSONParser] and return the
|
||||
* result. This method returns `null` if parsing fails.
|
||||
*
|
||||
* @param parser [JSONParser] implementation
|
||||
* @return successfully parsed [JSON] POJO or `null`.
|
||||
*/
|
||||
fun getJson(parser: JSONParser): JSON? {
|
||||
return jsonOrDescription.get()
|
||||
?.let {
|
||||
try {
|
||||
parser.parse(it)
|
||||
} catch (e: ParseException) {
|
||||
null
|
||||
}
|
||||
return jsonOrDescription.get()?.let {
|
||||
try {
|
||||
parser.parse(it)
|
||||
} catch (e: ParseException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
|
@ -116,35 +126,37 @@ data class Verification(
|
|||
/**
|
||||
* POJO data class representing JSON metadata.
|
||||
*
|
||||
* @param signers list of supplied CERTS objects that could have issued the signature, identified by
|
||||
* the name given on the command line.
|
||||
* @param signers list of supplied CERTS objects that could have issued the signature,
|
||||
* identified by the name given on the command line.
|
||||
* @param comment a freeform UTF-8 encoded text describing the verification
|
||||
* @param ext an extension object containing arbitrary, implementation-specific data
|
||||
*/
|
||||
data class JSON(
|
||||
val signers: List<String>,
|
||||
val comment: String?,
|
||||
val ext: Any?)
|
||||
data class JSON(val signers: List<String>, val comment: String?, val ext: Any?) {
|
||||
|
||||
/**
|
||||
* Interface abstracting a JSON parser that parses [JSON] POJOs from single-line strings.
|
||||
*/
|
||||
/** Create a JSON object with only a list of signers. */
|
||||
constructor(signers: List<String>) : this(signers, null, null)
|
||||
|
||||
/** Create a JSON object with only a single signer. */
|
||||
constructor(signer: String) : this(listOf(signer))
|
||||
}
|
||||
|
||||
/** Interface abstracting a JSON parser that parses [JSON] POJOs from single-line strings. */
|
||||
fun interface JSONParser {
|
||||
/**
|
||||
* Parse a [JSON] POJO from the given single-line [string].
|
||||
* If the string does not represent a JSON object matching the [JSON] definition,
|
||||
* this method throws a [ParseException].
|
||||
* Parse a [JSON] POJO from the given single-line [string]. If the string does not represent
|
||||
* a JSON object matching the [JSON] definition, this method throws a [ParseException].
|
||||
*
|
||||
* @param string [String] representation of the [JSON] object.
|
||||
* @return parsed [JSON] POJO
|
||||
* @throws ParseException if the [string] is not a JSON string representing the [JSON] object.
|
||||
* @throws ParseException if the [string] is not a JSON string representing the [JSON]
|
||||
* object.
|
||||
*/
|
||||
@Throws(ParseException::class)
|
||||
fun parse(string: String): JSON
|
||||
@Throws(ParseException::class) fun parse(string: String): JSON
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface abstracting a JSON serializer that converts [JSON] POJOs into single-line JSON strings.
|
||||
* Interface abstracting a JSON serializer that converts [JSON] POJOs into single-line JSON
|
||||
* strings.
|
||||
*/
|
||||
fun interface JSONSerializer {
|
||||
|
||||
|
|
163
sop-java/src/test/java/sop/VerificationJSONTest.java
Normal file
163
sop-java/src/test/java/sop/VerificationJSONTest.java
Normal file
|
@ -0,0 +1,163 @@
|
|||
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.enums.SignatureMode;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class VerificationJSONTest {
|
||||
|
||||
// A hacky self-made "JSON parser" stand-in.
|
||||
// Only used for testing, do not use in production!
|
||||
private Verification.JSONParser dummyParser = new Verification.JSONParser() {
|
||||
@NotNull
|
||||
@Override
|
||||
public Verification.JSON parse(@NotNull String string) throws ParseException {
|
||||
if (!string.startsWith("{")) {
|
||||
throw new ParseException("Alleged JSON String does not begin with '{'", 0);
|
||||
}
|
||||
if (!string.endsWith("}")) {
|
||||
throw new ParseException("Alleged JSON String does not end with '}'", string.length() - 1);
|
||||
}
|
||||
|
||||
List<String> signersList = new ArrayList<>();
|
||||
Matcher signersMat = Pattern.compile("\"signers\": \\[(.*?)\\]").matcher(string);
|
||||
if (signersMat.find()) {
|
||||
String signersCat = signersMat.group(1);
|
||||
String[] split = signersCat.split(",");
|
||||
for (String s : split) {
|
||||
s = s.trim();
|
||||
signersList.add(s.substring(1, s.length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
String comment = null;
|
||||
Matcher commentMat = Pattern.compile("\"comment\": \"(.*?)\"").matcher(string);
|
||||
if (commentMat.find()) {
|
||||
comment = commentMat.group(1);
|
||||
}
|
||||
|
||||
String ext = null;
|
||||
Matcher extMat = Pattern.compile("\"ext\": (.*?})}").matcher(string);
|
||||
if (extMat.find()) {
|
||||
ext = extMat.group(1);
|
||||
}
|
||||
|
||||
return new Verification.JSON(signersList, comment, ext);
|
||||
}
|
||||
};
|
||||
|
||||
// A just as hacky "JSON Serializer" lookalike.
|
||||
// Also don't use in production, for testing only!
|
||||
private Verification.JSONSerializer dummySerializer = new Verification.JSONSerializer() {
|
||||
@NotNull
|
||||
@Override
|
||||
public String serialize(@NotNull Verification.JSON json) {
|
||||
if (json.getSigners().isEmpty() && json.getComment() == null && json.getExt() == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
boolean comma = false;
|
||||
|
||||
if (!json.getSigners().isEmpty()) {
|
||||
comma = true;
|
||||
sb.append("\"signers\": [");
|
||||
for (Iterator<String> iterator = json.getSigners().iterator(); iterator.hasNext(); ) {
|
||||
String signer = iterator.next();
|
||||
sb.append("\"").append(signer).append("\"");
|
||||
if (iterator.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
}
|
||||
|
||||
if (json.getComment() != null) {
|
||||
if (comma) {
|
||||
sb.append(", ");
|
||||
}
|
||||
comma = true;
|
||||
sb.append("\"comment\": \"").append(json.getComment()).append("\"");
|
||||
}
|
||||
|
||||
if (json.getExt() != null) {
|
||||
if (comma) {
|
||||
sb.append(", ");
|
||||
}
|
||||
comma = true;
|
||||
sb.append("\"ext\": ").append(json.getExt().toString());
|
||||
}
|
||||
return sb.append("}").toString();
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testSimpleSerializeParse() throws ParseException {
|
||||
String signer = "alice.pub";
|
||||
Verification.JSON json = new Verification.JSON(signer);
|
||||
|
||||
String string = dummySerializer.serialize(json);
|
||||
assertEquals("{\"signers\": [\"alice.pub\"]}", string);
|
||||
|
||||
Verification.JSON parsed = dummyParser.parse(string);
|
||||
assertEquals(signer, parsed.getSigners().get(0));
|
||||
assertEquals(1, parsed.getSigners().size());
|
||||
assertNull(parsed.getComment());
|
||||
assertNull(parsed.getExt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdvancedSerializeParse() throws ParseException {
|
||||
Verification.JSON json = new Verification.JSON(
|
||||
Arrays.asList("../certs/alice.pub", "/etc/pgp/certs.pgp"),
|
||||
"This is a comment",
|
||||
"{\"Foo\": \"Bar\"}");
|
||||
|
||||
String serialized = dummySerializer.serialize(json);
|
||||
assertEquals("{\"signers\": [\"../certs/alice.pub\", \"/etc/pgp/certs.pgp\"], \"comment\": \"This is a comment\", \"ext\": {\"Foo\": \"Bar\"}}",
|
||||
serialized);
|
||||
|
||||
Verification.JSON parsed = dummyParser.parse(serialized);
|
||||
assertEquals(json.getSigners(), parsed.getSigners());
|
||||
assertEquals(json.getComment(), parsed.getComment());
|
||||
assertEquals(json.getExt(), parsed.getExt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerificationWithSimpleJson() {
|
||||
String string = "2019-10-29T18:36:45Z EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E mode:text {\"signers\": [\"alice.pgp\"]}";
|
||||
Verification verification = Verification.fromString(string);
|
||||
|
||||
assertTrue(verification.getContainsJson());
|
||||
assertEquals("EB85BB5FA33A75E15E944E63F231550C4F47E38E", verification.getSigningKeyFingerprint());
|
||||
assertEquals("EB85BB5FA33A75E15E944E63F231550C4F47E38E", verification.getSigningCertFingerprint());
|
||||
assertEquals(SignatureMode.text, verification.getSignatureMode().get());
|
||||
|
||||
Verification.JSON json = verification.getJson(dummyParser);
|
||||
assertNotNull(json, "The verification string MUST contain valid extension json");
|
||||
|
||||
assertEquals(Collections.singletonList("alice.pgp"), json.getSigners());
|
||||
assertNull(json.getComment());
|
||||
assertNull(json.getExt());
|
||||
|
||||
verification = new Verification(verification.getCreationTime(), verification.getSigningKeyFingerprint(), verification.getSigningCertFingerprint(), verification.getSignatureMode().get(), json, dummySerializer);
|
||||
assertEquals(string, verification.toString());
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import java.text.ParseException;
|
|||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class VerificationTest {
|
||||
|
@ -25,6 +26,8 @@ public class VerificationTest {
|
|||
Verification verification = new Verification(signDate, keyFP, certFP);
|
||||
assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B", verification.toString());
|
||||
|
||||
assertFalse(verification.getContainsJson());
|
||||
|
||||
VerificationAssert.assertThatVerification(verification)
|
||||
.issuedBy(certFP)
|
||||
.isBySigningKey(keyFP)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue