diff --git a/.secrets.baseline b/.secrets.baseline index fafda2d6..e6b35c8d 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -75,7 +75,18 @@ "name": "TwilioKeyDetector" } ], - "results": {}, + "results": { + "galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java": [ + { + "hashed_secret": "1beb7496ebbe82c61151be093956d83dac625c13", + "is_secret": false, + "is_verified": false, + "line_number": 246, + "type": "Secret Keyword", + "verified_result": null + } + ] + }, "version": "0.13.1+ibm.62.dss", "word_list": { "file": null, diff --git a/galasa-extensions-parent/buildSrc/src/main/groovy/galasa.extensions.gradle b/galasa-extensions-parent/buildSrc/src/main/groovy/galasa.extensions.gradle index 95aa7362..e33c4f64 100644 --- a/galasa-extensions-parent/buildSrc/src/main/groovy/galasa.extensions.gradle +++ b/galasa-extensions-parent/buildSrc/src/main/groovy/galasa.extensions.gradle @@ -1,6 +1,7 @@ plugins { id 'galasa.java' id 'biz.aQute.bnd.builder' + id 'jacoco' } dependencies { @@ -17,3 +18,16 @@ dependencies { testImplementation 'org.awaitility:awaitility:3.0.0' testImplementation 'org.assertj:assertj-core:3.16.1' } + +test { + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + csv.required = true + html.outputLocation = layout.buildDirectory.dir('jacocoHtml') + } +} \ No newline at end of file diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/build.gradle b/galasa-extensions-parent/dev.galasa.auth.couchdb/build.gradle index 59d9ba6c..5936fbf2 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/build.gradle +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/build.gradle @@ -1,7 +1,6 @@ plugins { id 'biz.aQute.bnd.builder' id 'galasa.extensions' - id 'jacoco' } description = 'Galasa Authentication - CouchDB' @@ -21,14 +20,6 @@ dependencies { testImplementation(project(':dev.galasa.extensions.mocks')) } -jacocoTestReport { - reports { - xml.required = true - csv.required = true - html.outputLocation = layout.buildDirectory.dir('jacocoHtml') - } -} - // Note: These values are consumed by the parent build process // They indicate which packages of functionality this OSGi bundle should be delivered inside, // or referenced from. diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java index e8e98940..ab3d8544 100644 --- a/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java @@ -5,14 +5,12 @@ */ package dev.galasa.cps.etcd.internal; -import static com.google.common.base.Charsets.UTF_8; - import java.io.UnsupportedEncodingException; import java.net.URI; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.concurrent.CompletableFuture; +import java.util.Properties; +import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import javax.crypto.spec.SecretKeySpec; @@ -25,12 +23,10 @@ import dev.galasa.framework.spi.creds.CredentialsUsername; import dev.galasa.framework.spi.creds.CredentialsUsernamePassword; import dev.galasa.framework.spi.creds.CredentialsUsernameToken; +import dev.galasa.framework.spi.creds.FrameworkEncryptionService; import dev.galasa.framework.spi.creds.ICredentialsStore; -import io.etcd.jetcd.ByteSequence; +import dev.galasa.framework.spi.creds.IEncryptionService; import io.etcd.jetcd.Client; -import io.etcd.jetcd.KV; -import io.etcd.jetcd.KeyValue; -import io.etcd.jetcd.kv.GetResponse; /** * This class implements the credential store in a etcd store. Usernames, @@ -38,10 +34,9 @@ * * "secure.credentials.{SomeCredentialId};.username" */ -public class Etcd3CredentialsStore implements ICredentialsStore { - private final Client client; - private final KV kvClient; +public class Etcd3CredentialsStore extends Etcd3Store implements ICredentialsStore { private final SecretKeySpec key; + private final IEncryptionService encryptionService; /** * This constructor instantiates the Key value client that can retrieve values @@ -51,10 +46,8 @@ public class Etcd3CredentialsStore implements ICredentialsStore { * @throws CredentialsException A failure occurred. */ public Etcd3CredentialsStore(IFramework framework, URI etcd) throws CredentialsException { + super(etcd); try { - client = Client.builder().endpoints(etcd).build(); - kvClient = client.getKVClient(); - IConfigurationPropertyStoreService cpsService = framework.getConfigurationPropertyService("secure"); String encryptionKey = cpsService.getProperty("credentials.file", "encryption.key"); if (encryptionKey != null) { @@ -62,11 +55,19 @@ public Etcd3CredentialsStore(IFramework framework, URI etcd) throws CredentialsE } else { key = null; } + + this.encryptionService = new FrameworkEncryptionService(key); } catch (Exception e) { throw new CredentialsException("Unable to initialise the etcd credentials store", e); } } + public Etcd3CredentialsStore(SecretKeySpec key, IEncryptionService encryptionService, Client etcdClient) throws CredentialsException { + super(etcdClient); + this.key = key; + this.encryptionService = encryptionService; + } + /** * This method checks for the three available credential types in the * credentials stores and returns the appropiate response. @@ -78,50 +79,30 @@ public Etcd3CredentialsStore(IFramework framework, URI etcd) throws CredentialsE * @throws CredentialsException A failure occurred. */ public ICredentials getCredentials(String credentialsId) throws CredentialsException { - String token = get("secure.credentials." + credentialsId + ".token"); - if (token != null) { + try { + ICredentials credentials = null; + String token = get("secure.credentials." + credentialsId + ".token"); String username = get("secure.credentials." + credentialsId + ".username"); - if (username != null) { - return new CredentialsUsernameToken(key, username, token); + // Check if the credentials are UsernameToken or Token + if (token != null && username != null) { + credentials = new CredentialsUsernameToken(key, username, token); + } else if (token != null) { + credentials = new CredentialsToken(key, token); + } else if (username != null) { + // We have a username, so check if the credentials are UsernamePassword or Username + String password = get("secure.credentials." + credentialsId + ".password"); + if (password != null) { + credentials = new CredentialsUsernamePassword(key, username, password); + } else { + credentials = new CredentialsUsername(key, username); + } } - return new CredentialsToken(key, token); - } - - String username = get("secure.credentials." + credentialsId + ".username"); - String password = get("secure.credentials." + credentialsId + ".password"); - - if (username == null) { - return null; - } - - if (password == null) { - return new CredentialsUsername(key, username); - } - - return new CredentialsUsernamePassword(key, username, password); - } - - /** - * A get method which interacts with the etcd client correctly. - * - * @param key - the full key to search for with CredId and type of credential. - * @return String vaule response. - * @throws CredentialsException - */ - private String get(String key) throws CredentialsException { - ByteSequence bsKey = ByteSequence.from(key, UTF_8); - CompletableFuture getFuture = kvClient.get(bsKey); - try { - GetResponse response = getFuture.get(); - List kvs = response.getKvs(); - if (kvs.isEmpty()) { - return null; - } - return kvs.get(0).getValue().toString(UTF_8); + + return credentials; } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); - throw new CredentialsException("Could not retrieve key.", e); + throw new CredentialsException("Failed to get credentials", e); } } @@ -135,8 +116,30 @@ private static SecretKeySpec createKey(String secret) @Override public void shutdown() throws CredentialsException { - kvClient.close(); - client.close(); + super.shutdownStore(); } + @Override + public void setCredentials(String credentialsId, ICredentials credentials) throws CredentialsException { + Properties credentialProperties = credentials.toProperties(credentialsId); + + try { + for (Entry property : credentialProperties.entrySet()) { + put((String) property.getKey(), encryptionService.encrypt((String) property.getValue())); + } + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + throw new CredentialsException("Failed to set credentials", e); + } + } + + @Override + public void deleteCredentials(String credentialsId) throws CredentialsException { + try { + deletePrefix("secure.credentials." + credentialsId); + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + throw new CredentialsException("Failed to delete credentials", e); + } + } } diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3Store.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3Store.java new file mode 100644 index 00000000..a8856f95 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3Store.java @@ -0,0 +1,77 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.cps.etcd.internal; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import javax.validation.constraints.NotNull; + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.KV; +import io.etcd.jetcd.KeyValue; +import io.etcd.jetcd.kv.GetResponse; +import io.etcd.jetcd.options.DeleteOption; + +/** + * Abstract class containing common methods used to interact with etcd, like getting, setting, + * and deleting properties. + */ +public abstract class Etcd3Store { + + protected final Client client; + protected final KV kvClient; + + public Etcd3Store(Client client) { + this.client = client; + this.kvClient = client.getKVClient(); + } + + public Etcd3Store(URI etcdUri) { + this(Client.builder().endpoints(etcdUri).build()); + } + + protected String get(String key) throws InterruptedException, ExecutionException { + ByteSequence bsKey = ByteSequence.from(key, UTF_8); + CompletableFuture getFuture = kvClient.get(bsKey); + GetResponse response = getFuture.get(); + List kvs = response.getKvs(); + + String retrievedKey = null; + if (!kvs.isEmpty()) { + retrievedKey = kvs.get(0).getValue().toString(UTF_8); + } + return retrievedKey; + } + + protected void put(String key, String value) throws InterruptedException, ExecutionException { + ByteSequence bytesKey = ByteSequence.from(key, UTF_8); + ByteSequence bytesValue = ByteSequence.from(value, UTF_8); + kvClient.put(bytesKey, bytesValue).get(); + } + + protected void delete(@NotNull String key) throws InterruptedException, ExecutionException { + ByteSequence bytesKey = ByteSequence.from(key, StandardCharsets.UTF_8); + kvClient.delete(bytesKey).get(); + } + + protected void deletePrefix(@NotNull String keyPrefix) throws InterruptedException, ExecutionException { + ByteSequence bsKey = ByteSequence.from(keyPrefix, UTF_8); + DeleteOption options = DeleteOption.newBuilder().isPrefix(true).build(); + kvClient.delete(bsKey, options).get(); + } + + protected void shutdownStore() { + kvClient.close(); + client.close(); + } +} diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java new file mode 100644 index 00000000..fe519efd --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java @@ -0,0 +1,278 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.etcd.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import dev.galasa.cps.etcd.internal.Etcd3CredentialsStore; +import dev.galasa.etcd.internal.mocks.MockEncryptionService; +import dev.galasa.etcd.internal.mocks.MockEtcdClient; +import dev.galasa.framework.spi.creds.CredentialsToken; +import dev.galasa.framework.spi.creds.CredentialsUsername; +import dev.galasa.framework.spi.creds.CredentialsUsernamePassword; +import dev.galasa.framework.spi.creds.CredentialsUsernameToken; + +import static org.assertj.core.api.Assertions.*; + +public class Etcd3CredentialsStoreTest { + + @Test + public void testGetUsernameCredentialsReturnsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "my-user"; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".username", username); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + CredentialsUsername creds = (CredentialsUsername) store.getCredentials(credsId); + + // Then... + assertThat(creds).isNotNull(); + assertThat(creds.getUsername()).isEqualTo(username); + } + + @Test + public void testGetUsernamePasswordCredentialsReturnsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "my-user"; + String password = "not-a-password"; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".username", username); + mockCreds.put("secure.credentials." + credsId + ".password", password); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + CredentialsUsernamePassword creds = (CredentialsUsernamePassword) store.getCredentials(credsId); + + // Then... + assertThat(creds).isNotNull(); + assertThat(creds.getUsername()).isEqualTo(username); + assertThat(creds.getPassword()).isEqualTo(password); + } + + @Test + public void testGetUsernameTokenCredentialsReturnsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "my-user"; + String token = "a-token"; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".username", username); + mockCreds.put("secure.credentials." + credsId + ".token", token); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + CredentialsUsernameToken creds = (CredentialsUsernameToken) store.getCredentials(credsId); + + // Then... + assertThat(creds).isNotNull(); + assertThat(creds.getUsername()).isEqualTo(username); + assertThat(creds.getToken()).isEqualTo(token.getBytes()); + } + + @Test + public void testGetTokenCredentialsReturnsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String token = "a-token"; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".token", token); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + CredentialsToken creds = (CredentialsToken) store.getCredentials(credsId); + + // Then... + assertThat(creds).isNotNull(); + assertThat(creds.getToken()).isEqualTo(token.getBytes()); + } + + @Test + public void testSetUsernameCredentialsSetsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "a-username"; + + Map mockCreds = new HashMap<>(); + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + CredentialsUsername mockUsernameCreds = new CredentialsUsername(username); + + // When... + store.setCredentials(credsId, mockUsernameCreds); + + // Then... + assertThat(mockCreds).hasSize(1); + assertThat(mockCreds.get("secure.credentials." + credsId + ".username")).isEqualTo(username); + + // The credentials should have been encrypted when being set + assertThat(mockEncryptionService.getEncryptCount()).isEqualTo(1); + assertThat(mockEncryptionService.getDecryptCount()).isEqualTo(0); + } + + @Test + public void testSetUsernamePasswordCredentialsSetsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "a-username"; + String password = "not-a-password"; + + Map mockCreds = new HashMap<>(); + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + CredentialsUsernamePassword mockUsernamePasswordCreds = new CredentialsUsernamePassword(username, password); + + // When... + store.setCredentials(credsId, mockUsernamePasswordCreds); + + // Then... + assertThat(mockCreds).hasSize(2); + assertThat(mockCreds.get("secure.credentials." + credsId + ".username")).isEqualTo(username); + assertThat(mockCreds.get("secure.credentials." + credsId + ".password")).isEqualTo(password); + + // The credentials should have been encrypted when being set + assertThat(mockEncryptionService.getEncryptCount()).isEqualTo(2); + assertThat(mockEncryptionService.getDecryptCount()).isEqualTo(0); + } + + @Test + public void testSetUsernameTokenCredentialsSetsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "a-username"; + String token = "a-token"; + + Map mockCreds = new HashMap<>(); + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + CredentialsUsernameToken mockUsernameTokenCreds = new CredentialsUsernameToken(username, token); + + // When... + store.setCredentials(credsId, mockUsernameTokenCreds); + + // Then... + assertThat(mockCreds).hasSize(2); + assertThat(mockCreds.get("secure.credentials." + credsId + ".username")).isEqualTo(username); + assertThat(mockCreds.get("secure.credentials." + credsId + ".token")).isEqualTo(token); + + // The credentials should have been encrypted when being set + assertThat(mockEncryptionService.getEncryptCount()).isEqualTo(2); + assertThat(mockEncryptionService.getDecryptCount()).isEqualTo(0); + } + + @Test + public void testSetTokenCredentialsSetsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String token = "a-token"; + + Map mockCreds = new HashMap<>(); + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + CredentialsToken mockTokenCreds = new CredentialsToken(token); + + // When... + store.setCredentials(credsId, mockTokenCreds); + + // Then... + assertThat(mockCreds).hasSize(1); + assertThat(mockCreds.get("secure.credentials." + credsId + ".token")).isEqualTo(token); + + // The credentials should have been encrypted when being set + assertThat(mockEncryptionService.getEncryptCount()).isEqualTo(1); + assertThat(mockEncryptionService.getDecryptCount()).isEqualTo(0); + } + + @Test + public void testDeleteCredentialsRemovesCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "a-username"; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".username", username); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + assertThat(mockCreds).hasSize(1); + store.deleteCredentials(credsId); + + // Then... + assertThat(mockCreds).hasSize(0); + } + + @Test + public void testDeleteCredentialsRemovesCredentialsByPrefix() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "a-username"; + String password = "not-a-password"; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".username", username); + mockCreds.put("secure.credentials." + credsId + ".password", password); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + assertThat(mockCreds).hasSize(2); + store.deleteCredentials(credsId); + + // Then... + assertThat(mockCreds).hasSize(0); + } + + @Test + public void testShutdownClosesEtcdClientsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + Map mockCreds = new HashMap<>(); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + store.shutdown(); + + // Then... + assertThat(mockClient.isClientShutDown()).isTrue(); + } +} diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEncryptionService.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEncryptionService.java new file mode 100644 index 00000000..02090d27 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEncryptionService.java @@ -0,0 +1,35 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.etcd.internal.mocks; + +import dev.galasa.framework.spi.creds.CredentialsException; +import dev.galasa.framework.spi.creds.IEncryptionService; + +public class MockEncryptionService implements IEncryptionService { + + private int encryptCount = 0; + private int decryptCount = 0; + + public int getEncryptCount() { + return encryptCount; + } + + public int getDecryptCount() { + return decryptCount; + } + + @Override + public String encrypt(String plainText) throws CredentialsException { + encryptCount++; + return plainText; + } + + @Override + public String decrypt(String encryptedText) throws CredentialsException { + decryptCount++; + return encryptedText; + } +} diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEtcdClient.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEtcdClient.java new file mode 100644 index 00000000..92d67080 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEtcdClient.java @@ -0,0 +1,78 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.etcd.internal.mocks; + +import java.util.Map; + +import io.etcd.jetcd.Auth; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.Cluster; +import io.etcd.jetcd.Election; +import io.etcd.jetcd.KV; +import io.etcd.jetcd.Lease; +import io.etcd.jetcd.Lock; +import io.etcd.jetcd.Maintenance; +import io.etcd.jetcd.Watch; + +public class MockEtcdClient implements Client { + + private KV kvClient; + private boolean isClientShutDown = false; + + public MockEtcdClient(Map kvContents) { + this.kvClient = new MockEtcdKvClient(kvContents); + } + + @Override + public KV getKVClient() { + return kvClient; + } + + @Override + public void close() { + isClientShutDown = true; + } + + public boolean isClientShutDown() { + return isClientShutDown; + } + + @Override + public Auth getAuthClient() { + throw new UnsupportedOperationException("Unimplemented method 'getAuthClient'"); + } + + @Override + public Cluster getClusterClient() { + throw new UnsupportedOperationException("Unimplemented method 'getClusterClient'"); + } + + @Override + public Election getElectionClient() { + throw new UnsupportedOperationException("Unimplemented method 'getElectionClient'"); + } + + @Override + public Lease getLeaseClient() { + throw new UnsupportedOperationException("Unimplemented method 'getLeaseClient'"); + } + + @Override + public Lock getLockClient() { + throw new UnsupportedOperationException("Unimplemented method 'getLockClient'"); + } + + @Override + public Maintenance getMaintenanceClient() { + throw new UnsupportedOperationException("Unimplemented method 'getMaintenanceClient'"); + } + + @Override + public Watch getWatchClient() { + throw new UnsupportedOperationException("Unimplemented method 'getWatchClient'"); + } + +} diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEtcdKvClient.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEtcdKvClient.java new file mode 100644 index 00000000..0062b3b0 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/mocks/MockEtcdKvClient.java @@ -0,0 +1,120 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.etcd.internal.mocks; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import com.google.protobuf.ByteString; + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.KV; +import io.etcd.jetcd.Txn; +import io.etcd.jetcd.api.KeyValue; +import io.etcd.jetcd.api.KeyValue.Builder; +import io.etcd.jetcd.api.RangeResponse; +import io.etcd.jetcd.kv.CompactResponse; +import io.etcd.jetcd.kv.DeleteResponse; +import io.etcd.jetcd.kv.GetResponse; +import io.etcd.jetcd.kv.PutResponse; +import io.etcd.jetcd.options.CompactOption; +import io.etcd.jetcd.options.DeleteOption; +import io.etcd.jetcd.options.GetOption; +import io.etcd.jetcd.options.PutOption; + +public class MockEtcdKvClient implements KV { + + Map kvContents = new HashMap<>(); + + public MockEtcdKvClient(Map kvContents) { + this.kvContents = kvContents; + } + + @Override + public CompletableFuture get(ByteSequence key) { + String keyStr = key.toString(); + String value = kvContents.get(keyStr); + + RangeResponse rangeResponse; + if (value == null) { + rangeResponse = RangeResponse.newBuilder().build(); + } else { + ByteString keyByteStr = ByteString.copyFromUtf8(keyStr); + Builder builder = KeyValue.newBuilder().setKey(keyByteStr); + ByteString valueByteStr = ByteString.copyFromUtf8(value); + builder = builder.setValue(valueByteStr); + KeyValue kv = builder.build(); + rangeResponse = RangeResponse.newBuilder().addKvs(kv).build(); + } + GetResponse mockResponse = new GetResponse(rangeResponse, key); + return CompletableFuture.completedFuture(mockResponse); + } + + @Override + public CompletableFuture put(ByteSequence key, ByteSequence value) { + String keyStr = key.toString(); + String valueStr = value.toString(); + kvContents.put(keyStr, valueStr); + + return CompletableFuture.completedFuture(null); + } + + + @Override + public CompletableFuture delete(ByteSequence key, DeleteOption options) { + String keyStr = key.toString(); + + if (options.isPrefix()) { + Set keysToRemove = new HashSet<>(); + Set existingKeySet = kvContents.keySet(); + + for (String existingKey : existingKeySet) { + if (existingKey.startsWith(keyStr)) { + keysToRemove.add(existingKey); + } + } + + existingKeySet.removeAll(keysToRemove); + } else { + kvContents.remove(keyStr); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture compact(long key) { + throw new UnsupportedOperationException("Unimplemented method 'compact'"); + } + + @Override + public CompletableFuture compact(long key, CompactOption options) { + throw new UnsupportedOperationException("Unimplemented method 'compact'"); + } + + @Override + public CompletableFuture delete(ByteSequence key) { + throw new UnsupportedOperationException("Unimplemented method 'delete'"); + } + + @Override + public CompletableFuture get(ByteSequence key, GetOption options) { + throw new UnsupportedOperationException("Unimplemented method 'get'"); + } + + @Override + public CompletableFuture put(ByteSequence key, ByteSequence value, PutOption options) { + throw new UnsupportedOperationException("Unimplemented method 'put'"); + } + + @Override + public Txn txn() { + throw new UnsupportedOperationException("Unimplemented method 'txn'"); + } + +} diff --git a/galasa-extensions-parent/dev.galasa.events.kafka/build.gradle b/galasa-extensions-parent/dev.galasa.events.kafka/build.gradle index 7c5b5905..464b489f 100644 --- a/galasa-extensions-parent/dev.galasa.events.kafka/build.gradle +++ b/galasa-extensions-parent/dev.galasa.events.kafka/build.gradle @@ -1,7 +1,6 @@ plugins { id 'biz.aQute.bnd.builder' id 'galasa.extensions' - id 'jacoco' } description = 'Galasa Events Plug-In - Kafka' @@ -14,14 +13,6 @@ dependencies { testImplementation(project(':dev.galasa.extensions.mocks')) } -jacocoTestReport { - reports { - xml.required = true - csv.required = true - html.outputLocation = layout.buildDirectory.dir('jacocoHtml') - } -} - // Note: These values are consumed by the parent build process // They indicate which packages of functionality this OSGi bundle should be delivered inside, // or referenced from. diff --git a/galasa-extensions-parent/dev.galasa.ras.couchdb/build.gradle b/galasa-extensions-parent/dev.galasa.ras.couchdb/build.gradle index 60ab6881..ac505166 100644 --- a/galasa-extensions-parent/dev.galasa.ras.couchdb/build.gradle +++ b/galasa-extensions-parent/dev.galasa.ras.couchdb/build.gradle @@ -1,7 +1,6 @@ plugins { id 'biz.aQute.bnd.builder' id 'galasa.extensions' - id 'jacoco' } description = 'Galasa RAS - CouchDB' @@ -21,14 +20,6 @@ dependencies { testImplementation(project(':dev.galasa.extensions.mocks')) } -jacocoTestReport { - reports { - xml.required = true - csv.required = true - html.outputLocation = layout.buildDirectory.dir('jacocoHtml') - } -} - // Note: These values are consumed by the parent build process // They indicate which packages of functionality this OSGi bundle should be delivered inside, // or referenced from.