Compare commits

...

23 Commits
8.0.0 ... main

Author SHA1 Message Date
Paul Schaub 1d80ff1d8d
Update changelog 2024-03-30 19:02:58 +01:00
Paul Schaub 9356447226
Remove label() option from armor() operation 2024-03-30 19:00:09 +01:00
Paul Schaub a13f1e2a0d
Mark ProxyOutputStream as deprecated 2024-03-27 21:57:04 +01:00
Paul Schaub e39cc7f0ac
Remove deprecated junit5-system-exit
Replaced with custom test DSL that avoids System.exit
2024-03-27 21:50:01 +01:00
Paul Schaub cbbdd09472
SOP-Java 10.0.1-SNAPSHOT 2024-03-21 14:06:42 +01:00
Paul Schaub da6cba1d55
SOP-Java 10.0.0 2024-03-21 14:01:48 +01:00
Paul Schaub 4b2875d572
Update changelog 2024-03-21 13:54:56 +01:00
Paul Schaub 30f7ca90cd
Bump logback-core and logback-classic from 1.2.11 to 1.2.13 2024-03-21 13:54:56 +01:00
Paul Schaub bfa97aede8
Add description of external-sop module 2024-03-21 13:54:56 +01:00
Paul Schaub bdbc9593c8
Update spec revision and badge link 2024-03-21 13:54:56 +01:00
Paul Schaub 3643aff082
Bump version to 10.0.0 2024-03-21 13:54:55 +01:00
Paul Schaub ed9b2f5fef
Move signature verification operations to sopv interface subset 2024-03-21 13:54:55 +01:00
Paul Schaub cd208c8942
Add test ckecking that BadData is thrown if KEYS is passed for CERTS 2024-03-21 13:54:55 +01:00
Paul Schaub 7325cad696
Replace assumeTrue(false) with explicit TestAbortedException 2024-03-21 13:54:55 +01:00
Paul Schaub 7a825c7607
Fix javadoc reference 2024-03-21 13:54:55 +01:00
Paul Schaub 03f8950b16
Rename woodpecker files 2024-03-17 18:05:35 +01:00
Paul Schaub d5d7d67d6f
Fix reuse compliance 2024-03-17 17:58:04 +01:00
Paul Schaub e2a568e73e
Update issue templates 2024-02-26 11:03:16 +01:00
Paul Schaub 7092baee4f
SOP-Java 8.0.2-SNAPSHOT 2023-11-22 18:21:41 +01:00
Paul Schaub 592aecd646
SOP-Java 8.0.1 2023-11-22 18:19:13 +01:00
Paul Schaub e5e64003f3
decrypt: Do not throw NoSignature exception when verifications is empty 2023-11-22 17:23:06 +01:00
Paul Schaub 51d9c29837
decrypt --verify-with: Do not expect exit 3 when verifications is empty 2023-11-22 17:23:06 +01:00
Paul Schaub ae83ddcff6
SOP-Java 8.0.1-SNAPSHOT 2023-11-15 19:01:50 +01:00
39 changed files with 852 additions and 289 deletions

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**Version**
<!-- What versions of the following libraries are you using? -->
- `sop-java`:
- `pgpainless-core`:
- `bouncycastle`:
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
```
Example Code Block
```
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@ -22,3 +22,8 @@ License: Apache-2.0
Files: external-sop/src/main/resources/sop/testsuite/external/* Files: external-sop/src/main/resources/sop/testsuite/external/*
Copyright: 2023 the original author or authors Copyright: 2023 the original author or authors
License: Apache-2.0 License: Apache-2.0
# Github Issue Templates
Files: .github/ISSUE_TEMPLATE/*
Copyright: 2024 the original author or authors
License: Apache-2.0

View File

@ -6,6 +6,18 @@ SPDX-License-Identifier: Apache-2.0
# Changelog # Changelog
## 10.0.1-SNAPSHOT
- Remove `label()` option from `Armor` operation
## 10.0.0
- Update implementation to [SOP Specification revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html).
- Throw `BadData` when passing KEYS where CERTS are expected
- Introduce `sopv` interface subset with revision `1.0`
- Add `sop version --sopv`
## 8.0.1
- `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty.
## 8.0.0 ## 8.0.0
- Rewrote `sop-java` in Kotlin - Rewrote `sop-java` in Kotlin
- Rewrote `sop-java-picocli` in Kotlin - Rewrote `sop-java-picocli` in Kotlin

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
# SOP for Java # SOP for Java
[![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java)
[![Spec Revision: 8](https://img.shields.io/badge/Spec%20Revision-8-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/08/) [![Spec Revision: 10](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/10/)
[![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main)
[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java)
@ -25,6 +25,8 @@ The repository contains the following modules:
* [sop-java](/sop-java) defines a set of Java interfaces describing the Stateless OpenPGP Protocol. * [sop-java](/sop-java) defines a set of Java interfaces describing the Stateless OpenPGP Protocol.
* [sop-java-picocli](/sop-java-picocli) contains a wrapper application that transforms the `sop-java` API into a command line application * [sop-java-picocli](/sop-java-picocli) contains a wrapper application that transforms the `sop-java` API into a command line application
compatible with the SOP-CLI specification. compatible with the SOP-CLI specification.
* [external-sop](/external-sop) contains an API implementation that can be used to forward API calls to a SOP executable,
allowing to delegate the implementation logic to an arbitrary SOP CLI implementation.
## Known Implementations ## Known Implementations
(Please expand!) (Please expand!)

View File

@ -16,7 +16,7 @@ import sop.external.operation.*
import sop.operation.* import sop.operation.*
/** /**
* Implementation of the {@link SOP} API using an external SOP binary. * Implementation of the [SOP] API using an external SOP binary.
* *
* Instantiate an [ExternalSOP] object for the given binary and the given [TempDirProvider] using * Instantiate an [ExternalSOP] object for the given binary and the given [TempDirProvider] using
* empty environment variables. * empty environment variables.

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.external
import java.nio.file.Files
import java.util.*
import sop.SOPV
import sop.external.ExternalSOP.TempDirProvider
import sop.external.operation.DetachedVerifyExternal
import sop.external.operation.InlineVerifyExternal
import sop.external.operation.VersionExternal
import sop.operation.DetachedVerify
import sop.operation.InlineVerify
import sop.operation.Version
/**
* Implementation of the [SOPV] API subset using an external sopv/sop binary.
*
* Instantiate an [ExternalSOPV] object for the given binary and the given [TempDirProvider] using
* empty environment variables.
*
* @param binaryName name / path of the sopv binary
* @param tempDirProvider custom tempDirProvider
*/
class ExternalSOPV(
private val binaryName: String,
private val properties: Properties = Properties(),
private val tempDirProvider: TempDirProvider = defaultTempDirProvider()
) : SOPV {
override fun version(): Version = VersionExternal(binaryName, properties)
override fun detachedVerify(): DetachedVerify = DetachedVerifyExternal(binaryName, properties)
override fun inlineVerify(): InlineVerify =
InlineVerifyExternal(binaryName, properties, tempDirProvider)
companion object {
/**
* Default implementation of the [TempDirProvider] which stores temporary files in the
* systems temp dir ([Files.createTempDirectory]).
*
* @return default implementation
*/
@JvmStatic
fun defaultTempDirProvider(): TempDirProvider {
return TempDirProvider { Files.createTempDirectory("ext-sopv").toFile() }
}
}
}

View File

@ -7,7 +7,6 @@ package sop.external.operation
import java.io.InputStream import java.io.InputStream
import java.util.Properties import java.util.Properties
import sop.Ready import sop.Ready
import sop.enums.ArmorLabel
import sop.exception.SOPGPException import sop.exception.SOPGPException
import sop.external.ExternalSOP import sop.external.ExternalSOP
import sop.operation.Armor import sop.operation.Armor
@ -18,8 +17,6 @@ class ArmorExternal(binary: String, environment: Properties) : Armor {
private val commandList: MutableList<String> = mutableListOf(binary, "armor") private val commandList: MutableList<String> = mutableListOf(binary, "armor")
private val envList: List<String> = ExternalSOP.propertiesToEnv(environment) private val envList: List<String> = ExternalSOP.propertiesToEnv(environment)
override fun label(label: ArmorLabel): Armor = apply { commandList.add("--label=$label") }
@Throws(SOPGPException.BadData::class) @Throws(SOPGPException.BadData::class)
override fun data(data: InputStream): Ready = override fun data(data: InputStream): Ready =
ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data) ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data)

View File

@ -68,6 +68,10 @@ class VersionExternal(binary: String, environment: Properties) : Version {
return null return null
} }
override fun getSopVVersion(): String {
return executeForLines(commandList.plus("--sopv"))
}
override fun getSopSpecVersion(): String { override fun getSopSpecVersion(): String {
return executeForLines(commandList.plus("--sop-spec")) return executeForLines(commandList.plus("--sop-spec"))
} }

View File

@ -12,15 +12,12 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Testing Exit Codes in JUnit
// https://todd.ginsberg.com/post/testing-system-exit/
testImplementation "com.ginsberg:junit5-system-exit:$junitSysExitVersion"
// Mocking Components // Mocking Components
testImplementation "org.mockito:mockito-core:$mockitoVersion" testImplementation "org.mockito:mockito-core:$mockitoVersion"
// SOP // SOP
implementation(project(":sop-java")) implementation(project(":sop-java"))
testImplementation(testFixtures(project(":sop-java")))
// CLI // CLI
implementation "info.picocli:picocli:$picocliVersion" implementation "info.picocli:picocli:$picocliVersion"

View File

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.cli.picocli
import java.util.*
import kotlin.system.exitProcess
import picocli.AutoComplete
import picocli.CommandLine
import sop.SOPV
import sop.cli.picocli.commands.*
import sop.exception.SOPGPException
@CommandLine.Command(
name = "sopv",
resourceBundle = "msg_sop",
exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE,
subcommands =
[
// Meta subcommands
VersionCmd::class,
// signature verification subcommands
VerifyCmd::class,
InlineVerifyCmd::class,
// misc
CommandLine.HelpCommand::class,
AutoComplete.GenerateCompletion::class])
class SopVCLI {
companion object {
@JvmStatic private var sopvInstance: SOPV? = null
@JvmStatic
fun getSopV(): SOPV =
checkNotNull(sopvInstance) { cliMsg.getString("sop.error.runtime.no_backend_set") }
@JvmStatic
fun setSopVInstance(sopv: SOPV?) {
sopvInstance = sopv
}
@JvmField var cliMsg: ResourceBundle = ResourceBundle.getBundle("msg_sop")
@JvmField var EXECUTABLE_NAME = "sopv"
@JvmField
@CommandLine.Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT)
var stacktrace = false
@JvmStatic
fun main(vararg args: String) {
val exitCode = execute(*args)
if (exitCode != 0) {
exitProcess(exitCode)
}
}
@JvmStatic
fun execute(vararg args: String): Int {
// Set locale
CommandLine(InitLocale()).parseArgs(*args)
// Re-set bundle with updated locale
cliMsg = ResourceBundle.getBundle("msg_sop")
return CommandLine(SopVCLI::class.java)
.apply {
// explicitly set help command resource bundle
subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help"))
// Hide generate-completion command
subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true)
// overwrite executable name
commandName = EXECUTABLE_NAME
// setup exception handling
executionExceptionHandler = SOPExecutionExceptionHandler()
exitCodeExceptionMapper = SOPExceptionExitCodeMapper()
isCaseInsensitiveEnumValuesAllowed = true
}
.execute(*args)
}
}
/**
* Control the locale.
*
* @see <a href="https://picocli.info/#_controlling_the_locale">Picocli Readme</a>
*/
@CommandLine.Command
class InitLocale {
@CommandLine.Option(names = ["-l", "--locale"], descriptionKey = "sop.locale")
fun setLocale(locale: String) = Locale.setDefault(Locale(locale))
@CommandLine.Unmatched
var remainder: MutableList<String> =
mutableListOf() // ignore any other parameters and options in the first parsing phase
}
}

View File

@ -85,13 +85,10 @@ class DecryptCmd : AbstractSopCmd() {
@Throws(IOException::class) @Throws(IOException::class)
private fun writeVerifyOut(result: DecryptionResult) { private fun writeVerifyOut(result: DecryptionResult) {
verifyOut?.let { verifyOut?.let {
if (result.verifications.isEmpty()) { getOutput(it).use { out ->
val errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found") PrintWriter(out).use { pw ->
throw NoSignature(errorMsg) result.verifications.forEach { verification -> pw.println(verification) }
} }
getOutput(verifyOut).use { out ->
PrintWriter(out).use { pw -> result.verifications.forEach { pw.println(it) } }
} }
} }
} }

View File

@ -22,6 +22,7 @@ class VersionCmd : AbstractSopCmd() {
@Option(names = ["--extended"]) var extended: Boolean = false @Option(names = ["--extended"]) var extended: Boolean = false
@Option(names = ["--backend"]) var backend: Boolean = false @Option(names = ["--backend"]) var backend: Boolean = false
@Option(names = ["--sop-spec"]) var sopSpec: Boolean = false @Option(names = ["--sop-spec"]) var sopSpec: Boolean = false
@Option(names = ["--sopv"]) var sopv: Boolean = false
} }
override fun run() { override fun run() {
@ -47,5 +48,10 @@ class VersionCmd : AbstractSopCmd() {
println(version.getSopSpecVersion()) println(version.getSopSpecVersion())
return return
} }
if (exclusive!!.sopv) {
println(version.getSopVVersion())
return
}
} }
} }

View File

@ -2,7 +2,6 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
usage.header=Add ASCII Armor to standard input usage.header=Add ASCII Armor to standard input
label=Label to be used in the header and tail of the armoring
stacktrace=Print stacktrace stacktrace=Print stacktrace
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0

View File

@ -2,7 +2,6 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
usage.header=Schütze Standard-Eingabe mit ASCII Armor usage.header=Schütze Standard-Eingabe mit ASCII Armor
label=Label für Kopf- und Fußzeile der ASCII Armor
stacktrace=Stacktrace ausgeben stacktrace=Stacktrace ausgeben
# Generic TODO: Remove when bumping picocli to 4.7.0 # Generic TODO: Remove when bumping picocli to 4.7.0

View File

@ -2,7 +2,9 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
sop.name=sop sop.name=sop
sopv.name=sopv
usage.header=Stateless OpenPGP Protocol usage.header=Stateless OpenPGP Protocol
sopv.usage.header=Stateless OpenPGP Protocol - Signature Verification Interface Subset
locale=Locale for description texts locale=Locale for description texts
# Generic # Generic

View File

@ -2,7 +2,9 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
sop.name=sop sop.name=sop
sopv.name=sopv
usage.header=Stateless OpenPGP Protocol usage.header=Stateless OpenPGP Protocol
sopv.usage.header=Stateless OpenPGP Protocol - Signature Verification Interface Subset
locale=Gebietsschema für Beschreibungstexte locale=Gebietsschema für Beschreibungstexte
# Generic # Generic

View File

@ -6,12 +6,13 @@ package sop.cli.picocli;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedSubcommand;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.SOP; import sop.SOP;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
@ -34,20 +35,18 @@ import sop.operation.Version;
public class SOPTest { public class SOPTest {
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedSubcommand.EXIT_CODE)
public void assertExitOnInvalidSubcommand() { public void assertExitOnInvalidSubcommand() {
SOP sop = mock(SOP.class); SOP sop = mock(SOP.class);
SopCLI.setSopInstance(sop); SopCLI.setSopInstance(sop);
SopCLI.main(new String[] {"invalid"}); assertUnsupportedSubcommand(() -> SopCLI.execute("invalid"));
} }
@Test @Test
@ExpectSystemExitWithStatus(1)
public void assertThrowsIfNoSOPBackendSet() { public void assertThrowsIfNoSOPBackendSet() {
SopCLI.setSopInstance(null); SopCLI.setSopInstance(null);
// At this point, no SOP backend is set, so an InvalidStateException triggers exit(1) // At this point, no SOP backend is set, so an InvalidStateException triggers error code 1
SopCLI.main(new String[] {"armor"}); assertGenericError(() -> SopCLI.execute("armor"));
} }
@Test @Test

View File

@ -4,8 +4,6 @@
package sop.cli.picocli.commands; package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.Ready; import sop.Ready;
@ -24,6 +22,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
public class ArmorCmdTest { public class ArmorCmdTest {
@ -42,24 +42,22 @@ public class ArmorCmdTest {
@Test @Test
public void assertDataIsAlwaysCalled() throws SOPGPException.BadData, IOException { public void assertDataIsAlwaysCalled() throws SOPGPException.BadData, IOException {
SopCLI.main(new String[] {"armor"}); assertSuccess(() -> SopCLI.execute("armor"));
verify(armor, times(1)).data((InputStream) any()); verify(armor, times(1)).data((InputStream) any());
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void ifBadDataExit41() throws SOPGPException.BadData, IOException { public void ifBadDataExit41() throws SOPGPException.BadData, IOException {
when(armor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(armor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"armor"}); assertBadData(() -> SopCLI.execute("armor"));
} }
@Test @Test
@FailOnSystemExit
public void ifNoErrorsNoExit() { public void ifNoErrorsNoExit() {
when(sop.armor()).thenReturn(armor); when(sop.armor()).thenReturn(armor);
SopCLI.main(new String[] {"armor"}); assertSuccess(() -> SopCLI.execute("armor"));
} }
private static Ready nopReady() { private static Ready nopReady() {

View File

@ -9,12 +9,13 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.Ready; import sop.Ready;
@ -48,14 +49,13 @@ public class DearmorCmdTest {
@Test @Test
public void assertDataIsCalled() throws IOException, SOPGPException.BadData { public void assertDataIsCalled() throws IOException, SOPGPException.BadData {
SopCLI.main(new String[] {"dearmor"}); assertSuccess(() -> SopCLI.execute("dearmor"));
verify(dearmor, times(1)).data((InputStream) any()); verify(dearmor, times(1)).data((InputStream) any());
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void assertBadDataCausesExit41() throws IOException, SOPGPException.BadData { public void assertBadDataCausesExit41() throws IOException, SOPGPException.BadData {
when(dearmor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException("invalid armor"))); when(dearmor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException("invalid armor")));
SopCLI.main(new String[] {"dearmor"}); assertBadData(() -> SopCLI.execute("dearmor"));
} }
} }

View File

@ -4,7 +4,6 @@
package sop.cli.picocli.commands; package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatcher;
@ -21,6 +20,7 @@ import sop.operation.Decrypt;
import sop.util.HexUtil; import sop.util.HexUtil;
import sop.util.UTCUtil; import sop.util.UTCUtil;
import javax.annotation.Nonnull;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -41,6 +41,18 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertCannotDecrypt;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertIncompleteVerification;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertOutputExists;
import static sop.testsuite.assertions.SopExecutionAssertions.assertPasswordNotHumanReadable;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
public class DecryptCmdTest { public class DecryptCmdTest {
@ -73,47 +85,47 @@ public class DecryptCmdTest {
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void missingArgumentsExceptionCausesExit19() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException { public void missingArgumentsExceptionCausesExit19() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException {
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.MissingArg("Missing arguments.")); when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.MissingArg("Missing arguments."));
SopCLI.main(new String[] {"decrypt"}); assertMissingArg(() -> SopCLI.execute("decrypt"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void badDataExceptionCausesExit41() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException { public void badDataExceptionCausesExit41() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException {
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"decrypt"}); assertBadData(() -> SopCLI.execute("decrypt"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.PasswordNotHumanReadable.EXIT_CODE)
public void assertNotHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, public void assertNotHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable,
SOPGPException.UnsupportedOption, IOException { SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable"); File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable");
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.PasswordNotHumanReadable()); when(decrypt.withPassword(any())).thenThrow(new SOPGPException.PasswordNotHumanReadable());
SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()}); assertPasswordNotHumanReadable(() ->
SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath())
);
} }
@Test @Test
public void assertWithPasswordPassesPasswordDown() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { public void assertWithPasswordPassesPasswordDown() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("orange"); File passwordFile = TestFileUtil.writeTempStringFile("orange");
SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()}); assertSuccess(() -> SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath()));
verify(decrypt, times(1)).withPassword("orange"); verify(decrypt, times(1)).withPassword("orange");
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("swordfish"); File passwordFile = TestFileUtil.writeTempStringFile("swordfish");
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Decrypting with password not supported.")); when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Decrypting with password not supported."));
SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath())
);
} }
@Test @Test
public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption { public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption {
Date now = new Date(); Date now = new Date();
SopCLI.main(new String[] {"decrypt"}); assertSuccess(() -> SopCLI.execute("decrypt"));
verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME);
verify(decrypt, times(1)).verifyNotAfter( verify(decrypt, times(1)).verifyNotAfter(
ArgumentMatchers.argThat(argument -> { ArgumentMatchers.argThat(argument -> {
@ -124,7 +136,8 @@ public class DecryptCmdTest {
@Test @Test
public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption { public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"decrypt", "--verify-not-before", "-", "--verify-not-after", "-"}); assertSuccess(() ->
SopCLI.execute("decrypt", "--verify-not-before", "-", "--verify-not-after", "-"));
verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME);
verify(decrypt, times(1)).verifyNotAfter(AbstractSopCmd.END_OF_TIME); verify(decrypt, times(1)).verifyNotAfter(AbstractSopCmd.END_OF_TIME);
} }
@ -137,54 +150,57 @@ public class DecryptCmdTest {
return Math.abs(now.getTime() - argument.getTime()) <= 1000; return Math.abs(now.getTime() - argument.getTime()) <= 1000;
}; };
SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now", "--verify-not-after", "now"}); assertSuccess(() ->
SopCLI.execute("decrypt", "--verify-not-before", "now", "--verify-not-after", "now"));
verify(decrypt, times(1)).verifyNotAfter(ArgumentMatchers.argThat(isMaxOneSecOff)); verify(decrypt, times(1)).verifyNotAfter(ArgumentMatchers.argThat(isMaxOneSecOff));
verify(decrypt, times(1)).verifyNotBefore(ArgumentMatchers.argThat(isMaxOneSecOff)); verify(decrypt, times(1)).verifyNotBefore(ArgumentMatchers.argThat(isMaxOneSecOff));
} }
@Test @Test
@ExpectSystemExitWithStatus(1)
public void assertMalformedDateInNotBeforeCausesExit1() { public void assertMalformedDateInNotBeforeCausesExit1() {
// ParserException causes exit(1) // ParserException causes exit(1)
SopCLI.main(new String[] {"decrypt", "--verify-not-before", "invalid"}); assertGenericError(() ->
SopCLI.execute("decrypt", "--verify-not-before", "invalid"));
} }
@Test @Test
@ExpectSystemExitWithStatus(1)
public void assertMalformedDateInNotAfterCausesExit1() { public void assertMalformedDateInNotAfterCausesExit1() {
// ParserException causes exit(1) // ParserException causes exit(1)
SopCLI.main(new String[] {"decrypt", "--verify-not-after", "invalid"}); assertGenericError(() ->
SopCLI.execute("decrypt", "--verify-not-after", "invalid"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption { public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption {
when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); when(decrypt.verifyNotAfter(any())).thenThrow(
SopCLI.main(new String[] {"decrypt", "--verify-not-after", "now"}); new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--verify-not-after", "now"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption { public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption {
when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); when(decrypt.verifyNotBefore(any())).thenThrow(
SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now"}); new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--verify-not-before", "now"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE)
public void assertExistingSessionKeyOutFileCausesExit59() throws IOException { public void assertExistingSessionKeyOutFileCausesExit59() throws IOException {
File tempFile = File.createTempFile("existing-session-key-", ".tmp"); File tempFile = File.createTempFile("existing-session-key-", ".tmp");
tempFile.deleteOnExit(); tempFile.deleteOnExit();
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()}); assertOutputExists(() ->
SopCLI.execute("decrypt", "--session-key-out", tempFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void assertWhenSessionKeyCannotBeExtractedExit37() throws IOException { public void assertWhenSessionKeyCannotBeExtractedExit37() throws IOException {
Path tempDir = Files.createTempDirectory("session-key-out-dir"); Path tempDir = Files.createTempDirectory("session-key-out-dir");
File tempFile = new File(tempDir.toFile(), "session-key"); File tempFile = new File(tempDir.toFile(), "session-key");
tempFile.deleteOnExit(); tempFile.deleteOnExit();
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("decrypt", "--session-key-out", tempFile.getAbsolutePath()));
} }
@Test @Test
@ -209,8 +225,10 @@ public class DecryptCmdTest {
File verificationsFile = new File(tempDir.toFile(), "verifications"); File verificationsFile = new File(tempDir.toFile(), "verifications");
File keyFile = new File(tempDir.toFile(), "key.asc"); File keyFile = new File(tempDir.toFile(), "key.asc");
keyFile.createNewFile(); keyFile.createNewFile();
SopCLI.main(new String[] {"decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(), assertSuccess(() ->
"--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with", keyFile.getAbsolutePath()}); SopCLI.execute("decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(),
"--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with",
keyFile.getAbsolutePath()));
ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream(); ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream();
try (FileInputStream fileIn = new FileInputStream(sessionKeyFile)) { try (FileInputStream fileIn = new FileInputStream(sessionKeyFile)) {
@ -240,45 +258,49 @@ public class DecryptCmdTest {
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.CannotDecrypt.EXIT_CODE)
public void assertUnableToDecryptExceptionResultsInExit29() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { public void assertUnableToDecryptExceptionResultsInExit29() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.CannotDecrypt()); when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.CannotDecrypt());
SopCLI.main(new String[] {"decrypt"}); assertCannotDecrypt(() ->
SopCLI.execute("decrypt"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.NoSignature.EXIT_CODE) public void assertNoVerificationsIsOkay() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
public void assertNoSignatureExceptionCausesExit3() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { File tempFile = File.createTempFile("verify-with-", ".tmp");
File verifyOut = new File(tempFile.getParent(), "verifications.out");
verifyOut.deleteOnExit();
when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult<DecryptionResult>() { when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
@Override @Override
public DecryptionResult writeTo(OutputStream outputStream) throws SOPGPException.NoSignature { public DecryptionResult writeTo(@Nonnull OutputStream outputStream) throws SOPGPException.NoSignature {
throw new SOPGPException.NoSignature(); return new DecryptionResult(null, Collections.emptyList());
} }
}); });
SopCLI.main(new String[] {"decrypt"}); assertSuccess(() ->
SopCLI.execute("decrypt", "--verify-with", tempFile.getAbsolutePath(), "--verifications-out",
verifyOut.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void badDataInVerifyWithCausesExit41() throws IOException, SOPGPException.BadData { public void badDataInVerifyWithCausesExit41() throws IOException, SOPGPException.BadData {
when(decrypt.verifyWithCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(decrypt.verifyWithCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
File tempFile = File.createTempFile("verify-with-", ".tmp"); File tempFile = File.createTempFile("verify-with-", ".tmp");
SopCLI.main(new String[] {"decrypt", "--verify-with", tempFile.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("decrypt", "--verify-with", tempFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void unexistentCertFileCausesExit61() { public void unexistentCertFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--verify-with", "invalid"}); assertMissingInput(() ->
SopCLI.execute("decrypt", "--verify-with", "invalid"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE)
public void existingVerifyOutCausesExit59() throws IOException { public void existingVerifyOutCausesExit59() throws IOException {
File certFile = File.createTempFile("existing-verify-out-cert", ".asc"); File certFile = File.createTempFile("existing-verify-out-cert", ".asc");
File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp"); File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp");
SopCLI.main(new String[] {"decrypt", "--verifications-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); assertOutputExists(() -> SopCLI.execute("decrypt", "--verifications-out",
existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()));
} }
@Test @Test
@ -302,7 +324,9 @@ public class DecryptCmdTest {
} }
}); });
SopCLI.main(new String[] {"decrypt", "--verifications-out", verifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("decrypt", "--verifications-out", verifyOut.getAbsolutePath(),
"--verify-with", certFile.getAbsolutePath()));
try (BufferedReader reader = new BufferedReader(new FileReader(verifyOut))) { try (BufferedReader reader = new BufferedReader(new FileReader(verifyOut))) {
String line = reader.readLine(); String line = reader.readLine();
assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line); assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line);
@ -317,66 +341,64 @@ public class DecryptCmdTest {
File sessionKeyFile1 = TestFileUtil.writeTempStringFile(key1.toString()); File sessionKeyFile1 = TestFileUtil.writeTempStringFile(key1.toString());
File sessionKeyFile2 = TestFileUtil.writeTempStringFile(key2.toString()); File sessionKeyFile2 = TestFileUtil.writeTempStringFile(key2.toString());
SopCLI.main(new String[] {"decrypt", assertSuccess(() ->
"--with-session-key", sessionKeyFile1.getAbsolutePath(), SopCLI.execute("decrypt",
"--with-session-key", sessionKeyFile2.getAbsolutePath()}); "--with-session-key", sessionKeyFile1.getAbsolutePath(),
"--with-session-key", sessionKeyFile2.getAbsolutePath()));
verify(decrypt).withSessionKey(key1); verify(decrypt).withSessionKey(key1);
verify(decrypt).withSessionKey(key2); verify(decrypt).withSessionKey(key2);
} }
@Test @Test
@ExpectSystemExitWithStatus(1)
public void assertMalformedSessionKeysResultInExit1() throws IOException { public void assertMalformedSessionKeysResultInExit1() throws IOException {
File sessionKeyFile = TestFileUtil.writeTempStringFile("C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"); File sessionKeyFile = TestFileUtil.writeTempStringFile("C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137");
SopCLI.main(new String[] {"decrypt", assertGenericError(() ->
"--with-session-key", sessionKeyFile.getAbsolutePath()}); SopCLI.execute("decrypt",
"--with-session-key", sessionKeyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void assertBadDataInKeysResultsInExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { public void assertBadDataInKeysResultsInExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
File tempKeyFile = File.createTempFile("key-", ".tmp"); File tempKeyFile = File.createTempFile("key-", ".tmp");
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()}); assertBadData(() -> SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void assertKeyFileNotFoundCausesExit61() { public void assertKeyFileNotFoundCausesExit61() {
SopCLI.main(new String[] {"decrypt", "nonexistent-key"}); assertMissingInput(() -> SopCLI.execute("decrypt", "nonexistent-key"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE)
public void assertProtectedKeyCausesExit67() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData { public void assertProtectedKeyCausesExit67() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
File tempKeyFile = File.createTempFile("key-", ".tmp"); File tempKeyFile = File.createTempFile("key-", ".tmp");
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()}); assertKeyIsProtected(() -> SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE)
public void assertUnsupportedAlgorithmExceptionCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { public void assertUnsupportedAlgorithmExceptionCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new IOException())); when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new IOException()));
File tempKeyFile = File.createTempFile("key-", ".tmp"); File tempKeyFile = File.createTempFile("key-", ".tmp");
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()}); assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void assertMissingPassphraseFileCausesExit61() { public void assertMissingPassphraseFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--with-password", "missing"}); assertMissingInput(() ->
SopCLI.execute("decrypt", "--with-password", "missing"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void assertMissingSessionKeyFileCausesExit61() { public void assertMissingSessionKeyFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--with-session-key", "missing"}); assertMissingInput(() ->
SopCLI.execute("decrypt", "--with-session-key", "missing"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.IncompleteVerification.EXIT_CODE)
public void verifyOutWithoutVerifyWithCausesExit23() { public void verifyOutWithoutVerifyWithCausesExit23() {
SopCLI.main(new String[] {"decrypt", "--verifications-out", "out.file"}); assertIncompleteVerification(() ->
SopCLI.execute("decrypt", "--verifications-out", "out.file"));
} }
} }

View File

@ -4,7 +4,6 @@
package sop.cli.picocli.commands; package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -28,6 +27,17 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertCertCannotEncrypt;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyCannotSign;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertPasswordNotHumanReadable;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
public class EncryptCmdTest { public class EncryptCmdTest {
@ -50,48 +60,50 @@ public class EncryptCmdTest {
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) public void missingBothPasswordAndCertFileCausesMissingArg() {
public void missingBothPasswordAndCertFileCauseExit19() { assertMissingArg(() ->
SopCLI.main(new String[] {"encrypt", "--no-armor"}); SopCLI.execute("encrypt", "--no-armor"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void as_unsupportedEncryptAsCausesUnsupportedOption() throws SOPGPException.UnsupportedOption {
public void as_unsupportedEncryptAsCausesExit37() throws SOPGPException.UnsupportedOption {
when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting encryption mode not supported.")); when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting encryption mode not supported."));
SopCLI.main(new String[] {"encrypt", "--as", "Binary"}); assertUnsupportedOption(() ->
SopCLI.execute("encrypt", "--as", "Binary"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void as_invalidModeOptionCausesUnsupportedOption() {
public void as_invalidModeOptionCausesExit37() { assertUnsupportedOption(() ->
SopCLI.main(new String[] {"encrypt", "--as", "invalid"}); SopCLI.execute("encrypt", "--as", "invalid"));
} }
@Test @Test
public void as_modeIsPassedDown() throws SOPGPException.UnsupportedOption, IOException { public void as_modeIsPassedDown() throws SOPGPException.UnsupportedOption, IOException {
File passwordFile = TestFileUtil.writeTempStringFile("0rbit"); File passwordFile = TestFileUtil.writeTempStringFile("0rbit");
for (EncryptAs mode : EncryptAs.values()) { for (EncryptAs mode : EncryptAs.values()) {
SopCLI.main(new String[] {"encrypt", "--as", mode.name(), "--with-password", passwordFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("encrypt", "--as", mode.name(),
"--with-password", passwordFile.getAbsolutePath()));
verify(encrypt, times(1)).mode(mode); verify(encrypt, times(1)).mode(mode);
} }
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.PasswordNotHumanReadable.EXIT_CODE) public void withPassword_notHumanReadablePasswordCausesPWNotHumanReadable() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
public void withPassword_notHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
when(encrypt.withPassword("pretendThisIsNotReadable")).thenThrow(new SOPGPException.PasswordNotHumanReadable()); when(encrypt.withPassword("pretendThisIsNotReadable")).thenThrow(new SOPGPException.PasswordNotHumanReadable());
File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable"); File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); assertPasswordNotHumanReadable(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void withPassword_unsupportedWithPasswordCausesUnsupportedOption() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
public void withPassword_unsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException {
when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Encrypting with password not supported.")); when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Encrypting with password not supported."));
File passwordFile = TestFileUtil.writeTempStringFile("orange"); File passwordFile = TestFileUtil.writeTempStringFile("orange");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
} }
@Test @Test
@ -99,99 +111,107 @@ public class EncryptCmdTest {
File keyFile1 = File.createTempFile("sign-with-1-", ".asc"); File keyFile1 = File.createTempFile("sign-with-1-", ".asc");
File keyFile2 = File.createTempFile("sign-with-2-", ".asc"); File keyFile2 = File.createTempFile("sign-with-2-", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("password"); File passwordFile = TestFileUtil.writeTempStringFile("password");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile1.getAbsolutePath(), "--sign-with", keyFile2.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile1.getAbsolutePath(),
"--sign-with", keyFile2.getAbsolutePath()));
verify(encrypt, times(2)).signWith((InputStream) any()); verify(encrypt, times(2)).signWith((InputStream) any());
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void signWith_nonExistentKeyFileCausesMissingInput() {
public void signWith_nonExistentKeyFileCausesExit61() { assertMissingInput(() ->
SopCLI.main(new String[] {"encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"}); SopCLI.execute("encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE) public void signWith_keyIsProtectedCausesKeyIsProtected() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
File keyFile = File.createTempFile("sign-with", ".asc"); File keyFile = File.createTempFile("sign-with", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("starship"); File passwordFile = TestFileUtil.writeTempStringFile("starship");
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", passwordFile.getAbsolutePath()}); assertKeyIsProtected(() ->
SopCLI.execute("encrypt", "--sign-with", keyFile.getAbsolutePath(),
"--with-password", passwordFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE) public void signWith_unsupportedAsymmetricAlgoCausesUnsupportedAsymAlgo() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception())); when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
File keyFile = File.createTempFile("sign-with", ".asc"); File keyFile = File.createTempFile("sign-with", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("123456"); File passwordFile = TestFileUtil.writeTempStringFile("123456");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()}); assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.KeyCannotSign.EXIT_CODE) public void signWith_certCannotSignCausesKeyCannotSign() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
public void signWith_certCannotSignCausesExit79() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyCannotSign()); when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyCannotSign());
File keyFile = File.createTempFile("sign-with", ".asc"); File keyFile = File.createTempFile("sign-with", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("dragon"); File passwordFile = TestFileUtil.writeTempStringFile("dragon");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()}); assertKeyCannotSign(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void signWith_badDataCausesBadData() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
File keyFile = File.createTempFile("sign-with", ".asc"); File keyFile = File.createTempFile("sign-with", ".asc");
File passwordFile = TestFileUtil.writeTempStringFile("orange"); File passwordFile = TestFileUtil.writeTempStringFile("orange");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(),
"--sign-with", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void cert_nonExistentCertFileCausesMissingInput() {
public void cert_nonExistentCertFileCausesExit61() { assertMissingInput(() ->
SopCLI.main(new String[] {"encrypt", "invalid.asc"}); SopCLI.execute("encrypt", "invalid.asc"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE) public void cert_unsupportedAsymmetricAlgorithmCausesUnsupportedAsymAlg() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
public void cert_unsupportedAsymmetricAlgorithmCausesExit13() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception())); when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
File certFile = File.createTempFile("cert", ".asc"); File certFile = File.createTempFile("cert", ".asc");
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()}); assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("encrypt", certFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.CertCannotEncrypt.EXIT_CODE) public void cert_certCannotEncryptCausesCertCannotEncrypt() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
public void cert_certCannotEncryptCausesExit17() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.CertCannotEncrypt("Certificate cannot encrypt.", new Exception())); when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.CertCannotEncrypt("Certificate cannot encrypt.", new Exception()));
File certFile = File.createTempFile("cert", ".asc"); File certFile = File.createTempFile("cert", ".asc");
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()}); assertCertCannotEncrypt(() ->
SopCLI.execute("encrypt", certFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void cert_badDataCausesBadData() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
public void cert_badDataCausesExit41() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
File certFile = File.createTempFile("cert", ".asc"); File certFile = File.createTempFile("cert", ".asc");
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("encrypt", certFile.getAbsolutePath()));
} }
@Test @Test
public void noArmor_notCalledByDefault() throws IOException { public void noArmor_notCalledByDefault() throws IOException {
File passwordFile = TestFileUtil.writeTempStringFile("clownfish"); File passwordFile = TestFileUtil.writeTempStringFile("clownfish");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
verify(encrypt, never()).noArmor(); verify(encrypt, never()).noArmor();
} }
@Test @Test
public void noArmor_callGetsPassedDown() throws IOException { public void noArmor_callGetsPassedDown() throws IOException {
File passwordFile = TestFileUtil.writeTempStringFile("monkey"); File passwordFile = TestFileUtil.writeTempStringFile("monkey");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--no-armor"}); assertSuccess(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), "--no-armor"));
verify(encrypt, times(1)).noArmor(); verify(encrypt, times(1)).noArmor();
} }
@Test @Test
@ExpectSystemExitWithStatus(1) public void writeTo_ioExceptionCausesGenericError() throws IOException {
public void writeTo_ioExceptionCausesExit1() throws IOException {
when(encrypt.plaintext((InputStream) any())).thenReturn(new ReadyWithResult<EncryptionResult>() { when(encrypt.plaintext((InputStream) any())).thenReturn(new ReadyWithResult<EncryptionResult>() {
@Override @Override
public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException {
@ -199,6 +219,7 @@ public class EncryptCmdTest {
} }
}); });
File passwordFile = TestFileUtil.writeTempStringFile("wildcat"); File passwordFile = TestFileUtil.writeTempStringFile("wildcat");
SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); assertGenericError(() ->
SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath()));
} }
} }

View File

@ -10,12 +10,14 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.Ready; import sop.Ready;
@ -45,32 +47,34 @@ public class ExtractCertCmdTest {
@Test @Test
public void noArmor_notCalledByDefault() { public void noArmor_notCalledByDefault() {
SopCLI.main(new String[] {"extract-cert"}); assertSuccess(() ->
SopCLI.execute("extract-cert"));
verify(extractCert, never()).noArmor(); verify(extractCert, never()).noArmor();
} }
@Test @Test
public void noArmor_passedDown() { public void noArmor_passedDown() {
SopCLI.main(new String[] {"extract-cert", "--no-armor"}); assertSuccess(() ->
SopCLI.execute("extract-cert", "--no-armor"));
verify(extractCert, times(1)).noArmor(); verify(extractCert, times(1)).noArmor();
} }
@Test @Test
@ExpectSystemExitWithStatus(1) public void key_ioExceptionCausesGenericError() throws IOException, SOPGPException.BadData {
public void key_ioExceptionCausesExit1() throws IOException, SOPGPException.BadData {
when(extractCert.key((InputStream) any())).thenReturn(new Ready() { when(extractCert.key((InputStream) any())).thenReturn(new Ready() {
@Override @Override
public void writeTo(OutputStream outputStream) throws IOException { public void writeTo(OutputStream outputStream) throws IOException {
throw new IOException(); throw new IOException();
} }
}); });
SopCLI.main(new String[] {"extract-cert"}); assertGenericError(() ->
SopCLI.execute("extract-cert"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void key_badDataCausesBadData() throws IOException, SOPGPException.BadData {
public void key_badDataCausesExit41() throws IOException, SOPGPException.BadData {
when(extractCert.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(extractCert.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"extract-cert"}); assertBadData(() ->
SopCLI.execute("extract-cert"));
} }
} }

View File

@ -10,11 +10,14 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.InOrder; import org.mockito.InOrder;
@ -47,19 +50,22 @@ public class GenerateKeyCmdTest {
@Test @Test
public void noArmor_notCalledByDefault() { public void noArmor_notCalledByDefault() {
SopCLI.main(new String[] {"generate-key", "Alice"}); assertSuccess(() ->
SopCLI.execute("generate-key", "Alice"));
verify(generateKey, never()).noArmor(); verify(generateKey, never()).noArmor();
} }
@Test @Test
public void noArmor_passedDown() { public void noArmor_passedDown() {
SopCLI.main(new String[] {"generate-key", "--no-armor", "Alice"}); assertSuccess(() ->
SopCLI.execute("generate-key", "--no-armor", "Alice"));
verify(generateKey, times(1)).noArmor(); verify(generateKey, times(1)).noArmor();
} }
@Test @Test
public void userId_multipleUserIdsPassedDownInProperOrder() { public void userId_multipleUserIdsPassedDownInProperOrder() {
SopCLI.main(new String[] {"generate-key", "Alice <alice@pgpainless.org>", "Bob <bob@pgpainless.org>"}); assertSuccess(() ->
SopCLI.execute("generate-key", "Alice <alice@pgpainless.org>", "Bob <bob@pgpainless.org>"));
InOrder inOrder = Mockito.inOrder(generateKey); InOrder inOrder = Mockito.inOrder(generateKey);
inOrder.verify(generateKey).userId("Alice <alice@pgpainless.org>"); inOrder.verify(generateKey).userId("Alice <alice@pgpainless.org>");
@ -69,30 +75,32 @@ public class GenerateKeyCmdTest {
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void missingArgumentCausesExit19() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException { public void missingArgumentCausesExit19() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
// TODO: RFC4880-bis and the current Stateless OpenPGP CLI spec allow keys to have no user-ids, // TODO: RFC4880-bis and the current Stateless OpenPGP CLI spec allow keys to have no user-ids,
// so we might want to change this test in the future. // so we might want to change this test in the future.
when(generateKey.generate()).thenThrow(new SOPGPException.MissingArg("Missing user-id.")); when(generateKey.generate()).thenThrow(new SOPGPException.MissingArg("Missing user-id."));
SopCLI.main(new String[] {"generate-key"}); assertMissingArg(() ->
SopCLI.execute("generate-key"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE)
public void unsupportedAsymmetricAlgorithmCausesExit13() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException { public void unsupportedAsymmetricAlgorithmCausesExit13() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception())); when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
SopCLI.main(new String[] {"generate-key", "Alice"}); assertUnsupportedAsymmetricAlgo(() ->
SopCLI.execute("generate-key", "Alice"));
} }
@Test @Test
@ExpectSystemExitWithStatus(1) public void ioExceptionCausesGenericError() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
public void ioExceptionCausesExit1() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
when(generateKey.generate()).thenReturn(new Ready() { when(generateKey.generate()).thenReturn(new Ready() {
@Override @Override
public void writeTo(OutputStream outputStream) throws IOException { public void writeTo(OutputStream outputStream) throws IOException {
throw new IOException(); throw new IOException();
} }
}); });
SopCLI.main(new String[] {"generate-key", "Alice"});
assertGenericError(() ->
SopCLI.execute("generate-key", "Alice"));
} }
} }

View File

@ -4,7 +4,6 @@
package sop.cli.picocli.commands; package sop.cli.picocli.commands;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.ReadyWithResult; import sop.ReadyWithResult;
@ -26,6 +25,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
public class InlineDetachCmdTest { public class InlineDetachCmdTest {
@ -41,9 +42,9 @@ public class InlineDetachCmdTest {
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) public void testMissingSignaturesOutResultsInMissingArg() {
public void testMissingSignaturesOutResultsInExit19() { assertMissingArg(() ->
SopCLI.main(new String[] {"inline-detach"}); SopCLI.execute("inline-detach"));
} }
@Test @Test
@ -67,7 +68,8 @@ public class InlineDetachCmdTest {
} }
}); });
SopCLI.main(new String[] {"inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor"}); assertSuccess(() ->
SopCLI.execute("inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor"));
verify(inlineDetach, times(1)).noArmor(); verify(inlineDetach, times(1)).noArmor();
verify(inlineDetach, times(1)).message((InputStream) any()); verify(inlineDetach, times(1)).message((InputStream) any());
} }

View File

@ -10,13 +10,20 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertExpectedText;
import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError;
import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.ReadyWithResult; import sop.ReadyWithResult;
@ -54,70 +61,77 @@ public class SignCmdTest {
@Test @Test
public void as_optionsAreCaseInsensitive() { public void as_optionsAreCaseInsensitive() {
SopCLI.main(new String[] {"sign", "--as", "Binary", keyFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()}); SopCLI.execute("sign", "--as", "Binary", keyFile.getAbsolutePath()));
SopCLI.main(new String[] {"sign", "--as", "BINARY", keyFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("sign", "--as", "binary", keyFile.getAbsolutePath()));
assertSuccess(() ->
SopCLI.execute("sign", "--as", "BINARY", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void as_invalidOptionCausesExit37() { public void as_invalidOptionCausesExit37() {
SopCLI.main(new String[] {"sign", "--as", "Invalid", keyFile.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("sign", "--as", "Invalid", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(detachedSign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported.")); when(detachedSign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported."));
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("sign", "--as", "binary", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void key_nonExistentKeyFileCausesExit61() { public void key_nonExistentKeyFileCausesExit61() {
SopCLI.main(new String[] {"sign", "invalid.asc"}); assertMissingInput(() ->
SopCLI.execute("sign", "invalid.asc"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE)
public void key_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData { public void key_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); assertKeyIsProtected(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData { public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE)
public void key_missingKeyFileCausesExit19() { public void key_missingKeyFileCausesExit19() {
SopCLI.main(new String[] {"sign"}); assertMissingArg(() ->
SopCLI.execute("sign"));
} }
@Test @Test
public void noArmor_notCalledByDefault() { public void noArmor_notCalledByDefault() {
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
verify(detachedSign, never()).noArmor(); verify(detachedSign, never()).noArmor();
} }
@Test @Test
public void noArmor_passedDown() { public void noArmor_passedDown() {
SopCLI.main(new String[] {"sign", "--no-armor", keyFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("sign", "--no-armor", keyFile.getAbsolutePath()));
verify(detachedSign, times(1)).noArmor(); verify(detachedSign, times(1)).noArmor();
} }
@Test @Test
public void withKeyPassword_passedDown() { public void withKeyPassword_passedDown() {
SopCLI.main(new String[] {"sign", "--with-key-password", passFile.getAbsolutePath(), keyFile.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("sign",
"--with-key-password", passFile.getAbsolutePath(),
keyFile.getAbsolutePath()));
verify(detachedSign, times(1)).withKeyPassword("sw0rdf1sh"); verify(detachedSign, times(1)).withKeyPassword("sw0rdf1sh");
} }
@Test @Test
@ExpectSystemExitWithStatus(1)
public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText { public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText {
when(detachedSign.data((InputStream) any())).thenReturn(new ReadyWithResult<SigningResult>() { when(detachedSign.data((InputStream) any())).thenReturn(new ReadyWithResult<SigningResult>() {
@Override @Override
@ -125,13 +139,14 @@ public class SignCmdTest {
throw new IOException(); throw new IOException();
} }
}); });
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); assertGenericError(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.ExpectedText.EXIT_CODE)
public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText { public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText {
when(detachedSign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText()); when(detachedSign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText());
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); assertExpectedText(() ->
SopCLI.execute("sign", keyFile.getAbsolutePath()));
} }
} }

View File

@ -10,6 +10,11 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData;
import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput;
import static sop.testsuite.assertions.SopExecutionAssertions.assertNoSignature;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -21,7 +26,6 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -76,60 +80,75 @@ public class VerifyCmdTest {
@Test @Test
public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException { public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException {
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", "--not-after", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(date); verify(detachedVerify, times(1)).notAfter(date);
} }
@Test @Test
public void notAfter_now() throws SOPGPException.UnsupportedOption { public void notAfter_now() throws SOPGPException.UnsupportedOption {
Date now = new Date(); Date now = new Date();
SopCLI.main(new String[] {"verify", "--not-after", "now", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", "--not-after", "now",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(dateMatcher(now)); verify(detachedVerify, times(1)).notAfter(dateMatcher(now));
} }
@Test @Test
public void notAfter_dashCountsAsEndOfTime() throws SOPGPException.UnsupportedOption { public void notAfter_dashCountsAsEndOfTime() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"verify", "--not-after", "-", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", "--not-after", "-",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(AbstractSopCmd.END_OF_TIME); verify(detachedVerify, times(1)).notAfter(AbstractSopCmd.END_OF_TIME);
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(detachedVerify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); when(detachedVerify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("verify", "--not-after", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
} }
@Test @Test
public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException { public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException {
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", "--not-before", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notBefore(date); verify(detachedVerify, times(1)).notBefore(date);
} }
@Test @Test
public void notBefore_now() throws SOPGPException.UnsupportedOption { public void notBefore_now() throws SOPGPException.UnsupportedOption {
Date now = new Date(); Date now = new Date();
SopCLI.main(new String[] {"verify", "--not-before", "now", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", "--not-before", "now",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notBefore(dateMatcher(now)); verify(detachedVerify, times(1)).notBefore(dateMatcher(now));
} }
@Test @Test
public void notBefore_dashCountsAsBeginningOfTime() throws SOPGPException.UnsupportedOption { public void notBefore_dashCountsAsBeginningOfTime() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"verify", "--not-before", "-", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", "--not-before", "-",
signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME);
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(detachedVerify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); when(detachedVerify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertUnsupportedOption(() ->
SopCLI.execute("verify", "--not-before", "2019-10-29T18:36:45Z",
signature.getAbsolutePath(), cert.getAbsolutePath()));
} }
@Test @Test
public void notBeforeAndNotAfterAreCalledWithDefaultValues() throws SOPGPException.UnsupportedOption { public void notBeforeAndNotAfterAreCalledWithDefaultValues() throws SOPGPException.UnsupportedOption {
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
verify(detachedVerify, times(1)).notAfter(dateMatcher(new Date())); verify(detachedVerify, times(1)).notAfter(dateMatcher(new Date()));
verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME);
} }
@ -139,43 +158,43 @@ public class VerifyCmdTest {
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void cert_fileNotFoundCausesExit61() { public void cert_fileNotFoundCausesExit61() {
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), "invalid.asc"}); assertMissingInput(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), "invalid.asc"));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void cert_badDataCausesExit41() throws SOPGPException.BadData, IOException { public void cert_badDataCausesExit41() throws SOPGPException.BadData, IOException {
when(detachedVerify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(detachedVerify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE)
public void signature_fileNotFoundCausesExit61() { public void signature_fileNotFoundCausesExit61() {
SopCLI.main(new String[] {"verify", "invalid.sig", cert.getAbsolutePath()}); assertMissingInput(() ->
SopCLI.execute("verify", "invalid.sig", cert.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void signature_badDataCausesExit41() throws SOPGPException.BadData, IOException { public void signature_badDataCausesExit41() throws SOPGPException.BadData, IOException {
when(detachedVerify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(detachedVerify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.NoSignature.EXIT_CODE)
public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature()); when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature());
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertNoSignature(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertBadData(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
} }
@Test @Test
@ -192,7 +211,8 @@ public class VerifyCmdTest {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out)); System.setOut(new PrintStream(out));
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); assertSuccess(() ->
SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath()));
System.setOut(originalSout); System.setOut(originalSout);

View File

@ -4,19 +4,19 @@
package sop.cli.picocli.commands; package sop.cli.picocli.commands;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sop.SOP; import sop.SOP;
import sop.cli.picocli.SopCLI; import sop.cli.picocli.SopCLI;
import sop.exception.SOPGPException;
import sop.operation.Version; import sop.operation.Version;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess;
import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption;
public class VersionCmdTest { public class VersionCmdTest {
private Version version; private Version version;
@ -29,6 +29,8 @@ public class VersionCmdTest {
when(version.getVersion()).thenReturn("1.0"); when(version.getVersion()).thenReturn("1.0");
when(version.getExtendedVersion()).thenReturn("MockSop Extended Version Information"); when(version.getExtendedVersion()).thenReturn("MockSop Extended Version Information");
when(version.getBackendVersion()).thenReturn("Foo"); when(version.getBackendVersion()).thenReturn("Foo");
when(version.getSopSpecVersion()).thenReturn("draft-dkg-openpgp-stateless-cli-XX");
when(version.getSopVVersion()).thenReturn("1.0");
when(sop.version()).thenReturn(version); when(sop.version()).thenReturn(version);
SopCLI.setSopInstance(sop); SopCLI.setSopInstance(sop);
@ -36,26 +38,41 @@ public class VersionCmdTest {
@Test @Test
public void assertVersionCommandWorks() { public void assertVersionCommandWorks() {
SopCLI.main(new String[] {"version"}); assertSuccess(() ->
SopCLI.execute("version"));
verify(version, times(1)).getVersion(); verify(version, times(1)).getVersion();
verify(version, times(1)).getName(); verify(version, times(1)).getName();
} }
@Test @Test
public void assertExtendedVersionCommandWorks() { public void assertExtendedVersionCommandWorks() {
SopCLI.main(new String[] {"version", "--extended"}); assertSuccess(() ->
SopCLI.execute("version", "--extended"));
verify(version, times(1)).getExtendedVersion(); verify(version, times(1)).getExtendedVersion();
} }
@Test @Test
public void assertBackendVersionCommandWorks() { public void assertBackendVersionCommandWorks() {
SopCLI.main(new String[] {"version", "--backend"}); assertSuccess(() ->
SopCLI.execute("version", "--backend"));
verify(version, times(1)).getBackendVersion(); verify(version, times(1)).getBackendVersion();
} }
@Test @Test
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertSpecVersionCommandWorks() {
assertSuccess(() ->
SopCLI.execute("version", "--sop-spec"));
}
@Test
public void assertSOPVVersionCommandWorks() {
assertSuccess(() ->
SopCLI.execute("version", "--sopv"));
}
@Test
public void assertInvalidOptionResultsInExit37() { public void assertInvalidOptionResultsInExit37() {
SopCLI.main(new String[] {"version", "--invalid"}); assertUnsupportedOption(() ->
SopCLI.execute("version", "--invalid"));
} }
} }

View File

@ -9,16 +9,13 @@ import sop.operation.ChangeKeyPassword
import sop.operation.Dearmor import sop.operation.Dearmor
import sop.operation.Decrypt import sop.operation.Decrypt
import sop.operation.DetachedSign import sop.operation.DetachedSign
import sop.operation.DetachedVerify
import sop.operation.Encrypt import sop.operation.Encrypt
import sop.operation.ExtractCert import sop.operation.ExtractCert
import sop.operation.GenerateKey import sop.operation.GenerateKey
import sop.operation.InlineDetach import sop.operation.InlineDetach
import sop.operation.InlineSign import sop.operation.InlineSign
import sop.operation.InlineVerify
import sop.operation.ListProfiles import sop.operation.ListProfiles
import sop.operation.RevokeKey import sop.operation.RevokeKey
import sop.operation.Version
/** /**
* Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related
@ -26,10 +23,7 @@ import sop.operation.Version
* intended for reuse. If you for example need to generate multiple keys, make a dedicated call to * intended for reuse. If you for example need to generate multiple keys, make a dedicated call to
* [generateKey] once per key generation. * [generateKey] once per key generation.
*/ */
interface SOP { interface SOP : SOPV {
/** Get information about the implementations name and version. */
fun version(): Version
/** Generate a secret key. */ /** Generate a secret key. */
fun generateKey(): GenerateKey fun generateKey(): GenerateKey
@ -53,24 +47,6 @@ interface SOP {
*/ */
fun inlineSign(): InlineSign fun inlineSign(): InlineSign
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun verify(): DetachedVerify = detachedVerify()
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun detachedVerify(): DetachedVerify
/**
* Verify signatures of an inline-signed message. If you need to verify detached signatures over
* a message, use [detachedVerify] instead.
*/
fun inlineVerify(): InlineVerify
/** Detach signatures from an inline signed message. */ /** Detach signatures from an inline signed message. */
fun inlineDetach(): InlineDetach fun inlineDetach(): InlineDetach

View File

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop
import sop.operation.DetachedVerify
import sop.operation.InlineVerify
import sop.operation.Version
/** Subset of [SOP] implementing only OpenPGP signature verification. */
interface SOPV {
/** Get information about the implementations name and version. */
fun version(): Version
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun verify(): DetachedVerify = detachedVerify()
/**
* Verify detached signatures. If you need to verify an inline-signed message, use
* [inlineVerify] instead.
*/
fun detachedVerify(): DetachedVerify
/**
* Verify signatures of an inline-signed message. If you need to verify detached signatures over
* a message, use [detachedVerify] instead.
*/
fun inlineVerify(): InlineVerify
}

View File

@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.enums
@Deprecated("Use of armor labels is deprecated.")
enum class ArmorLabel {
auto,
sig,
key,
cert,
message
}

View File

@ -7,22 +7,10 @@ package sop.operation
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import sop.Ready import sop.Ready
import sop.enums.ArmorLabel
import sop.exception.SOPGPException.BadData import sop.exception.SOPGPException.BadData
import sop.exception.SOPGPException.UnsupportedOption
interface Armor { interface Armor {
/**
* Overrides automatic detection of label.
*
* @param label armor label
* @return builder instance
*/
@Deprecated("Use of armor labels is deprecated and will be removed in a future release.")
@Throws(UnsupportedOption::class)
fun label(label: ArmorLabel): Armor
/** /**
* Armor the provided data. * Armor the provided data.
* *

View File

@ -4,6 +4,9 @@
package sop.operation package sop.operation
import kotlin.jvm.Throws
import sop.exception.SOPGPException
interface Version { interface Version {
/** /**
@ -97,4 +100,11 @@ interface Version {
* @return remarks or null * @return remarks or null
*/ */
fun getSopSpecImplementationRemarks(): String? fun getSopSpecImplementationRemarks(): String?
/**
* Return the single-line SEMVER version of the sopv interface subset it provides complete
* coverage of. If the implementation does not provide complete coverage for any sopv interface,
* this method throws an [SOPGPException.UnsupportedOption] instead.
*/
@Throws(SOPGPException.UnsupportedOption::class) fun getSopVVersion(): String
} }

View File

@ -15,6 +15,7 @@ import java.io.OutputStream
* class is useful if we need to provide an [OutputStream] at one point in time when the final * class is useful if we need to provide an [OutputStream] at one point in time when the final
* target output stream is not yet known. * target output stream is not yet known.
*/ */
@Deprecated("Marked for removal.")
class ProxyOutputStream : OutputStream() { class ProxyOutputStream : OutputStream() {
private val buffer = ByteArrayOutputStream() private val buffer = ByteArrayOutputStream()
private var swapped: OutputStream? = null private var swapped: OutputStream? = null

View File

@ -0,0 +1,235 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.testsuite.assertions;
import sop.exception.SOPGPException;
import java.util.function.IntSupplier;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
/**
* DSL for testing the return values of SOP method calls.
*/
public class SopExecutionAssertions {
/**
* Assert that the execution of the given function returns 0.
*
* @param function function to execute
*/
public static void assertSuccess(IntSupplier function) {
assertEquals(0, function.getAsInt());
}
/**
* Assert that the execution of the given function returns a generic error with error code 1.
*
* @param function function to execute.
*/
public static void assertGenericError(IntSupplier function) {
assertEquals(1, function.getAsInt());
}
/**
* Assert that the execution of the given function returns a non-zero error code.
*
* @param function function to execute
*/
public static void assertAnyError(IntSupplier function) {
assertNotEquals(0, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 3
* (which corresponds to {@link sop.exception.SOPGPException.NoSignature}).
*
* @param function function to execute.
*/
public static void assertNoSignature(IntSupplier function) {
assertEquals(SOPGPException.NoSignature.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 13
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedAsymmetricAlgo}).
*
* @param function function to execute.
*/
public static void assertUnsupportedAsymmetricAlgo(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 17
* (which corresponds to {@link sop.exception.SOPGPException.CertCannotEncrypt}).
*
* @param function function to execute.
*/
public static void assertCertCannotEncrypt(IntSupplier function) {
assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 19
* (which corresponds to {@link sop.exception.SOPGPException.MissingArg}).
*
* @param function function to execute.
*/
public static void assertMissingArg(IntSupplier function) {
assertEquals(SOPGPException.MissingArg.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 23
* (which corresponds to {@link sop.exception.SOPGPException.IncompleteVerification}).
*
* @param function function to execute.
*/
public static void assertIncompleteVerification(IntSupplier function) {
assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 29
* (which corresponds to {@link sop.exception.SOPGPException.CannotDecrypt}).
*
* @param function function to execute.
*/
public static void assertCannotDecrypt(IntSupplier function) {
assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 31
* (which corresponds to {@link sop.exception.SOPGPException.PasswordNotHumanReadable}).
*
* @param function function to execute.
*/
public static void assertPasswordNotHumanReadable(IntSupplier function) {
assertEquals(SOPGPException.PasswordNotHumanReadable.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 37
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedOption}).
*
* @param function function to execute.
*/
public static void assertUnsupportedOption(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 41
* (which corresponds to {@link sop.exception.SOPGPException.BadData}).
*
* @param function function to execute.
*/
public static void assertBadData(IntSupplier function) {
assertEquals(SOPGPException.BadData.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 53
* (which corresponds to {@link sop.exception.SOPGPException.ExpectedText}).
*
* @param function function to execute.
*/
public static void assertExpectedText(IntSupplier function) {
assertEquals(SOPGPException.ExpectedText.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 59
* (which corresponds to {@link sop.exception.SOPGPException.OutputExists}).
*
* @param function function to execute.
*/
public static void assertOutputExists(IntSupplier function) {
assertEquals(SOPGPException.OutputExists.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 61
* (which corresponds to {@link sop.exception.SOPGPException.MissingInput}).
*
* @param function function to execute.
*/
public static void assertMissingInput(IntSupplier function) {
assertEquals(SOPGPException.MissingInput.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 67
* (which corresponds to {@link sop.exception.SOPGPException.KeyIsProtected}).
*
* @param function function to execute.
*/
public static void assertKeyIsProtected(IntSupplier function) {
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 69
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedSubcommand}).
*
* @param function function to execute.
*/
public static void assertUnsupportedSubcommand(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedSubcommand.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 71
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedSpecialPrefix}).
*
* @param function function to execute.
*/
public static void assertUnsupportedSpecialPrefix(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedSpecialPrefix.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 73
* (which corresponds to {@link sop.exception.SOPGPException.AmbiguousInput}).
*
* @param function function to execute.
*/
public static void assertAmbiguousInput(IntSupplier function) {
assertEquals(SOPGPException.AmbiguousInput.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 79
* (which corresponds to {@link sop.exception.SOPGPException.KeyCannotSign}).
*
* @param function function to execute.
*/
public static void assertKeyCannotSign(IntSupplier function) {
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 83
* (which corresponds to {@link sop.exception.SOPGPException.IncompatibleOptions}).
*
* @param function function to execute.
*/
public static void assertIncompatibleOptions(IntSupplier function) {
assertEquals(SOPGPException.IncompatibleOptions.EXIT_CODE, function.getAsInt());
}
/**
* Assert that the execution of the given function returns error code 89
* (which corresponds to {@link sop.exception.SOPGPException.UnsupportedProfile}).
*
* @param function function to execute.
*/
public static void assertUnsupportedProfile(IntSupplier function) {
assertEquals(SOPGPException.UnsupportedProfile.EXIT_CODE, function.getAsInt());
}
}

View File

@ -327,4 +327,15 @@ public class EncryptDecryptTest extends AbstractSOPTest {
.toByteArrayAndResult() .toByteArrayAndResult()
.getBytes()); .getBytes());
} }
@ParameterizedTest
@MethodSource("provideInstances")
public void passingSecretKeysForPublicKeysFails(SOP sop) {
assertThrows(SOPGPException.BadData.class, () ->
sop.encrypt()
.withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
.plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))
.toByteArrayAndResult()
.getBytes());
}
} }

View File

@ -8,14 +8,15 @@ import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.opentest4j.TestAbortedException;
import sop.SOP; import sop.SOP;
import sop.exception.SOPGPException;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
public class VersionTest extends AbstractSOPTest { public class VersionTest extends AbstractSOPTest {
@ -59,7 +60,7 @@ public class VersionTest extends AbstractSOPTest {
try { try {
sop.version().getSopSpecVersion(); sop.version().getSopSpecVersion();
} catch (RuntimeException e) { } catch (RuntimeException e) {
assumeTrue(false); // SOP backend does not support this operation yet throw new TestAbortedException("SOP backend does not support 'version --sop-spec' yet.");
} }
String sopSpec = sop.version().getSopSpecVersion(); String sopSpec = sop.version().getSopSpecVersion();
@ -72,4 +73,17 @@ public class VersionTest extends AbstractSOPTest {
int sopRevision = sop.version().getSopSpecRevisionNumber(); int sopRevision = sop.version().getSopSpecRevisionNumber();
assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision)); assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision));
} }
@ParameterizedTest
@MethodSource("provideInstances")
public void sopVVersionTest(SOP sop) {
try {
sop.version().getSopVVersion();
} catch (SOPGPException.UnsupportedOption e) {
throw new TestAbortedException(
"Implementation does (gracefully) not provide coverage for any sopv interface version.");
} catch (RuntimeException e) {
throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version.");
}
}
} }

View File

@ -4,15 +4,15 @@
allprojects { allprojects {
ext { ext {
shortVersion = '8.0.0' shortVersion = '10.0.1'
isSnapshot = false isSnapshot = true
minAndroidSdk = 10 minAndroidSdk = 10
javaSourceCompatibility = 1.8 javaSourceCompatibility = 1.8
gsonVersion = '2.10.1' gsonVersion = '2.10.1'
jsrVersion = '3.0.2' jsrVersion = '3.0.2'
junitVersion = '5.8.2' junitVersion = '5.8.2'
junitSysExitVersion = '1.1.2' junitSysExitVersion = '1.1.2'
logbackVersion = '1.2.11' logbackVersion = '1.2.13'
mockitoVersion = '4.5.1' mockitoVersion = '4.5.1'
picocliVersion = '4.6.3' picocliVersion = '4.6.3'
slf4jVersion = '1.7.36' slf4jVersion = '1.7.36'