mirror of
https://codeberg.org/PGPainless/cert-d-java.git
synced 2025-09-09 03:09:39 +02:00
Get rid of certificate-store abstraction
This commit is contained in:
parent
7c39781d15
commit
7cc0ef5037
30 changed files with 31 additions and 399 deletions
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Paul Schaub <info@pgpainless.org>
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
# PGP Certificate Store Definitions
|
||||
|
||||
[](https://javadoc.io/doc/org.pgpainless/pgp-certificate-store)
|
||||
[](https://search.maven.org/artifact/org.pgpainless/pgp-certificate-store)
|
||||
|
||||
This module contains API definitions for an OpenPGP certificate store.
|
||||
A certificate store is used to store public key certificates only.
|
|
@ -1,36 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
group 'org.pgpainless'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
apply plugin: 'ru.vyarus.animalsniffer'
|
||||
|
||||
dependencies {
|
||||
// animal sniffer for ensuring Android API compatibility
|
||||
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature"
|
||||
|
||||
// JUnit for testing
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// Logging
|
||||
api "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
}
|
||||
|
||||
animalsniffer {
|
||||
sourceSets = [sourceSets.main]
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import pgp.certificate_store.exception.BadDataException;
|
||||
import pgp.certificate_store.exception.BadNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class AbstractCertificateStore implements CertificateStore {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCertificateStore.class);
|
||||
|
||||
public Set<Certificate> getCertificatesBySubkeyId(long subkeyId)
|
||||
throws IOException {
|
||||
Set<String> identifiers = getCertificateFingerprintsForSubkeyId(subkeyId);
|
||||
if (identifiers.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<Certificate> certificates = new HashSet<>();
|
||||
for (String identifier : identifiers) {
|
||||
try {
|
||||
certificates.add(getCertificate(identifier));
|
||||
} catch (BadNameException | BadDataException e) {
|
||||
LOGGER.warn("Could not read certificate.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return certificates;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
/**
|
||||
* OpenPGP certificate (public key).
|
||||
*/
|
||||
public abstract class Certificate implements KeyMaterial {
|
||||
|
||||
@Override
|
||||
public Certificate asCertificate() {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import pgp.certificate_store.exception.BadDataException;
|
||||
import pgp.certificate_store.exception.BadNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Certificate storage definition.
|
||||
* This interface defines methods to insert and retrieve {@link Certificate Certificates} to and from a store.
|
||||
*
|
||||
* {@link Certificate Certificates} are hereby identified by identifiers. An identifier can either be a fingerprint
|
||||
* or a special name. Special names are implementation-defined identifiers for certificates.
|
||||
*
|
||||
* Fingerprints are expected to be hexadecimal lowercase character sequences.
|
||||
*/
|
||||
public interface CertificateDirectory {
|
||||
|
||||
/**
|
||||
* Return the certificate that matches the given identifier.
|
||||
* If no matching certificate can be found, return null.
|
||||
*
|
||||
* @param identifier identifier for a certificate.
|
||||
* @return certificate or null
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws BadNameException if the identifier is invalid
|
||||
* @throws BadDataException if the certificate file contains invalid data
|
||||
*/
|
||||
Certificate getCertificate(String identifier)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
/**
|
||||
* Return the certificate that matches the given identifier, but only iff it changed since the last invocation.
|
||||
* To compare the certificate against its last returned result, the given tag is used.
|
||||
* If the tag of the currently found certificate matches the given argument, return null.
|
||||
*
|
||||
* @param identifier identifier for a certificate
|
||||
* @param tag tag to compare freshness
|
||||
* @return changed certificate or null
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws BadNameException if the identifier is invalid
|
||||
* @throws BadDataException if the certificate file contains invalid data
|
||||
*/
|
||||
Certificate getCertificateIfChanged(String identifier, String tag)
|
||||
throws IOException, BadNameException, BadDataException;
|
||||
|
||||
/**
|
||||
* Insert a certificate into the store.
|
||||
* If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be
|
||||
* used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate
|
||||
* will be stored in the store and returned.
|
||||
*
|
||||
* This method will block until a write-lock on the store can be acquired. If you cannot afford blocking,
|
||||
* consider to use {@link #tryInsertCertificate(InputStream, KeyMaterialMerger)} instead.
|
||||
*
|
||||
* @param data input stream containing the new certificate instance
|
||||
* @param merge callback for merging with an existing certificate instance
|
||||
* @return merged certificate
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws InterruptedException in case the inserting thread gets interrupted
|
||||
* @throws BadDataException if the data stream does not contain valid OpenPGP data
|
||||
*/
|
||||
Certificate insertCertificate(InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, InterruptedException, BadDataException;
|
||||
|
||||
/**
|
||||
* Insert a certificate into the store.
|
||||
* If an instance of the certificate is already present in the store, the given {@link KeyMaterialMerger} will be
|
||||
* used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate
|
||||
* will be stored in the store and returned.
|
||||
*
|
||||
* This method will not block. Instead, if the store is already write-locked, this method will simply return null
|
||||
* without any writing.
|
||||
* However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock
|
||||
* and return the written certificate.
|
||||
*
|
||||
* @param data input stream containing the new certificate instance
|
||||
* @param merge callback for merging with an existing certificate instance
|
||||
* @return merged certificate or null if the store cannot be locked
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws BadDataException if the data stream does not contain valid OpenPGP data
|
||||
*/
|
||||
Certificate tryInsertCertificate(InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, BadDataException;
|
||||
|
||||
/**
|
||||
* Insert a certificate into the store.
|
||||
* The certificate will be stored under the given special name instead of its fingerprint.
|
||||
*
|
||||
* If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be
|
||||
* used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate
|
||||
* will be stored in the store and returned.
|
||||
*
|
||||
* This method will block until a write-lock on the store can be acquired. If you cannot afford blocking,
|
||||
* consider to use {@link #tryInsertCertificateBySpecialName(String, InputStream, KeyMaterialMerger)} instead.
|
||||
*
|
||||
* @param specialName special name of the certificate
|
||||
* @param data input stream containing the new certificate instance
|
||||
* @param merge callback for merging with an existing certificate instance
|
||||
* @return merged certificate or null if the store cannot be locked
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws InterruptedException if the thread is interrupted
|
||||
* @throws BadDataException if the certificate file does not contain valid OpenPGP data
|
||||
* @throws BadNameException if the special name is unknown
|
||||
*/
|
||||
Certificate insertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, InterruptedException, BadDataException, BadNameException;
|
||||
|
||||
/**
|
||||
* Insert a certificate into the store.
|
||||
* The certificate will be stored under the given special name instead of its fingerprint.
|
||||
*
|
||||
* If an instance of the certificate is already present under the special name in the store, the given {@link KeyMaterialMerger} will be
|
||||
* used to merge both the existing and the new instance of the {@link Certificate}. The resulting merged certificate
|
||||
* will be stored in the store and returned.
|
||||
*
|
||||
* This method will not block. Instead, if the store is already write-locked, this method will simply return null
|
||||
* without any writing.
|
||||
* However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock
|
||||
* and return the written certificate.
|
||||
*
|
||||
* @param specialName special name for the certificate
|
||||
* @param data input stream containing the new certificate instance
|
||||
* @param merge callback for merging with an existing certificate instance
|
||||
* @return merged certificate or null if the store cannot be locked
|
||||
*
|
||||
* @throws IOException in case of an IO-error
|
||||
* @throws BadDataException if the data stream does not contain valid OpenPGP data
|
||||
* @throws BadNameException if the special name is not known
|
||||
*/
|
||||
Certificate tryInsertCertificateBySpecialName(String specialName, InputStream data, KeyMaterialMerger merge)
|
||||
throws IOException, BadDataException, BadNameException;
|
||||
|
||||
/**
|
||||
* Return an {@link Iterator} containing all certificates in the store.
|
||||
* The iterator will contain both certificates addressed by special names and by fingerprints.
|
||||
*
|
||||
* @return certificates
|
||||
*/
|
||||
Iterator<Certificate> getCertificates();
|
||||
|
||||
/**
|
||||
* Return an {@link Iterator} containing all certificate fingerprints from the store.
|
||||
* Note that this only includes the fingerprints of certificate primary keys, not those of subkeys.
|
||||
*
|
||||
* @return fingerprints
|
||||
*/
|
||||
Iterator<String> getFingerprints();
|
||||
|
||||
/**
|
||||
* Return the current trust-root key.
|
||||
* If no trust-root key is present, return null.
|
||||
*
|
||||
* @return trust-root key
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadDataException if the key datum contains invalid data
|
||||
*/
|
||||
Key getTrustRoot()
|
||||
throws IOException, BadDataException;
|
||||
|
||||
/**
|
||||
* Return the current trust-root key, but only iff it changed since the last invocation of this method.
|
||||
* To compare the key against its last returned result, the given tag is used.
|
||||
* If the tag of the currently found key matches the given argument, return null.
|
||||
*
|
||||
* @param tag tag to compare freshness
|
||||
* @return changed key or null
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadDataException if the key datum contains invalid data
|
||||
*/
|
||||
Key getTrustRootIfChanged(String tag)
|
||||
throws IOException, BadDataException;
|
||||
|
||||
/**
|
||||
* Insert the given trust-root key into the store.
|
||||
* If the key store already holds a trust-root key, the given {@link KeyMaterialMerger} callback will be used to merge
|
||||
* the two instances into one {@link Key}. The result will be stored in the store and returned.
|
||||
*
|
||||
* This method will not block. Instead, if the store is already write-locked, this method will simply return null
|
||||
* without writing anything.
|
||||
* However, if the write-lock is available, this method will acquire the lock, write to the store, release the lock
|
||||
* and return the written key.
|
||||
*
|
||||
* @param data input stream containing the new trust-root key
|
||||
* @param keyMerger callback for merging with an existing key instance
|
||||
* @return merged key
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws InterruptedException in case the inserting thread gets interrupted
|
||||
* @throws BadDataException if the data stream does not contain a valid OpenPGP key
|
||||
*/
|
||||
Key insertTrustRoot(InputStream data, KeyMaterialMerger keyMerger)
|
||||
throws IOException, InterruptedException, BadDataException;
|
||||
|
||||
/**
|
||||
* Insert the given trust-root key into the store.
|
||||
* If the key store already holds a trust-root key, the given {@link KeyMaterialMerger} callback will be used to merge
|
||||
* the two instances into one {@link Key}. The result will be stored in the store and returned.
|
||||
*
|
||||
* This method will block until a write-lock on the store can be acquired. If you cannot afford blocking,
|
||||
* consider using {@link #tryInsertTrustRoot(InputStream, KeyMaterialMerger)} instead.
|
||||
*
|
||||
* @param data input stream containing the new trust-root key
|
||||
* @param keyMerger callback for merging with an existing key instance
|
||||
* @return merged key
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadDataException if the data stream does not contain a valid OpenPGP key
|
||||
*/
|
||||
Key tryInsertTrustRoot(InputStream data, KeyMaterialMerger keyMerger)
|
||||
throws IOException, BadDataException;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
public interface CertificateStore extends CertificateDirectory, SubkeyLookup {
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
/**
|
||||
* OpenPGP key (secret key).
|
||||
*/
|
||||
public abstract class Key implements KeyMaterial {
|
||||
|
||||
/**
|
||||
* Return the certificate part of this OpenPGP key.
|
||||
*
|
||||
* @return OpenPGP certificate
|
||||
*/
|
||||
public abstract Certificate getCertificate();
|
||||
|
||||
@Override
|
||||
public Certificate asCertificate() {
|
||||
return getCertificate();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
public interface KeyMaterial {
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the certificate as 40 lowercase hex characters.
|
||||
* TODO: Allow OpenPGP V5 fingerprints
|
||||
*
|
||||
* @return fingerprint
|
||||
*/
|
||||
String getFingerprint();
|
||||
|
||||
Certificate asCertificate();
|
||||
|
||||
/**
|
||||
* Return an {@link InputStream} of the binary representation of the secret key.
|
||||
*
|
||||
* @return input stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
InputStream getInputStream() throws IOException;
|
||||
|
||||
String getTag() throws IOException;
|
||||
|
||||
/**
|
||||
* Return a {@link Set} containing key-ids of subkeys.
|
||||
*
|
||||
* @return subkeys
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Set<Long> getSubkeyIds() throws IOException;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Merge a given {@link Key} (update) with an existing {@link Key}.
|
||||
*/
|
||||
public interface KeyMaterialMerger {
|
||||
|
||||
/**
|
||||
* Merge the given key material with an existing copy and return the result.
|
||||
* If no existing {@link KeyMaterial} is found (i.e. if existing is null), this method returns the unmodified data.
|
||||
*
|
||||
* @param data key material
|
||||
* @param existing optional already existing copy of the key material
|
||||
* @return merged key material
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
KeyMaterial merge(KeyMaterial data, KeyMaterial existing) throws IOException;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import pgp.certificate_store.exception.BadDataException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface KeyMaterialReaderBackend {
|
||||
|
||||
/**
|
||||
* Read a {@link KeyMaterial} (either {@link Key} or {@link Certificate}) from the given {@link InputStream}.
|
||||
*
|
||||
* @param data input stream containing the binary representation of the key.
|
||||
* @return key or certificate object
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadDataException in case that the data stream does not contain a valid OpenPGP key/certificate
|
||||
*/
|
||||
KeyMaterial read(InputStream data) throws IOException, BadDataException;
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public interface SubkeyLookup {
|
||||
|
||||
/**
|
||||
* Lookup the fingerprint of the certificate that contains the given subkey.
|
||||
* If no record is found, return null.
|
||||
*
|
||||
* @param subkeyId subkey id
|
||||
* @return fingerprint of the certificate
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Set<String> getCertificateFingerprintsForSubkeyId(long subkeyId) throws IOException;
|
||||
|
||||
/**
|
||||
* Record, which certificate the subkey-ids in the list belong to.
|
||||
* This method does not change the affiliation of subkey-ids not contained in the provided list.
|
||||
*
|
||||
* @param certificate certificate fingerprint
|
||||
* @param subkeyIds subkey ids
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
void storeCertificateSubkeyIds(String certificate, List<Long> subkeyIds) throws IOException;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store.exception;
|
||||
|
||||
/**
|
||||
* The data was not a valid OpenPGP cert or key in binary format.
|
||||
*/
|
||||
public class BadDataException extends Exception {
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store.exception;
|
||||
|
||||
/**
|
||||
* Provided name was neither a valid fingerprint, nor a known special name.
|
||||
*/
|
||||
public class BadNameException extends Exception {
|
||||
|
||||
public BadNameException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BadNameException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store.exception;
|
||||
|
||||
/**
|
||||
* The base dir cannot possibly contain a store.
|
||||
*/
|
||||
public class NotAStoreException extends Exception {
|
||||
|
||||
public NotAStoreException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NotAStoreException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Exceptions defined by the Shared PGP Certificate Directory.
|
||||
*
|
||||
* @see <a href="https://sequoia-pgp.gitlab.io/pgp-cert-d/#name-failure-modes">Failure Modes</a>
|
||||
*/
|
||||
package pgp.certificate_store.exception;
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Abstract definitions of an OpenPGP certificate store.
|
||||
*/
|
||||
package pgp.certificate_store;
|
|
@ -1,16 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.certificate_store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class DummyTest {
|
||||
@Test
|
||||
public void test() {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue