From d401cc9ae183d3cb0ea4e371897c4ccb13f78f3b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 20 Feb 2025 14:29:08 -0600 Subject: [PATCH] Implement and test DeleteAction functionality - Unified `deleteFile` API across storage modules by removing unused `QInstance` parameter. - Added implementations for S3, SFTP, and local filesystem deleteAction. --- .../actions/AbstractBaseFilesystemAction.java | 61 ++++++++++++++- .../actions/AbstractFilesystemAction.java | 2 +- .../local/actions/FilesystemDeleteAction.java | 27 +++---- .../basic/BasicETLCleanupSourceFilesStep.java | 2 +- .../importer/FilesystemImporterStep.java | 2 +- .../s3/actions/AbstractS3Action.java | 5 +- .../filesystem/s3/actions/S3DeleteAction.java | 27 +++---- .../sftp/actions/AbstractSFTPAction.java | 11 ++- .../sftp/actions/SFTPDeleteAction.java | 27 +++---- .../local/FilesystemBackendModuleTest.java | 4 +- .../actions/FilesystemDeleteActionTest.java | 29 ++++++-- .../filesystem/s3/S3BackendModuleTest.java | 6 +- .../s3/actions/S3DeleteActionTest.java | 48 +++++++++++- .../module/filesystem/sftp/BaseSFTPTest.java | 7 +- .../sftp/actions/SFTPDeleteActionTest.java | 74 ++++++++++++++++++- 15 files changed, 259 insertions(+), 73 deletions(-) diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java index 76a28706..30999153 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java @@ -40,6 +40,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; @@ -57,6 +59,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantSett import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsUtil; import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage; import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils; +import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; +import com.kingsrook.qqq.backend.core.utils.ObjectUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier; @@ -138,7 +142,7 @@ public abstract class AbstractBaseFilesystemAction ** ** @throws FilesystemException if the delete is known to have failed, and the file is thought to still exit *******************************************************************************/ - public abstract void deleteFile(QInstance instance, QTableMetaData table, String fileReference) throws FilesystemException; + public abstract void deleteFile(QTableMetaData table, String fileReference) throws FilesystemException; /******************************************************************************* ** Move a file from a source path, to a destination path. @@ -285,7 +289,7 @@ public abstract class AbstractBaseFilesystemAction QueryOutput queryOutput = new QueryOutput(queryInput); String requestedPath = null; - QQueryFilter filter = queryInput.getFilter(); + QQueryFilter filter = queryInput.getFilter(); if(filter != null && tableDetails.getCardinality().equals(Cardinality.ONE)) { if(filter.getCriteria() != null && filter.getCriteria().size() == 1) @@ -670,4 +674,57 @@ public abstract class AbstractBaseFilesystemAction } } + + + /******************************************************************************* + ** + *******************************************************************************/ + protected DeleteOutput executeDelete(DeleteInput deleteInput) throws QException + { + try + { + preAction(deleteInput.getBackend()); + + DeleteOutput output = new DeleteOutput(); + output.setRecordsWithErrors(new ArrayList<>()); + + QTableMetaData table = deleteInput.getTable(); + QBackendMetaData backend = deleteInput.getBackend(); + + AbstractFilesystemTableBackendDetails tableDetails = getTableBackendDetails(AbstractFilesystemTableBackendDetails.class, table); + if(tableDetails.getCardinality().equals(Cardinality.ONE)) + { + int deletedCount = 0; + for(Serializable primaryKey : deleteInput.getPrimaryKeys()) + { + try + { + deleteFile(table, stripDuplicatedSlashes(getFullBasePath(table, backend) + "/" + primaryKey)); + deletedCount++; + } + catch(Exception e) + { + String message = ObjectUtils.tryElse(() -> ExceptionUtils.getRootException(e).getMessage(), "Message not available"); + output.addRecordWithError(new QRecord().withValue(table.getPrimaryKeyField(), primaryKey).withError(new SystemErrorStatusMessage("Error deleting file: " + message))); + } + } + output.setDeletedRecordCount(deletedCount); + } + else + { + throw (new NotImplementedException("Delete is not implemented for filesystem tables with cardinality: " + tableDetails.getCardinality())); + } + + return (output); + } + catch(Exception e) + { + throw new QException("Error executing delete: " + e.getMessage(), e); + } + finally + { + postAction(); + } + } + } diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java index 19090262..cb7d9255 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java @@ -230,7 +230,7 @@ public class AbstractFilesystemAction extends AbstractBaseFilesystemAction ** @throws FilesystemException if the delete is known to have failed, and the file is thought to still exit *******************************************************************************/ @Override - public void deleteFile(QInstance instance, QTableMetaData table, String fileReference) throws FilesystemException + public void deleteFile(QTableMetaData table, String fileReference) throws FilesystemException { File file = new File(fileReference); if(!file.exists()) diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java index 6d304f00..6dda158d 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java @@ -26,13 +26,12 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; -import org.apache.commons.lang.NotImplementedException; /******************************************************************************* ** *******************************************************************************/ -public class FilesystemDeleteAction implements DeleteInterface +public class FilesystemDeleteAction extends AbstractFilesystemAction implements DeleteInterface { /******************************************************************************* @@ -40,21 +39,19 @@ public class FilesystemDeleteAction implements DeleteInterface *******************************************************************************/ public DeleteOutput execute(DeleteInput deleteInput) throws QException { - throw new NotImplementedException("Filesystem delete not implemented"); - /* - try - { - DeleteResult rs = new DeleteResult(); - QTableMetaData table = deleteRequest.getTable(); + return (executeDelete(deleteInput)); + } - // return rs; - } - catch(Exception e) - { - throw new QException("Error executing delete: " + e.getMessage(), e); - } - */ + + /******************************************************************************* + ** Specify whether this particular module's update action can & should fetch + ** records before updating them, e.g., for audits or "not-found-checks" + *******************************************************************************/ + @Override + public boolean supportsPreFetchQuery() + { + return (false); } } diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesStep.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesStep.java index 68a319ca..604d4c2b 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesStep.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesStep.java @@ -94,7 +94,7 @@ public class BasicETLCleanupSourceFilesStep implements BackendStep if(VALUE_DELETE.equals(moveOrDelete)) { LOG.info("Deleting ETL source file: " + sourceFile); - actionBase.deleteFile(QContext.getQInstance(), table, sourceFile); + actionBase.deleteFile(table, sourceFile); } else if(VALUE_MOVE.equals(moveOrDelete)) { diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/importer/FilesystemImporterStep.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/importer/FilesystemImporterStep.java index f41ceb6b..bc0c3ec2 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/importer/FilesystemImporterStep.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/importer/FilesystemImporterStep.java @@ -319,7 +319,7 @@ public class FilesystemImporterStep implements BackendStep { String fullBasePath = sourceActionBase.getFullBasePath(sourceTable, sourceBackend); LOG.info("Removing source file", logPair("path", fullBasePath + "/" + sourceFileName), logPair("sourceTable", sourceTable.getName())); - sourceActionBase.deleteFile(QContext.getQInstance(), sourceTable, fullBasePath + "/" + sourceFileName); + sourceActionBase.deleteFile(sourceTable, fullBasePath + "/" + sourceFileName); } else { diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/AbstractS3Action.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/AbstractS3Action.java index 3693d32a..3b8bcb09 100644 --- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/AbstractS3Action.java +++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/AbstractS3Action.java @@ -31,6 +31,7 @@ import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; @@ -247,9 +248,9 @@ public class AbstractS3Action extends AbstractBaseFilesystemAction filesBeforeDelete = new AbstractFilesystemAction().listFiles(table, qInstance.getBackendForTable(table.getName())); FilesystemBackendModule filesystemBackendModule = new FilesystemBackendModule(); - filesystemBackendModule.getActionBase().deleteFile(qInstance, table, filesBeforeDelete.get(0).getAbsolutePath()); + filesystemBackendModule.getActionBase().deleteFile(table, filesBeforeDelete.get(0).getAbsolutePath()); List filesAfterDelete = new AbstractFilesystemAction().listFiles(table, qInstance.getBackendForTable(table.getName())); Assertions.assertEquals(filesBeforeDelete.size() - 1, filesAfterDelete.size(), @@ -191,7 +191,7 @@ public class FilesystemBackendModuleTest List filesBeforeDelete = new AbstractFilesystemAction().listFiles(table, qInstance.getBackendForTable(table.getName())); FilesystemBackendModule filesystemBackendModule = new FilesystemBackendModule(); - filesystemBackendModule.getActionBase().deleteFile(qInstance, table, PATH_THAT_WONT_EXIST); + filesystemBackendModule.getActionBase().deleteFile(table, PATH_THAT_WONT_EXIST); List filesAfterDelete = new AbstractFilesystemAction().listFiles(table, qInstance.getBackendForTable(table.getName())); Assertions.assertEquals(filesBeforeDelete.size(), filesAfterDelete.size(), diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteActionTest.java index e43a667e..ff113a24 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteActionTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteActionTest.java @@ -22,11 +22,19 @@ package com.kingsrook.qqq.backend.module.filesystem.local.actions; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.tables.CountAction; +import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; -import org.apache.commons.lang.NotImplementedException; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.module.filesystem.TestUtils; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; /******************************************************************************* @@ -34,14 +42,25 @@ import static org.junit.jupiter.api.Assertions.assertThrows; *******************************************************************************/ public class FilesystemDeleteActionTest extends FilesystemActionTest { - /******************************************************************************* ** *******************************************************************************/ @Test - public void test() throws QException + public void testSuccessfulDeleteMultiple() throws QException { - assertThrows(NotImplementedException.class, () -> new FilesystemDeleteAction().execute(new DeleteInput())); + int initialCount = new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_BLOB_LOCAL_FS)).getCount(); + + String filename1 = "A.txt"; + String filename2 = "B.txt"; + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_BLOB_LOCAL_FS).withRecords(List.of( + new QRecord().withValue("fileName", filename1).withValue("contents", "bytes"), + new QRecord().withValue("fileName", filename2).withValue("contents", "bytes")))); + assertEquals(initialCount + 2, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_BLOB_LOCAL_FS)).getCount()); + + DeleteOutput deleteOutput = new DeleteAction().execute(new DeleteInput(TestUtils.TABLE_NAME_BLOB_LOCAL_FS).withPrimaryKeys(List.of(filename1, filename2))); + assertEquals(2, deleteOutput.getDeletedRecordCount()); + assertEquals(0, deleteOutput.getRecordsWithErrors().size()); + assertEquals(initialCount, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_BLOB_LOCAL_FS)).getCount()); } } \ No newline at end of file diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/S3BackendModuleTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/S3BackendModuleTest.java index faec900e..b3b187de 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/S3BackendModuleTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/S3BackendModuleTest.java @@ -147,7 +147,7 @@ public class S3BackendModuleTest extends BaseS3Test S3BackendModule s3BackendModule = new S3BackendModule(); AbstractS3Action actionBase = (AbstractS3Action) s3BackendModule.getActionBase(); actionBase.setS3Utils(getS3Utils()); - actionBase.deleteFile(qInstance, table, s3ObjectSummariesBeforeDelete.get(0).getKey()); + actionBase.deleteFile(table, s3ObjectSummariesBeforeDelete.get(0).getKey()); List s3ObjectSummariesAfterDelete = getS3Utils().listObjectsInBucketMatchingGlob(BUCKET_NAME, TEST_FOLDER, ""); Assertions.assertEquals(s3ObjectSummariesBeforeDelete.size() - 1, s3ObjectSummariesAfterDelete.size(), @@ -176,7 +176,7 @@ public class S3BackendModuleTest extends BaseS3Test AbstractS3Action actionBase = (AbstractS3Action) s3BackendModule.getActionBase(); actionBase.setS3Utils(getS3Utils()); String path = "//" + s3ObjectSummariesBeforeDelete.get(0).getKey().replaceAll("/", "//"); - actionBase.deleteFile(qInstance, table, "//" + path); + actionBase.deleteFile(table, "//" + path); List s3ObjectSummariesAfterDelete = getS3Utils().listObjectsInBucketMatchingGlob(BUCKET_NAME, TEST_FOLDER, ""); Assertions.assertEquals(s3ObjectSummariesBeforeDelete.size() - 1, s3ObjectSummariesAfterDelete.size(), @@ -203,7 +203,7 @@ public class S3BackendModuleTest extends BaseS3Test S3BackendModule s3BackendModule = new S3BackendModule(); AbstractS3Action actionBase = (AbstractS3Action) s3BackendModule.getActionBase(); actionBase.setS3Utils(getS3Utils()); - actionBase.deleteFile(qInstance, table, PATH_THAT_WONT_EXIST); + actionBase.deleteFile(table, PATH_THAT_WONT_EXIST); List s3ObjectSummariesAfterDelete = getS3Utils().listObjectsInBucketMatchingGlob(BUCKET_NAME, TEST_FOLDER, ""); Assertions.assertEquals(s3ObjectSummariesBeforeDelete.size(), s3ObjectSummariesAfterDelete.size(), diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3DeleteActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3DeleteActionTest.java index 6b1ba2fa..ce01677f 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3DeleteActionTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3DeleteActionTest.java @@ -22,12 +22,19 @@ package com.kingsrook.qqq.backend.module.filesystem.s3.actions; +import java.util.List; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.module.filesystem.TestUtils; import com.kingsrook.qqq.backend.module.filesystem.s3.BaseS3Test; -import org.apache.commons.lang.NotImplementedException; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; /******************************************************************************* @@ -42,7 +49,42 @@ public class S3DeleteActionTest extends BaseS3Test @Test public void test() throws QException { - assertThrows(NotImplementedException.class, () -> new S3DeleteAction().execute(new DeleteInput())); + QInstance qInstance = TestUtils.defineInstance(); + + int initialCount = count(TestUtils.TABLE_NAME_BLOB_S3); + + InsertInput insertInput = new InsertInput(); + insertInput.setTableName(TestUtils.TABLE_NAME_BLOB_S3); + insertInput.setRecords(List.of( + new QRecord().withValue("fileName", "file2.txt").withValue("contents", "Hi, Bob."))); + + S3InsertAction insertAction = new S3InsertAction(); + insertAction.setS3Utils(getS3Utils()); + insertAction.execute(insertInput); + + assertEquals(initialCount + 1, count(TestUtils.TABLE_NAME_BLOB_S3)); + + S3DeleteAction deleteAction = new S3DeleteAction(); + deleteAction.setS3Utils(getS3Utils()); + DeleteOutput deleteOutput = deleteAction.execute(new DeleteInput(TestUtils.TABLE_NAME_BLOB_S3).withPrimaryKeys(List.of("file2.txt"))); + assertEquals(1, deleteOutput.getDeletedRecordCount()); + assertEquals(0, deleteOutput.getRecordsWithErrors().size()); + + assertEquals(initialCount, count(TestUtils.TABLE_NAME_BLOB_S3)); + } + + + /*************************************************************************** + ** + ***************************************************************************/ + private Integer count(String tableName) throws QException + { + CountInput countInput = new CountInput(); + countInput.setTableName(tableName); + S3CountAction s3CountAction = new S3CountAction(); + s3CountAction.setS3Utils(getS3Utils()); + CountOutput countOutput = s3CountAction.execute(countInput); + return countOutput.getCount(); } } \ No newline at end of file diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/BaseSFTPTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/BaseSFTPTest.java index 42c968be..6c71ea42 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/BaseSFTPTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/BaseSFTPTest.java @@ -43,8 +43,8 @@ public class BaseSFTPTest extends BaseTest public static final String TABLE_FOLDER = "files"; public static final String REMOTE_DIR = "/home/" + USERNAME + "/" + BACKEND_FOLDER + "/" + TABLE_FOLDER; - private static GenericContainer sftpContainer; - private static Integer currentPort; + protected static GenericContainer sftpContainer; + private static Integer currentPort; @@ -71,6 +71,7 @@ public class BaseSFTPTest extends BaseTest } + /*************************************************************************** ** ***************************************************************************/ @@ -80,6 +81,7 @@ public class BaseSFTPTest extends BaseTest } + /*************************************************************************** ** ***************************************************************************/ @@ -89,6 +91,7 @@ public class BaseSFTPTest extends BaseTest } + /*************************************************************************** ** ***************************************************************************/ diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/actions/SFTPDeleteActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/actions/SFTPDeleteActionTest.java index 3a992fbf..15d74d3b 100644 --- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/actions/SFTPDeleteActionTest.java +++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/sftp/actions/SFTPDeleteActionTest.java @@ -22,12 +22,23 @@ package com.kingsrook.qqq.backend.module.filesystem.sftp.actions; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.tables.CountAction; +import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.module.filesystem.TestUtils; import com.kingsrook.qqq.backend.module.filesystem.sftp.BaseSFTPTest; -import org.apache.commons.lang.NotImplementedException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; /******************************************************************************* @@ -35,14 +46,69 @@ import static org.junit.jupiter.api.Assertions.assertThrows; *******************************************************************************/ public class SFTPDeleteActionTest extends BaseSFTPTest { + private String filesBasename = "delete-test-"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + @AfterEach + void beforeAndAfterEach() throws Exception + { + rmrfInContainer(REMOTE_DIR + "/" + filesBasename + "*"); + } + + /******************************************************************************* ** *******************************************************************************/ @Test - public void test() throws QException + public void testSuccessfulDeleteMultiple() throws QException { - assertThrows(NotImplementedException.class, () -> new SFTPDeleteAction().execute(new DeleteInput())); + int initialCount = new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_SFTP_FILE)).getCount(); + + String filename1 = filesBasename + "A.txt"; + String filename2 = filesBasename + "B.txt"; + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_SFTP_FILE).withRecords(List.of( + new QRecord().withValue("fileName", filename1).withValue("contents", "bytes"), + new QRecord().withValue("fileName", filename2).withValue("contents", "bytes")))); + assertEquals(initialCount + 2, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_SFTP_FILE)).getCount()); + + DeleteOutput deleteOutput = new DeleteAction().execute(new DeleteInput(TestUtils.TABLE_NAME_SFTP_FILE).withPrimaryKeys(List.of(filename1, filename2))); + assertEquals(2, deleteOutput.getDeletedRecordCount()); + assertEquals(0, deleteOutput.getRecordsWithErrors().size()); + assertEquals(initialCount, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_SFTP_FILE)).getCount()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testFailedDelete() throws Exception + { + int initialCount = new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_SFTP_FILE)).getCount(); + + String filename1 = filesBasename + "C.txt"; + String filename2 = filesBasename + "D.txt"; + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_SFTP_FILE).withRecords(List.of( + new QRecord().withValue("fileName", filename1).withValue("contents", "bytes"), + new QRecord().withValue("fileName", filename2).withValue("contents", "bytes")))); + assertEquals(initialCount + 2, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_SFTP_FILE)).getCount()); + + sftpContainer.execInContainer("chmod", "000", REMOTE_DIR); + DeleteOutput deleteOutput = new DeleteAction().execute(new DeleteInput(TestUtils.TABLE_NAME_SFTP_FILE).withPrimaryKeys(List.of(filename1, filename2))); + sftpContainer.execInContainer("chmod", "777", REMOTE_DIR); + + assertEquals(0, deleteOutput.getDeletedRecordCount()); + assertEquals(2, deleteOutput.getRecordsWithErrors().size()); + assertThat(deleteOutput.getRecordsWithErrors().get(0).getErrorsAsString()).contains("Error deleting file: Permission denied"); + assertThat(deleteOutput.getRecordsWithErrors().get(1).getErrorsAsString()).contains("Error deleting file: Permission denied"); + assertEquals(initialCount + 2, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_SFTP_FILE)).getCount()); } } \ No newline at end of file