diff --git a/.github/workflows/pr-build.yaml b/.github/workflows/pr-build.yaml index 68c79db4..2706cefd 100644 --- a/.github/workflows/pr-build.yaml +++ b/.github/workflows/pr-build.yaml @@ -35,6 +35,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: gradle-version: 8.9 + cache-disabled: true - name: Build Extensions source code with gradle run: | 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 9ee04e0d..1f58b165 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 @@ -147,7 +147,7 @@ private Path getRunArtifactPath(TestStructureCouchdb ts) throws CouchdbRasExcept ArrayList runs = new ArrayList<>(); - HttpGet httpGet = requestFactory.getHttpGetRequest(store.getCouchdbUri() + "/galasa_run/_all_docs"); + HttpGet httpGet = requestFactory.getHttpGetRequest(store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/_all_docs"); try (CloseableHttpResponse response = store.getHttpClient().execute(httpGet)) { StatusLine statusLine = response.getStatusLine(); @@ -183,8 +183,19 @@ private Path getRunArtifactPath(TestStructureCouchdb ts) throws CouchdbRasExcept return runs; } - private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, CouchdbRasException { - HttpGet httpGet = requestFactory.getHttpGetRequest(store.getCouchdbUri() + "/galasa_run/" + id); + private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, ResultArchiveStoreException { + CouchdbRunResult runResult = fetchRunWithoutArtifacts(id); + TestStructureCouchdb testStructure = (TestStructureCouchdb) runResult.getTestStructure(); + + // Populate the run's artifacts filesystem + Path runArtifactPath = getRunArtifactPath(testStructure); + + runResult = new CouchdbRunResult(store, testStructure, runArtifactPath); + return runResult; + } + + private CouchdbRunResult fetchRunWithoutArtifacts(String id) throws ParseException, IOException, CouchdbRasException { + HttpGet httpGet = requestFactory.getHttpGetRequest(store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/" + id); try (CloseableHttpResponse response = store.getHttpClient().execute(httpGet)) { StatusLine statusLine = response.getStatusLine(); @@ -196,10 +207,8 @@ private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, String responseEntity = EntityUtils.toString(entity); TestStructureCouchdb ts = store.getGson().fromJson(responseEntity, TestStructureCouchdb.class); - Path runArtifactPath = getRunArtifactPath(ts); - // *** Add this run to the results - CouchdbRunResult cdbrr = new CouchdbRunResult(store, ts, runArtifactPath); + CouchdbRunResult cdbrr = new CouchdbRunResult(store, ts, createFileSystemProvider().getRoot()); return cdbrr; } } @@ -209,7 +218,7 @@ private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, ArrayList requestors = new ArrayList<>(); HttpGet httpGet = requestFactory.getHttpGetRequest( - store.getCouchdbUri() + "/galasa_run/_design/docs/_view/requestors-view?group=true"); + store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/_design/docs/_view/requestors-view?group=true"); try (CloseableHttpResponse response = store.getHttpClient().execute(httpGet)) { StatusLine statusLine = response.getStatusLine(); @@ -241,7 +250,7 @@ private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, ArrayList results = new ArrayList<>(); HttpGet httpGet = requestFactory.getHttpGetRequest( - store.getCouchdbUri() + "/galasa_run/_design/docs/_view/result-view?group=true"); + store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/_design/docs/_view/result-view?group=true"); try (CloseableHttpResponse response = store.getHttpClient().execute(httpGet)) { StatusLine statusLine = response.getStatusLine(); @@ -278,7 +287,7 @@ private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, ArrayList tests = new ArrayList<>(); HttpGet httpGet = requestFactory.getHttpGetRequest( - store.getCouchdbUri() + "/galasa_run/_design/docs/_view/bundle-testnames-view?group=true"); + store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/_design/docs/_view/bundle-testnames-view?group=true"); try (CloseableHttpResponse response = store.getHttpClient().execute(httpGet)) { StatusLine statusLine = response.getStatusLine(); @@ -326,7 +335,7 @@ private CouchdbRunResult fetchRun(String id) throws ParseException, IOException, public @NotNull RasRunResultPage getRunsPage(int maxResults, RasSortField primarySort, String pageToken, @NotNull IRasSearchCriteria... searchCriterias) throws ResultArchiveStoreException { - HttpPost httpPost = requestFactory.getHttpPostRequest(store.getCouchdbUri() + "/galasa_run/_find"); + HttpPost httpPost = requestFactory.getHttpPostRequest(store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/_find"); Find find = new Find(); find.selector = buildGetRunsQuery(searchCriterias); @@ -409,7 +418,7 @@ private JsonArray buildQuerySortJson(@NotNull RasSortField primarySort) { ArrayList runs = new ArrayList<>(); - HttpPost httpPost = requestFactory.getHttpPostRequest(store.getCouchdbUri() + "/galasa_run/_find"); + HttpPost httpPost = requestFactory.getHttpPostRequest(store.getCouchdbUri() + "/" + CouchdbRasStore.RUNS_DB + "/_find"); Find find = new Find(); find.selector = buildGetRunsQuery(searchCriterias); @@ -435,13 +444,17 @@ private JsonArray buildQuerySortJson(@NotNull RasSortField primarySort) { public void discardRun(String id) throws ResultArchiveStoreException { try { - CouchdbRunResult run = fetchRun(id); - TestStructureCouchdb testStructure = (TestStructureCouchdb) run.getTestStructure(); - - discardRunLogs(testStructure.getLogRecordIds()); - discardRunArtifacts(testStructure.getArtifactRecordIds()); - - discardRecord("galasa_run", id); + CouchdbRunResult run = fetchRunWithoutArtifacts(id); + if (run == null) { + logger.info("Run with ID " + id + " does not exist or has already been discarded"); + } else { + TestStructureCouchdb testStructure = (TestStructureCouchdb) run.getTestStructure(); + + discardRunLogs(testStructure.getLogRecordIds()); + discardRunArtifacts(testStructure.getArtifactRecordIds()); + + discardRecord(CouchdbRasStore.RUNS_DB, id, testStructure._rev); + } } catch (CouchdbRasException | ParseException | IOException e) { throw new ResultArchiveStoreException("Failed to discard run: " + id, e); } @@ -449,21 +462,25 @@ public void discardRun(String id) throws ResultArchiveStoreException { private void discardRunLogs(List ids) throws ResultArchiveStoreException { for (String id : ids) { - discardRecord("galasa_log", id); + discardRecord(CouchdbRasStore.LOG_DB, id); } } private void discardRunArtifacts(List ids) throws ResultArchiveStoreException { for (String id : ids) { - discardRecord("galasa_artifacts", id); + discardRecord(CouchdbRasStore.ARTIFACTS_DB, id); } } private void discardRecord(String databaseName, String id) throws ResultArchiveStoreException { + discardRecord(databaseName, id, getRevision(databaseName, id)); + } + + private void discardRecord(String databaseName, String id, String revision) throws ResultArchiveStoreException { URIBuilder builder; try { builder = new URIBuilder(store.getCouchdbUri() + "/" + databaseName + "/" + id); - builder.addParameter("rev", getRevision(databaseName, id)); + builder.addParameter("rev", revision); HttpDelete httpDelete = requestFactory.getHttpDeleteRequest(builder.build().toString()); diff --git a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbRasStore.java b/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbRasStore.java index 28a3aa22..6193bbbb 100644 --- a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbRasStore.java +++ b/galasa-extensions-parent/dev.galasa.ras.couchdb/src/main/java/dev/galasa/ras/couchdb/internal/CouchdbRasStore.java @@ -49,9 +49,9 @@ public class CouchdbRasStore extends CouchdbStore implements IResultArchiveStore private static final String COUCHDB_AUTH_ENV_VAR = "GALASA_RAS_TOKEN"; private static final String COUCHDB_AUTH_TYPE = "Basic"; - private static final String ARTIFACTS_DB = "galasa_artifacts"; - private static final String RUNS_DB = "galasa_run"; - private static final String LOG_DB = "galasa_log"; + public static final String ARTIFACTS_DB = "galasa_artifacts"; + public static final String RUNS_DB = "galasa_run"; + public static final String LOG_DB = "galasa_log"; private final Log logger ; diff --git a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/test/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryServiceTest.java b/galasa-extensions-parent/dev.galasa.ras.couchdb/src/test/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryServiceTest.java index 5697ce8b..8cee62bb 100644 --- a/galasa-extensions-parent/dev.galasa.ras.couchdb/src/test/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryServiceTest.java +++ b/galasa-extensions-parent/dev.galasa.ras.couchdb/src/test/java/dev/galasa/ras/couchdb/internal/CouchdbDirectoryServiceTest.java @@ -21,6 +21,7 @@ import org.apache.http.util.EntityUtils; import org.junit.Test; +import dev.galasa.extensions.common.couchdb.pojos.IdRev; import dev.galasa.extensions.common.impl.HttpRequestFactoryImpl; import dev.galasa.extensions.mocks.BaseHttpInteraction; import dev.galasa.extensions.mocks.HttpInteraction; @@ -82,13 +83,63 @@ private void validatePostRequestBody(HttpPost postRequest) { } } + class GetRunByIdFromCouchdbInteraction extends BaseHttpInteraction { + + public GetRunByIdFromCouchdbInteraction(String expectedUri, int statusCode, TestStructureCouchdb runTestStructure) { + super(expectedUri, statusCode); + setResponsePayload(runTestStructure); + } + + @Override + public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { + super.validateRequest(host,request); + assertThat(request.getRequestLine().getMethod()).isEqualTo("GET"); + } + } + + class GetDocumentByIdFromCouchdbInteraction extends BaseHttpInteraction { + + public GetDocumentByIdFromCouchdbInteraction(String expectedUri, int statusCode, IdRev idRev) { + super(expectedUri, statusCode); + setResponsePayload(idRev); + } + + @Override + public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { + super.validateRequest(host,request); + assertThat(request.getRequestLine().getMethod()).isEqualTo("GET"); + } + } + + class DeleteDocumentFromCouchdbInteraction extends BaseHttpInteraction { + + public DeleteDocumentFromCouchdbInteraction(String expectedUri, int statusCode) { + super(expectedUri, statusCode); + } + + @Override + public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { + super.validateRequest(host,request); + assertThat(request.getRequestLine().getMethod()).isEqualTo("DELETE"); + } + } + private TestStructureCouchdb createRunTestStructure(String runName) { TestStructureCouchdb mockTestStructure = new TestStructureCouchdb(); mockTestStructure._id = runName; + mockTestStructure._rev = "this-is-a-revision"; mockTestStructure.setRunName(runName); + mockTestStructure.setArtifactRecordIds(new ArrayList<>()); + mockTestStructure.setLogRecordIds(new ArrayList<>()); return mockTestStructure; } + //------------------------------------------ + // + // Tests for getting runs by criteria + // + //------------------------------------------ + @Test public void testGetRunsByQueuedFromOnePageReturnsRunsOk() throws Exception { // Given... @@ -500,4 +551,194 @@ public void testGetRunsPageWithNilBookmarkReturnsPageWithNoNextCursor() throws E // Then... assertThat(runsPage.getNextCursor()).isNull(); } + + //------------------------------------------ + // + // Tests for deleting runs + // + //------------------------------------------ + + @Test + public void testDiscardRunDeletesRunOk() throws Exception { + // Given... + String runId = "ABC123"; + TestStructureCouchdb mockRun1 = createRunTestStructure("run1"); + + IdRev mockIdRev = new IdRev(); + String revision = "this-is-a-revision"; + mockIdRev._id = "this-is-an-id"; + mockIdRev._rev = revision; + + String artifactId1 = "artifact1"; + String artifactId2 = "artifact2"; + List mockArtifactIds = List.of(artifactId1, artifactId2); + + String logId1 = "log1"; + String logId2 = "log2"; + List mockLogRecordIds = List.of(logId1, logId2); + + mockRun1.setArtifactRecordIds(mockArtifactIds); + mockRun1.setLogRecordIds(mockLogRecordIds); + + String baseUri = "http://my.uri"; + String runDbUri = baseUri + "/" + CouchdbRasStore.RUNS_DB + "/" + runId; + String artifactsDbUri = baseUri + "/" + CouchdbRasStore.ARTIFACTS_DB; + String logsDbUri = baseUri + "/" + CouchdbRasStore.LOG_DB; + List interactions = List.of( + // Fetch the run to be deleted + new GetRunByIdFromCouchdbInteraction(runDbUri, HttpStatus.SC_OK, mockRun1), + + // Start discarding the run's log records + new GetDocumentByIdFromCouchdbInteraction(logsDbUri + "/" + logId1, HttpStatus.SC_OK, mockIdRev), + new DeleteDocumentFromCouchdbInteraction(logsDbUri + "/" + logId1 + "?rev=" + revision, HttpStatus.SC_OK), + new GetDocumentByIdFromCouchdbInteraction(logsDbUri + "/" + logId2, HttpStatus.SC_OK, mockIdRev), + new DeleteDocumentFromCouchdbInteraction(logsDbUri + "/" + logId2 + "?rev=" + revision, HttpStatus.SC_OK), + + // Start discarding the run's artifact records + new GetDocumentByIdFromCouchdbInteraction(artifactsDbUri + "/" + artifactId1, HttpStatus.SC_OK, mockIdRev), + new DeleteDocumentFromCouchdbInteraction(artifactsDbUri + "/" + artifactId1 + "?rev=" + revision, HttpStatus.SC_OK), + new GetDocumentByIdFromCouchdbInteraction(artifactsDbUri + "/" + artifactId2, HttpStatus.SC_OK, mockIdRev), + new DeleteDocumentFromCouchdbInteraction(artifactsDbUri + "/" + artifactId2 + "?rev=" + revision, HttpStatus.SC_OK), + + // Delete the record of the run + new DeleteDocumentFromCouchdbInteraction(runDbUri + "?rev=" + mockRun1._rev, HttpStatus.SC_OK) + ); + + MockLogFactory mockLogFactory = new MockLogFactory(); + CouchdbRasStore mockRasStore = fixtures.createCouchdbRasStore(interactions, mockLogFactory); + CouchdbDirectoryService directoryService = new CouchdbDirectoryService(mockRasStore, mockLogFactory, new HttpRequestFactoryImpl()); + + // When... + directoryService.discardRun(runId); + + // Then... + // The assertions in the interactions should not have failed + } + + @Test + public void testDiscardRunWithNoArtifactsDeletesRunOk() throws Exception { + // Given... + String runId = "ABC123"; + TestStructureCouchdb mockRun1 = createRunTestStructure("run1"); + + IdRev mockIdRev = new IdRev(); + String revision = "this-is-a-revision"; + mockIdRev._id = "this-is-an-id"; + mockIdRev._rev = revision; + + String logId1 = "log1"; + String logId2 = "log2"; + List mockLogRecordIds = List.of(logId1, logId2); + + mockRun1.setLogRecordIds(mockLogRecordIds); + + String baseUri = "http://my.uri"; + String runDbUri = baseUri + "/" + CouchdbRasStore.RUNS_DB + "/" + runId; + String logsDbUri = baseUri + "/" + CouchdbRasStore.LOG_DB; + List interactions = List.of( + // Fetch the run to be deleted + new GetRunByIdFromCouchdbInteraction(runDbUri, HttpStatus.SC_OK, mockRun1), + + // Start discarding the run's log records + new GetDocumentByIdFromCouchdbInteraction(logsDbUri + "/" + logId1, HttpStatus.SC_OK, mockIdRev), + new DeleteDocumentFromCouchdbInteraction(logsDbUri + "/" + logId1 + "?rev=" + revision, HttpStatus.SC_OK), + new GetDocumentByIdFromCouchdbInteraction(logsDbUri + "/" + logId2, HttpStatus.SC_OK, mockIdRev), + new DeleteDocumentFromCouchdbInteraction(logsDbUri + "/" + logId2 + "?rev=" + revision, HttpStatus.SC_OK), + + // Delete the record of the run + new DeleteDocumentFromCouchdbInteraction(runDbUri + "?rev=" + mockRun1._rev, HttpStatus.SC_OK) + ); + + MockLogFactory mockLogFactory = new MockLogFactory(); + CouchdbRasStore mockRasStore = fixtures.createCouchdbRasStore(interactions, mockLogFactory); + CouchdbDirectoryService directoryService = new CouchdbDirectoryService(mockRasStore, mockLogFactory, new HttpRequestFactoryImpl()); + + // When... + directoryService.discardRun(runId); + + // Then... + // The assertions in the interactions should not have failed + } + + @Test + public void testDiscardRunWithNoArtifactsAndLogsDeletesRunOk() throws Exception { + // Given... + String runId = "ABC123"; + TestStructureCouchdb mockRun1 = createRunTestStructure("run1"); + + String baseUri = "http://my.uri"; + String runDbUri = baseUri + "/" + CouchdbRasStore.RUNS_DB + "/" + runId; + List interactions = List.of( + // Fetch the run to be deleted + new GetRunByIdFromCouchdbInteraction(runDbUri, HttpStatus.SC_OK, mockRun1), + + // Delete the record of the run + new DeleteDocumentFromCouchdbInteraction(runDbUri + "?rev=" + mockRun1._rev, HttpStatus.SC_OK) + ); + + MockLogFactory mockLogFactory = new MockLogFactory(); + CouchdbRasStore mockRasStore = fixtures.createCouchdbRasStore(interactions, mockLogFactory); + CouchdbDirectoryService directoryService = new CouchdbDirectoryService(mockRasStore, mockLogFactory, new HttpRequestFactoryImpl()); + + // When... + directoryService.discardRun(runId); + + // Then... + // The assertions in the interactions should not have failed + } + + @Test + public void testDiscardRunWithCouchdbServerErrorThrowsCorrectError() throws Exception { + // Given... + String runId = "ABC123"; + TestStructureCouchdb mockRun1 = createRunTestStructure("run1"); + + String baseUri = "http://my.uri"; + String runDbUri = baseUri + "/" + CouchdbRasStore.RUNS_DB + "/" + runId; + List interactions = List.of( + // Fetch the run to be deleted + new GetRunByIdFromCouchdbInteraction(runDbUri, HttpStatus.SC_OK, mockRun1), + + // Delete the record of the run + new DeleteDocumentFromCouchdbInteraction(runDbUri + "?rev=" + mockRun1._rev, HttpStatus.SC_INTERNAL_SERVER_ERROR) + ); + + MockLogFactory mockLogFactory = new MockLogFactory(); + CouchdbRasStore mockRasStore = fixtures.createCouchdbRasStore(interactions, mockLogFactory); + CouchdbDirectoryService directoryService = new CouchdbDirectoryService(mockRasStore, mockLogFactory, new HttpRequestFactoryImpl()); + + // When... + ResultArchiveStoreException thrown = catchThrowableOfType(() -> { + directoryService.discardRun(runId); + }, ResultArchiveStoreException.class); + + // Then... + // The assertions in the interactions should not have failed + assertThat(thrown).isNotNull(); + assertThat(thrown.getMessage()).contains("Unable to delete run", runId); + } + + @Test + public void testDiscardRunWithNonExistantRunDoesNotThrowError() throws Exception { + // Given... + String runId = "ABC123"; + TestStructureCouchdb mockRun1 = createRunTestStructure("run1"); + + String baseUri = "http://my.uri"; + String runDbUri = baseUri + "/" + CouchdbRasStore.RUNS_DB + "/" + runId; + List interactions = List.of( + new GetRunByIdFromCouchdbInteraction(runDbUri, HttpStatus.SC_INTERNAL_SERVER_ERROR, mockRun1) + ); + + MockLogFactory mockLogFactory = new MockLogFactory(); + CouchdbRasStore mockRasStore = fixtures.createCouchdbRasStore(interactions, mockLogFactory); + CouchdbDirectoryService directoryService = new CouchdbDirectoryService(mockRasStore, mockLogFactory, new HttpRequestFactoryImpl()); + + // When... + directoryService.discardRun(runId); + + // Then... + // The assertions in the interactions should not have failed + assertThat(mockLogFactory.toString()).contains(runId, "does not exist or has already been discarded"); + } }