diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java index add0a0d4..46461ce7 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java @@ -21,7 +21,7 @@ import dev.galasa.extensions.common.couchdb.CouchdbValidator; import dev.galasa.extensions.common.couchdb.pojos.ViewRow; import dev.galasa.extensions.common.api.HttpRequestFactory; -import dev.galasa.framework.spi.auth.IAuthToken; +import dev.galasa.framework.spi.auth.IInternalAuthToken; import dev.galasa.framework.spi.auth.IAuthStore; import dev.galasa.framework.spi.auth.User; import dev.galasa.framework.spi.utils.ITimeService; @@ -31,7 +31,7 @@ * When CouchDB is being used to store user-related information, including information * about authentication tokens (but not the tokens themselves), this class is called * upon to implement the auth store. - * + * * This class registers the auth store as the only auth store in the framework, and is * only used when Galasa is running in an ecosystem. It gets all of its data from a * CouchDB server. @@ -61,10 +61,10 @@ public CouchdbAuthStore( } @Override - public List getTokens() throws AuthStoreException { + public List getTokens() throws AuthStoreException { logger.info("Retrieving tokens from CouchDB"); List tokenDocuments = new ArrayList<>(); - List tokens = new ArrayList<>(); + List tokens = new ArrayList<>(); try { // Get all of the documents in the tokens database @@ -106,6 +106,16 @@ public void storeToken(String clientId, String description, User owner) throws A } } + @Override + public void deleteToken(String tokenId) throws AuthStoreException { + try { + deleteDocumentFromDatabase(TOKENS_DATABASE_NAME, tokenId); + } catch (CouchdbException e) { + String errorMessage = ERROR_FAILED_TO_DELETE_TOKEN_DOCUMENT.getMessage(e.getMessage()); + throw new AuthStoreException(errorMessage, e); + } + } + /** * Gets an auth token from a CouchDB document with the given document ID. * The document is assumed to be within the tokens database in the CouchDB server. @@ -114,7 +124,7 @@ public void storeToken(String clientId, String description, User owner) throws A * @return the auth token stored within the given document * @throws AuthStoreException if there was a problem accessing the auth store or its response */ - private IAuthToken getAuthTokenFromDocument(String documentId) throws CouchdbException { + private IInternalAuthToken getAuthTokenFromDocument(String documentId) throws CouchdbException { return getDocumentFromDatabase(TOKENS_DATABASE_NAME, documentId, CouchdbAuthToken.class); } } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthToken.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthToken.java index ecedc0a0..b5bea8f2 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthToken.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthToken.java @@ -7,10 +7,10 @@ import java.time.Instant; -import dev.galasa.framework.spi.auth.IAuthToken; +import dev.galasa.framework.spi.auth.IInternalAuthToken; import dev.galasa.framework.spi.auth.User; -public class CouchdbAuthToken implements IAuthToken { +public class CouchdbAuthToken implements IInternalAuthToken { private String _id; private String dexClientId; diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java index 160d4f60..6026bc03 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java @@ -19,6 +19,7 @@ import dev.galasa.auth.couchdb.internal.CouchdbAuthStore; import dev.galasa.auth.couchdb.internal.CouchdbAuthToken; +import dev.galasa.extensions.common.couchdb.pojos.IdRev; import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewRow; @@ -31,7 +32,7 @@ import dev.galasa.extensions.mocks.MockTimeService; import dev.galasa.extensions.mocks.couchdb.MockCouchdbValidator; import dev.galasa.framework.spi.auth.AuthStoreException; -import dev.galasa.framework.spi.auth.IAuthToken; +import dev.galasa.framework.spi.auth.IInternalAuthToken; import dev.galasa.framework.spi.auth.User; public class TestCouchdbAuthStore { @@ -50,11 +51,11 @@ public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeEx } } - class GetTokenDocumentInteraction extends BaseHttpInteraction { + class GetTokenDocumentInteraction extends BaseHttpInteraction { - public GetTokenDocumentInteraction(String expectedUri, int responseStatusCode, CouchdbAuthToken tokenToReturn) { + public GetTokenDocumentInteraction(String expectedUri, int responseStatusCode, T responseObjToReturn) { super(expectedUri, responseStatusCode); - setResponsePayload(tokenToReturn); + setResponsePayload(responseObjToReturn); } @Override @@ -64,6 +65,20 @@ public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeEx } } + class DeleteTokenDocumentInteraction extends BaseHttpInteraction { + + public DeleteTokenDocumentInteraction(String expectedUri, int responseStatusCode) { + super(expectedUri, responseStatusCode); + setResponsePayload(""); + } + + @Override + public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { + super.validateRequest(host,request); + assertThat(request.getRequestLine().getMethod()).isEqualTo("DELETE"); + } + } + class CreateTokenDocInteraction extends BaseHttpInteraction { public CreateTokenDocInteraction(String expectedUri, int responseStatusCode) { @@ -123,7 +138,7 @@ public void testGetTokensReturnsTokensFromCouchdbOK() throws Exception { CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), new User("johndoe")); List interactions = new ArrayList(); interactions.add(new GetAllTokenDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetTokenDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); + interactions.add(new GetTokenDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -133,12 +148,12 @@ public void testGetTokensReturnsTokensFromCouchdbOK() throws Exception { CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); // When... - List tokens = authStore.getTokens(); + List tokens = authStore.getTokens(); // Then... assertThat(tokens).hasSize(1); - IAuthToken actualToken = tokens.get(0); + IInternalAuthToken actualToken = tokens.get(0); assertThat(actualToken).usingRecursiveComparison().isEqualTo(mockToken); } @@ -187,4 +202,174 @@ public void testStoreTokenWithFailingRequestToCreateTokenDocumentReturnsError() assertThat(thrown).isNotNull(); assertThat(thrown.getMessage()).contains("GAL6102E", "Failed to store auth token in the CouchDB tokens database"); } + + @Test + public void testDeleteTokenSendsRequestToDeleteTokenDocumentOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + String tokenIdToDelete = "my-old-token"; + + IdRev mockIdRev = new IdRev(); + mockIdRev._rev = "this-is-a-revision"; + + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; + String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; + + List interactions = new ArrayList(); + interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); + interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_OK)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + authStore.deleteToken(tokenIdToDelete); + + // Then the assertions made in the document interactions shouldn't have failed. + } + + @Test + public void testDeleteTokenWithAcceptedRequestToDeleteTokenDocumentDoesNotError() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + String tokenIdToDelete = "my-old-token"; + + IdRev mockIdRev = new IdRev(); + mockIdRev._rev = "this-is-a-revision"; + + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; + String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; + + List interactions = new ArrayList(); + interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); + + // The DELETE request may return a 202 Accepted, which shouldn't be a problem for us + interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_ACCEPTED)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + authStore.deleteToken(tokenIdToDelete); + + // Then the assertions made in the document interactions shouldn't have failed. + } + + @Test + public void testDeleteTokenWithFailingRequestToDeleteTokenDocumentThrowsError() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + String tokenIdToDelete = "my-old-token"; + + IdRev mockIdRev = new IdRev(); + mockIdRev._rev = "this-is-a-revision"; + + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; + String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; + + List interactions = new ArrayList(); + interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); + + // Simulate an internal server error + interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + AuthStoreException thrown = catchThrowableOfType(() -> { + authStore.deleteToken(tokenIdToDelete); + }, AuthStoreException.class); + + // Then... + assertThat(thrown).isNotNull(); + assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); + assertThat(thrown.getMessage()).contains("GAL6007E", "Expected status code(s) [200, 202] but received 500"); + } + + @Test + public void testDeleteTokenWithFailingRequestToGetTokenDocumentThrowsError() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + String tokenIdToDelete = "my-old-token"; + + IdRev mockIdRev = new IdRev(); + mockIdRev._rev = "this-is-a-revision"; + + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; + + List interactions = new ArrayList(); + + // Simulate an internal server error + interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR, mockIdRev)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + AuthStoreException thrown = catchThrowableOfType(() -> { + authStore.deleteToken(tokenIdToDelete); + }, AuthStoreException.class); + + // Then... + assertThat(thrown).isNotNull(); + assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); + } + + @Test + public void testDeleteTokenWithBadGetTokenDocumentResponseBodyThrowsError() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + String tokenIdToDelete = "my-old-token"; + + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; + + List interactions = new ArrayList(); + + // Simulate an internal server error + interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, null)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + AuthStoreException thrown = catchThrowableOfType(() -> { + authStore.deleteToken(tokenIdToDelete); + }, AuthStoreException.class); + + // Then... + assertThat(thrown).isNotNull(); + assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); + assertThat(thrown.getMessage()).contains("GAL6011E", "Failed to get document with ID 'my-old-token' from the 'galasa_tokens' database"); + } } diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java index b2e8372c..ff1b876d 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java @@ -21,16 +21,18 @@ public enum Errors { ERROR_FAILED_TO_CREATE_COUCHDB_DATABASE (6004,"GAL6004E: Internal server error. Failed to create CouchDB database ''{0}''. Status code {1} from CouchDB server is not 201. The CouchDB server could be experiencing temporary issues or is not correctly configured. Report the problem to your Galasa Ecosystem owner."), ERROR_OUTDATED_COUCHDB_VERSION (6005,"GAL6005E: Outdated CouchDB server version ''{0}'' detected. Expected version ''{1}'' or above. Report the problem to your Galasa Ecosystem owner."), ERROR_FAILED_TO_CREATE_COUCHDB_DOCUMENT (6006,"GAL6006E: Internal server error. Failed to create new document in the CouchDB database ''{0}''. The CouchDB server could be experiencing temporary issues or is not correctly configured. Report the problem to your Galasa Ecosystem owner."), - ERROR_UNEXPECTED_COUCHDB_HTTP_RESPONSE (6007,"GAL6007E: Internal server error. Unexpected response received from CouchDB server after sending a HTTP request to ''{0}''. Expected status code {1} but received {2}. The CouchDB server could be experiencing temporary issues. Report the problem to your Galasa Ecosystem owner."), + ERROR_UNEXPECTED_COUCHDB_HTTP_RESPONSE (6007,"GAL6007E: Internal server error. Unexpected response received from CouchDB server after sending a HTTP request to ''{0}''. Expected status code(s) [{1}] but received {2}. The CouchDB server could be experiencing temporary issues. Report the problem to your Galasa Ecosystem owner."), ERROR_FAILURE_OCCURRED_WHEN_CONTACTING_COUCHDB (6008,"GAL6008E: Internal server error. Unexpected failure occurred during HTTP request to CouchDB server at URL ''{0}''. Cause: ''{1}''"), ERROR_FAILED_TO_GET_DOCUMENTS_FROM_DATABASE (6009,"GAL6009E: Internal server error. Failed to get all documents in the ''{0}'' database. Invalid JSON response returned from CouchDB. CouchDB could be experiencing temporary issues or is not correctly configured. Report the problem to your Galasa Ecosystem owner."), ERROR_INVALID_COUCHDB_VERSION_FORMAT (6010,"GAL6010E: Invalid CouchDB server version format detected. The CouchDB version ''{0}'' must be in the semantic versioning format (e.g. major.minor.patch). Expected version ''{1}'' or above. Report the problem to your Galasa Ecosystem owner."), + ERROR_FAILED_TO_GET_DOCUMENT_FROM_DATABASE (6011,"GAL6011E: Internal server error. Failed to get document with ID ''{0}'' from the ''{1}'' database. Invalid JSON response returned from CouchDB. CouchDB could be experiencing temporary issues or is not correctly configured. Report the problem to your Galasa Ecosystem owner."), // CouchDB Auth Store errors ERROR_GALASA_AUTH_STORE_SHUTDOWN_FAILED (6100,"GAL6100E: Failed to shut down Galasa CouchDB auth store. Cause: {0}"), ERROR_FAILED_TO_RETRIEVE_TOKENS (6101,"GAL6101E: Failed to get auth tokens from the CouchDB auth store. Cause: {0}"), ERROR_FAILED_TO_CREATE_TOKEN_DOCUMENT (6102,"GAL6102E: Failed to store auth token in the CouchDB tokens database. Cause: {0}"), ERROR_FAILED_TO_INITIALISE_AUTH_STORE (6103,"GAL6103E: Failed to initialise the Galasa CouchDB auth store. Cause: {0}"), + ERROR_FAILED_TO_DELETE_TOKEN_DOCUMENT (6104,"GAL6104E: Failed to delete auth token from the CouchDB tokens database. Cause: {0}"), // REST CPS errors ERROR_GALASA_WRONG_NUMBER_OF_PARAMETERS_IN_MESSAGE (6999,"GAL6999E: Failed to render message template. Not the expected number of parameters. Got ''{0}''. Expected ''{1}''"), diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java index 9eddc1ac..738c8ce7 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java @@ -12,12 +12,15 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.ParseException; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; @@ -27,6 +30,7 @@ import dev.galasa.extensions.common.api.HttpClientFactory; import dev.galasa.extensions.common.api.HttpRequestFactory; +import dev.galasa.extensions.common.couchdb.pojos.IdRev; import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewRow; @@ -117,26 +121,51 @@ protected List getAllDocsFromDatabase(String dbName) throws CouchdbExce * @throws CouchdbException if there was a problem accessing the CouchDB store or its response */ protected T getDocumentFromDatabase(String dbName, String documentId, Class classOfObject) throws CouchdbException { - HttpGet getTokenDoc = httpRequestFactory.getHttpGetRequest(storeUri + "/" + dbName + "/" + documentId); - return gson.fromJson(sendHttpRequest(getTokenDoc, HttpStatus.SC_OK), classOfObject); + HttpGet getDocumentRequest = httpRequestFactory.getHttpGetRequest(storeUri + "/" + dbName + "/" + documentId); + return gson.fromJson(sendHttpRequest(getDocumentRequest, HttpStatus.SC_OK), classOfObject); } + /** + * Deletes a document from a given database using its document ID by sending a + * DELETE /{db}/{docid} request to the CouchDB server. + * + * @param dbName the name of the database to delete the document from + * @param documentId the CouchDB ID for the document to delete + * @throws CouchdbException if there was a problem accessing the CouchDB store or its response + */ + protected void deleteDocumentFromDatabase(String dbName, String documentId) throws CouchdbException { + IdRev documentIdRev = getDocumentFromDatabase(dbName, documentId, IdRev.class); + + if (documentIdRev == null || documentIdRev._rev == null) { + String errorMessage = ERROR_FAILED_TO_GET_DOCUMENT_FROM_DATABASE.getMessage(documentId, dbName); + throw new CouchdbException(errorMessage); + } + + String deleteRequestUrl = storeUri + "/" + dbName + "/" + documentId + "?rev=" + documentIdRev._rev; + HttpDelete deleteDocumentRequest = httpRequestFactory.getHttpDeleteRequest(deleteRequestUrl); + sendHttpRequest(deleteDocumentRequest, HttpStatus.SC_OK, HttpStatus.SC_ACCEPTED); + } /** * Sends a given HTTP request to the CouchDB server and returns the response body as a string. * * @param httpRequest the HTTP request to send to the CouchDB server - * @param expectedHttpStatusCode the expected Status code to get from the CouchDb server upon the request being actioned + * @param expectedHttpStatusCodes the expected Status code to get from the CouchDb server upon the request being actioned * @return a string representation of the response. * @throws CouchdbException if there was a problem accessing the CouchDB store or its response */ - private String sendHttpRequest(HttpUriRequest httpRequest, int expectedHttpStatusCode) throws CouchdbException { + private String sendHttpRequest(HttpUriRequest httpRequest, int... expectedHttpStatusCodes) throws CouchdbException { String responseEntity = ""; try (CloseableHttpResponse response = httpClient.execute(httpRequest)) { StatusLine statusLine = response.getStatusLine(); int actualStatusCode = statusLine.getStatusCode(); - if (actualStatusCode != expectedHttpStatusCode) { - String errorMessage = ERROR_UNEXPECTED_COUCHDB_HTTP_RESPONSE.getMessage(httpRequest.getURI().toString(), expectedHttpStatusCode, actualStatusCode); + + if (!isStatusCodeExpected(actualStatusCode, expectedHttpStatusCodes)) { + String expectedStatusCodesStr = IntStream.of(expectedHttpStatusCodes) + .mapToObj(Integer::toString) + .collect(Collectors.joining(", ")); + + String errorMessage = ERROR_UNEXPECTED_COUCHDB_HTTP_RESPONSE.getMessage(httpRequest.getURI().toString(), expectedStatusCodesStr, actualStatusCode); throw new CouchdbException(errorMessage); } @@ -149,4 +178,22 @@ private String sendHttpRequest(HttpUriRequest httpRequest, int expectedHttpStatu } return responseEntity; } + + /** + * Checks if a given status code is an expected status code using a given array of expected status codes. + * + * @param actualStatusCode the status code to check + * @param expectedStatusCodes an array of expected status codes returned from CouchDB + * @return true if the actual status code is an expected status code, false otherwise + */ + private boolean isStatusCodeExpected(int actualStatusCode, int... expectedStatusCodes) { + boolean isExpectedStatusCode = false; + for (int statusCode : expectedStatusCodes) { + if (actualStatusCode == statusCode) { + isExpectedStatusCode = true; + break; + } + } + return isExpectedStatusCode; + } } diff --git a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/pojos/IdRev.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/pojos/IdRev.java similarity index 50% rename from galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/pojos/IdRev.java rename to galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/pojos/IdRev.java index b7919a68..9eddab74 100644 --- a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/pojos/IdRev.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/pojos/IdRev.java @@ -3,11 +3,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package dev.galasa.ras.couchdb.internal.pojos; +package dev.galasa.extensions.common.couchdb.pojos; public class IdRev { - public String _id; // NOSONAR - public String _rev; // NOSONAR + public String _id; + public String _rev; } \ No newline at end of file diff --git a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryService.java b/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryService.java index 30b6c77f..326fc68c 100644 --- a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryService.java +++ b/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryService.java @@ -49,13 +49,13 @@ import dev.galasa.framework.spi.ras.RasTestClass; import dev.galasa.framework.spi.ras.ResultArchiveStoreFileStore; import dev.galasa.extensions.common.api.LogFactory; +import dev.galasa.extensions.common.couchdb.pojos.IdRev; import dev.galasa.extensions.common.couchdb.pojos.Row; import dev.galasa.extensions.common.couchdb.pojos.ViewResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewRow; import dev.galasa.extensions.common.api.HttpRequestFactory; import dev.galasa.ras.couchdb.internal.pojos.Find; import dev.galasa.ras.couchdb.internal.pojos.FoundRuns; -import dev.galasa.ras.couchdb.internal.pojos.IdRev; import dev.galasa.ras.couchdb.internal.pojos.TestStructureCouchdb; public class CouchdbDirectoryService implements IResultArchiveStoreDirectoryService {