From 7c183dec418cda19cc0bc8e84ea751b344f56071 Mon Sep 17 00:00:00 2001 From: Lisiankou Date: Fri, 23 Jun 2023 13:29:39 +0300 Subject: [PATCH 1/2] IJMP-1147: now the params of the copied file from remote system to local one are set --- .../mover/RemoteToLocalFileMover.kt | 38 ++++++++-- .../dataops/OperationsTestSpec.kt | 71 ++++++++++++++++--- 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/RemoteToLocalFileMover.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/RemoteToLocalFileMover.kt index 57ead001..c4f0119d 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/RemoteToLocalFileMover.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/RemoteToLocalFileMover.kt @@ -9,18 +9,24 @@ */ package eu.ibagroup.formainframe.dataops.operations.mover +import com.intellij.openapi.fileEditor.impl.LoadTextUtil import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.attributes.RemoteUssAttributes import eu.ibagroup.formainframe.dataops.content.synchronizer.DocumentedSyncProvider +import eu.ibagroup.formainframe.dataops.content.synchronizer.LF_LINE_SEPARATOR import eu.ibagroup.formainframe.dataops.operations.OperationRunner import eu.ibagroup.formainframe.dataops.operations.OperationRunnerFactory +import eu.ibagroup.formainframe.utils.changeFileEncodingTo import eu.ibagroup.formainframe.utils.log import eu.ibagroup.formainframe.utils.runReadActionInEdtAndWait import eu.ibagroup.formainframe.utils.runWriteActionInEdtAndWait import eu.ibagroup.formainframe.vfs.MFVirtualFile import org.zowe.kotlinsdk.XIBMDataType +import java.io.File import java.nio.file.Paths /** @@ -79,26 +85,46 @@ class RemoteToLocalFileMover(val dataOpsManager: DataOpsManager) : AbstractFileM ?: return IllegalArgumentException("Cannot find synchronizer for file ${sourceFile.name}") val syncProvider = DocumentedSyncProvider(sourceFile) - if (!sourceFile.fileType.isBinary) { - sourceFileAttributes.contentMode = XIBMDataType(XIBMDataType.Type.TEXT) - } else { + if (sourceFile.fileType.isBinary || sourceFileAttributes is RemoteUssAttributes) { sourceFileAttributes.contentMode = XIBMDataType(XIBMDataType.Type.BINARY) + } else { + sourceFileAttributes.contentMode = XIBMDataType(XIBMDataType.Type.TEXT) } contentSynchronizer.synchronizeWithRemote(syncProvider, progressIndicator) + val sourceContent = contentSynchronizer.successfulContentStorage(syncProvider) runWriteActionInEdtAndWait { if (operation.forceOverwriting) { - destFile.children.filter { it.name === (newFileName?: sourceFile.name) && !it.isDirectory }.forEach { it.delete(this) } + destFile.children.filter { it.name === (newFileName) && !it.isDirectory }.forEach { it.delete(this) } } } - val createdFileJava = Paths.get(destFile.path, newFileName?: sourceFile.name).toFile().apply { createNewFile() } - createdFileJava.writeBytes(sourceFile.contentsToByteArray()) + val createdFileJava = Paths.get(destFile.path, newFileName).toFile().apply { createNewFile() } + createdFileJava.writeBytes(sourceContent) + if (!sourceFile.fileType.isBinary) { + setCreatedFileParams(createdFileJava, sourceFile) + } runReadActionInEdtAndWait { destFile.refresh(false, false) } return null } + /** + * Sets parameters (charset, line separator) for created file to be the same as the parameters of source file. + * @param createdFileJava created java file + * @param sourceFile source virtual file + */ + private fun setCreatedFileParams(createdFileJava: File, sourceFile: VirtualFile) { + val createdVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(createdFileJava) + createdVirtualFile?.let { + changeFileEncodingTo(it, sourceFile.charset) + val lineSeparator = sourceFile.detectedLineSeparator ?: LF_LINE_SEPARATOR + runWriteActionInEdtAndWait { + LoadTextUtil.changeLineSeparators(null, it, lineSeparator, it) + } + } + } + /** * Starts operation execution. Throws throwable if something went wrong. * @throws Throwable diff --git a/src/test/kotlin/eu/ibagroup/formainframe/dataops/OperationsTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/dataops/OperationsTestSpec.kt index 99353deb..f43da9a2 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/dataops/OperationsTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/dataops/OperationsTestSpec.kt @@ -11,7 +11,10 @@ package eu.ibagroup.formainframe.dataops import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.impl.LoadTextUtil import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl @@ -21,12 +24,15 @@ import eu.ibagroup.formainframe.config.ws.DSMask import eu.ibagroup.formainframe.dataops.attributes.* import eu.ibagroup.formainframe.dataops.content.synchronizer.ContentSynchronizer import eu.ibagroup.formainframe.dataops.content.synchronizer.DocumentedSyncProvider +import eu.ibagroup.formainframe.dataops.content.synchronizer.LF_LINE_SEPARATOR import eu.ibagroup.formainframe.dataops.operations.DeleteOperation import eu.ibagroup.formainframe.dataops.operations.mover.CrossSystemMemberOrUssFileOrSequentialToUssDirMover import eu.ibagroup.formainframe.dataops.operations.mover.MoveCopyOperation +import eu.ibagroup.formainframe.dataops.operations.mover.RemoteToLocalFileMover import eu.ibagroup.formainframe.testServiceImpl.TestDataOpsManagerImpl import eu.ibagroup.formainframe.testServiceImpl.TestZosmfApiImpl import eu.ibagroup.formainframe.utils.castOrNull +import eu.ibagroup.formainframe.utils.changeFileEncodingTo import eu.ibagroup.formainframe.utils.service import eu.ibagroup.formainframe.utils.setUssFileTag import eu.ibagroup.formainframe.vfs.MFVirtualFile @@ -35,16 +41,15 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic +import io.mockk.* import org.zowe.kotlinsdk.DataAPI import org.zowe.kotlinsdk.FilePath import org.zowe.kotlinsdk.XIBMDataType import org.zowe.kotlinsdk.annotations.ZVersion import retrofit2.Call import retrofit2.Response +import java.io.File +import java.nio.charset.Charset import java.nio.charset.StandardCharsets inline fun mockkRequesters( @@ -126,11 +131,59 @@ class OperationsTestSpec : ShouldSpec({ // SequentialToPdsMover.canRun should("return true when we try to move sequential dataset to the not sequential dataset") {} should("return false when we try to move sequential dataset to another sequential dataset") {} - // RemoteToLocalFileMover.canRun - should("return true when we try to move USS file to a local folder") {} - should("return false when we try to move USS file to a local file") {} - // RemoteToLocalFileMover.run - should("move a remote USS binary file to a local folder") {} + context("RemoteToLocalFileMover") { + val remoteToLocalFileMover = spyk(RemoteToLocalFileMover(mockk())) + + val fileMockk = mockk() + val virtualFileMockk = mockk() + val charsetMockk = mockk() + every { virtualFileMockk.charset } returns charsetMockk + every { virtualFileMockk.detectedLineSeparator } returns LF_LINE_SEPARATOR + + var encodingChanged = false + var lineSeparatorChanged = false + + beforeEach { + encodingChanged = false + lineSeparatorChanged = false + + mockkStatic(LocalFileSystem::getInstance) + every { LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileMockk) } returns mockk() + + mockkStatic(::changeFileEncodingTo) + every { changeFileEncodingTo(any(), charsetMockk) } answers { + encodingChanged = true + } + + mockkStatic(LoadTextUtil::changeLineSeparators) + every { LoadTextUtil.changeLineSeparators(null, any(), LF_LINE_SEPARATOR, any()) } answers { + lineSeparatorChanged = true + } + } + afterEach { + unmockkAll() + } + + val setCreatedFileParamsRef = remoteToLocalFileMover::class.java.declaredMethods + .first { it.name == "setCreatedFileParams" } + setCreatedFileParamsRef.trySetAccessible() + + // canRun + should("return true when we try to move USS file to a local folder") {} + should("return false when we try to move USS file to a local file") {} + // run + should("move a remote USS binary file to a local folder") {} + // setCreatedFileParams + should("set parameters for created file in local system") { + setCreatedFileParamsRef.invoke(remoteToLocalFileMover, fileMockk, virtualFileMockk) + + assertSoftly { + encodingChanged shouldBe true + lineSeparatorChanged shouldBe true + } + + } + } // RemoteToLocalDirectoryMoverFactory.canRun should("return true when we try to move USS folder to a local folder") {} should("return false when we try to move USS folder to a local file") {} From 4fa8c83bf796d2c42a55726114e78b4e1f0c2ae3 Mon Sep 17 00:00:00 2001 From: Uladzislau Kalesnikau Date: Wed, 28 Jun 2023 13:06:55 +0300 Subject: [PATCH 2/2] IJMP-1140-Fix-ExplorerPasteProvider-tests 1) tests fixed + commented out obsolete code for IDEA version 221 --- Jenkinsfile.groovy | 5 +- build.gradle.kts | 10 +- gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 7 +- gradlew | 18 +- gradlew.bat | 15 +- .../formainframe/config/ws/MaskState.kt | 26 +- .../explorer/actions/AddMaskAction.kt | 6 +- .../explorer/actions/AllocateDatasetAction.kt | 37 +- .../explorer/actions/EditMaskAction.kt | 143 +++ .../explorer/actions/ForceRenameAction.kt | 6 +- .../explorer/actions/RenameAction.kt | 216 ++-- ...ddMaskDialog.kt => AddOrEditMaskDialog.kt} | 33 +- .../formainframe/explorer/ui/DSMaskNode.kt | 2 +- .../explorer/ui/FileExplorerView.kt | 15 +- .../explorer/ui/FileLikeDatasetNode.kt | 4 +- .../explorer/ui/FilesWorkingSetNode.kt | 2 +- .../formainframe/explorer/ui/JesFilterNode.kt | 2 +- .../formainframe/explorer/ui/JesWsNode.kt | 2 +- .../formainframe/explorer/ui/JobNode.kt | 2 +- .../formainframe/explorer/ui/LibraryNode.kt | 7 +- .../formainframe/explorer/ui/MFNode.kt | 14 - .../formainframe/explorer/ui/RenameDialog.kt | 72 +- .../formainframe/explorer/ui/SpoolFileNode.kt | 2 +- .../formainframe/explorer/ui/UssDirNode.kt | 6 +- .../explorer/ui/WorkingSetNode.kt | 4 +- .../formainframe/utils/validationFunctions.kt | 34 +- src/main/resources/META-INF/plugin.xml | 8 +- .../formainframe/explorer/ExplorerTestSpec.kt | 972 +----------------- .../actions/EditMaskActionTestSpec.kt | 436 ++++++++ .../actions/GetJobPropertiesActionTestSpec.kt | 179 ++++ .../actions/PurgeJobActionTestSpec.kt | 315 ++++++ .../explorer/actions/RenameActionTestSpec.kt | 346 +++++++ .../explorer/actions/dummyClasses.kt | 55 + .../formainframe/utils/UtilsTestSpec.kt | 164 ++- 35 files changed, 1929 insertions(+), 1237 deletions(-) create mode 100644 gradle.properties create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt rename src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/{AddMaskDialog.kt => AddOrEditMaskDialog.kt} (72%) delete mode 100644 src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/MFNode.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/GetJobPropertiesActionTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/PurgeJobActionTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameActionTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/dummyClasses.kt diff --git a/Jenkinsfile.groovy b/Jenkinsfile.groovy index 74949046..276633c6 100644 --- a/Jenkinsfile.groovy +++ b/Jenkinsfile.groovy @@ -18,6 +18,7 @@ properties([gitLabConnection('code.iby.scdc.io-connection')]) // @NonCPS // def changeVersion(String xmlFile) { + // def xml = new XmlSlurper().parseText(xmlFile) // println xml.'idea-version'.'@since-build' // xml.'idea-version'.'@since-build' = '203.7148.72' @@ -116,7 +117,7 @@ pipeline { success { script { if (!jiraTicket.contains('release') && !'development'.equals(jiraTicket) && !'zowe-development'.equals(jiraTicket) && !"null".equals(jiraTicket)) { - jiraAddComment idOrKey: "$jiraTicket", comment: "Hello! It's jenkins. Your push in branch was successfully built. You can download your build from the following link http://10.221.23.186/ijmp-plugin/$jiraTicket/idea/$resultFileName.", site: "$jiraSite" + jiraAddComment idOrKey: "$jiraTicket", comment: "Hello! It's jenkins. Your push in branch was successfully built. You can download your build from the following link http://10.222.240.3/ijmp-plugin/$jiraTicket/idea/$resultFileName.", site: "$jiraSite" } } @@ -124,7 +125,7 @@ pipeline { failure { script { if (!jiraTicket.contains('release') && !'development'.equals(jiraTicket) && !'zowe-development'.equals(jiraTicket) && !"null".equals(jiraTicket)) { - jiraAddComment idOrKey: "$jiraTicket", comment: "Hello! It's jenkins. Your push in branch failed to build for Intellij IDEA. You can get console output by the following link http://10.221.23.186:8080/job/BuildPluginPipeline/", site: "$jiraSite" + jiraAddComment idOrKey: "$jiraTicket", comment: "Hello! It's jenkins. Your push in branch failed to build for Intellij IDEA. You can get console output by the following link http://10.222.240.3:8080/job/BuildPluginPipeline/", site: "$jiraSite" } } } diff --git a/build.gradle.kts b/build.gradle.kts index cd5e8b94..cb34d815 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ import kotlinx.kover.api.KoverTaskExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("org.jetbrains.intellij") version "1.13.0" + id("org.jetbrains.intellij") version "1.14.2" kotlin("jvm") version "1.7.10" java id("org.jetbrains.kotlinx.kover") version "0.6.1" @@ -33,13 +33,7 @@ repositories { url = uri("https://zowe.jfrog.io/zowe/libs-release") } maven { - url = uri("http://10.221.23.186:8082/repository/internal/") - isAllowInsecureProtocol = true - credentials { - username = "admin" - password = "password123" - } - maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") + url = uri("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") flatDir { dir("libs") } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..7d6c0a37 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xss1M diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 73bb918e..50832291 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1 +1,6 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c7873..65dcd68d 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..6689b85b 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/kotlin/eu/ibagroup/formainframe/config/ws/MaskState.kt b/src/main/kotlin/eu/ibagroup/formainframe/config/ws/MaskState.kt index 7c9cee0c..e05caadb 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/config/ws/MaskState.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/config/ws/MaskState.kt @@ -29,7 +29,29 @@ open class MaskState( ) : TableRow /** - * Mask state extension that stores working set reference to check the mask uniqueness basing on the working set existing masks + * Mask state extension that stores working set reference to check the mask uniqueness basing + * on the working set existing masks. + * An instance of this class could also be called with the [MaskState] instance provided, + * that will create a new instance of the mask state with working set + * @param mask the mask name + * @param type the mask type (z/OS or USS) + * @param isTypeSelectedAutomatically parameter to represent whether the type selected basing on the mask name input + * @param isTypeSelectedManually parameter to represent whether the type selected by the user through the combo box * @param ws the working set to check the mask uniqueness */ -class MaskStateWithWS(var ws: FilesWorkingSet) : MaskState() +class MaskStateWithWS( + mask: String = "", + type: MaskType = MaskType.ZOS, + isTypeSelectedAutomatically: Boolean = false, + isTypeSelectedManually: Boolean = false, + var ws: FilesWorkingSet +) : MaskState(mask, type, isTypeSelectedAutomatically, isTypeSelectedManually) { + constructor(maskState: MaskState, ws: FilesWorkingSet) : + this( + mask = maskState.mask, + type = maskState.type, + isTypeSelectedAutomatically = maskState.isTypeSelectedAutomatically, + isTypeSelectedManually = maskState.isTypeSelectedManually, + ws + ) +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt index 546c5b3f..62f6f996 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt @@ -17,7 +17,7 @@ import eu.ibagroup.formainframe.config.ws.DSMask import eu.ibagroup.formainframe.config.ws.MaskStateWithWS import eu.ibagroup.formainframe.config.ws.UssPath import eu.ibagroup.formainframe.explorer.FilesWorkingSet -import eu.ibagroup.formainframe.explorer.ui.AddMaskDialog +import eu.ibagroup.formainframe.explorer.ui.AddOrEditMaskDialog import eu.ibagroup.formainframe.explorer.ui.ExplorerUnitTreeNodeBase import eu.ibagroup.formainframe.explorer.ui.FileExplorerView import eu.ibagroup.formainframe.explorer.ui.getExplorerView @@ -31,8 +31,8 @@ class AddMaskAction : AnAction() { val view = e.getExplorerView() ?: return val ws = getUnits(view).firstOrNull() ?: return - val initialState = MaskStateWithWS(ws) - val dialog = AddMaskDialog(e.project, initialState) + val initialState = MaskStateWithWS(ws = ws) + val dialog = AddOrEditMaskDialog(e.project, "Create Mask", initialState) if (dialog.showAndGet()) { val state = dialog.state when (state.type) { diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt index 825f619c..f5996c55 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt @@ -33,12 +33,25 @@ import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationOperation import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams import eu.ibagroup.formainframe.explorer.FilesWorkingSet -import eu.ibagroup.formainframe.explorer.ui.* +import eu.ibagroup.formainframe.explorer.ui.AllocationDialog +import eu.ibagroup.formainframe.explorer.ui.DSMaskNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerUnitTreeNodeBase +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.FileFetchNode +import eu.ibagroup.formainframe.explorer.ui.FileLikeDatasetNode +import eu.ibagroup.formainframe.explorer.ui.FilesWorkingSetNode +import eu.ibagroup.formainframe.explorer.ui.LibraryNode +import eu.ibagroup.formainframe.explorer.ui.getExplorerView import eu.ibagroup.formainframe.utils.castOrNull import eu.ibagroup.formainframe.utils.clone import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey -import eu.ibagroup.formainframe.utils.service // TODO: remove in v1.*.*-223 and greater -import org.zowe.kotlinsdk.* +import eu.ibagroup.formainframe.utils.service +import org.zowe.kotlinsdk.AllocationUnit +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.DsnameType +import org.zowe.kotlinsdk.RecordFormat +import org.zowe.kotlinsdk.SpaceUnits /** * Action class for dataset allocation with parameters chosen by user @@ -54,15 +67,23 @@ class AllocateDatasetAction : AnAction() { } /** - * Determines if dataset allocation is possible for chosen object + * Determines if dataset allocation is possible for chosen object. + * Shows the action if: + * 1. It is a [FileExplorerView] + * 2. The first selected node is [FilesWorkingSetNode], [DSMaskNode], [LibraryNode] or [FileLikeDatasetNode] */ override fun update(e: AnActionEvent) { val view = e.getExplorerView() ?: let { e.presentation.isEnabledAndVisible = false return } - val selected = view.mySelectedNodesData - e.presentation.isEnabledAndVisible = selected.getOrNull(0)?.node is MFNode + val selectedNodesData = view.mySelectedNodesData + val node = selectedNodesData.getOrNull(0)?.node + if (node !is FilesWorkingSetNode && node !is DSMaskNode && node !is LibraryNode && node !is FileLikeDatasetNode) { + e.presentation.isEnabledAndVisible = false + return + } + e.presentation.isEnabledAndVisible = true e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") } @@ -266,8 +287,8 @@ class AllocateLikeAction : AnAction() { } val selected = view.mySelectedNodesData e.presentation.isEnabledAndVisible = selected.size == 1 - && selected[0].attributes is RemoteDatasetAttributes - && !(selected[0].attributes as RemoteDatasetAttributes).isMigrated + && selected[0].attributes is RemoteDatasetAttributes + && !(selected[0].attributes as RemoteDatasetAttributes).isMigrated e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt new file mode 100644 index 00000000..3b484053 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt @@ -0,0 +1,143 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import eu.ibagroup.formainframe.config.configCrudable +import eu.ibagroup.formainframe.config.ws.DSMask +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.config.ws.MaskState +import eu.ibagroup.formainframe.config.ws.MaskStateWithWS +import eu.ibagroup.formainframe.config.ws.UssPath +import eu.ibagroup.formainframe.explorer.FilesWorkingSet +import eu.ibagroup.formainframe.explorer.ui.AddOrEditMaskDialog +import eu.ibagroup.formainframe.explorer.ui.DSMaskNode +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.UssDirNode +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.utils.MaskType +import eu.ibagroup.formainframe.utils.clone +import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey + +/** + * Class which represents an "Edit" action. + * The action is shown and triggered only on [DSMaskNode] and [UssDirNode] (a USS mask) node types + */ +class EditMaskAction : AnAction() { + + /** + * Edit changed dataset mask. Will remove the mask from dataset masks list and add it to USS paths list + * if the mask type changed + * @param initialName the initial mask name + * @param wsConfToUpdate the working set config the mask belongs to, that should be updated + * @param changedMaskState the changed mask state with new parameters inside + * @return [FilesWorkingSetConfig] + */ + private fun editChangedDSMask( + initialName: String, + wsConfToUpdate: FilesWorkingSetConfig, + changedMaskState: MaskStateWithWS + ): FilesWorkingSetConfig { + val maskToChange = wsConfToUpdate.dsMasks.filter { it.mask == initialName }[0] + if (changedMaskState.type == MaskType.USS) { + wsConfToUpdate.dsMasks.remove(maskToChange) + wsConfToUpdate.ussPaths.add(UssPath(changedMaskState.mask)) + } else { + wsConfToUpdate.dsMasks.filter { it.mask == initialName }[0].mask = changedMaskState.mask.uppercase() + } + return wsConfToUpdate + } + + /** + * Edit changed USS path. Will remove the mask from USS paths list and add it to dataset masks list + * if the mask type changed + * @param initialName the initial mask name + * @param wsConfToUpdate the working set config the mask belongs to, that should be updated + * @param changedMaskState the changed mask state with new parameters inside + * @return [FilesWorkingSetConfig] + */ + private fun editChangedUssPath( + initialName: String, + wsConfToUpdate: FilesWorkingSetConfig, + changedMaskState: MaskStateWithWS + ): FilesWorkingSetConfig { + val maskToChange = wsConfToUpdate.ussPaths.filter { it.path == initialName }[0] + if (changedMaskState.type == MaskType.ZOS) { + wsConfToUpdate.ussPaths.remove(maskToChange) + wsConfToUpdate.dsMasks.add(DSMask(changedMaskState.mask.uppercase(), mutableListOf(), "")) + } else { + wsConfToUpdate.ussPaths.filter { it.path == initialName }[0].path = changedMaskState.mask + } + return wsConfToUpdate + } + + /** + * Run "Edit" action on the selected element. + * Runs only on [DSMaskNode] and [UssDirNode] (a USS mask) node types + */ + override fun actionPerformed(e: AnActionEvent) { + val view = e.getExplorerView() ?: return + val selectedNodeData = view.mySelectedNodesData[0] + val node = selectedNodeData.node + + if (node is DSMaskNode || (node is UssDirNode && node.isUssMask)) { + val parentWS = node.parent?.value as FilesWorkingSet + var wsConfToUpdate = configCrudable.getByUniqueKey(parentWS.uuid)?.clone() + if (wsConfToUpdate != null) { + val initialState = + if (node is DSMaskNode) MaskState(mask = node.value.mask, type = MaskType.ZOS) + else MaskState(mask = (node as UssDirNode).value.path, type = MaskType.USS) + val initialStateWithWS = MaskStateWithWS(initialState, parentWS) + val dialog = AddOrEditMaskDialog(e.project, "Edit Mask", initialStateWithWS) + if (dialog.showAndGet()) { + val changedMaskState = dialog.state + wsConfToUpdate = + if (node is DSMaskNode) editChangedDSMask(initialState.mask, wsConfToUpdate, changedMaskState) + else editChangedUssPath(initialState.mask, wsConfToUpdate, changedMaskState) + + configCrudable.update(wsConfToUpdate) + } + } + } + } + + /** + * Make "Edit" action visible if a context menu is called. + * The "Edit" action will be visible if: + * 1. A context menu is triggered in a [FileExplorerView] + * 2. Selected only one node + * 3. The node is a [DSMaskNode], [UssDirNode] (a USS mask) + */ + override fun update(e: AnActionEvent) { + val view = e.getExplorerView() ?: let { + e.presentation.isEnabledAndVisible = false + return + } + val selectedNodesData = view.mySelectedNodesData + if (selectedNodesData.size != 1) { + e.presentation.isEnabledAndVisible = false + return + } + val node = selectedNodesData[0].node + if (node !is DSMaskNode && (node !is UssDirNode || !node.isUssMask)) { + e.presentation.isEnabledAndVisible = false + return + } + } + + /** + * Determines if an action is dumb aware + */ + override fun isDumbAware(): Boolean { + return true + } +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt index 701b349d..cd9abec7 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt @@ -78,8 +78,8 @@ class ForceRenameAction : AnAction() { if (it is UssFileNode && it.value.filenameInternal == text) { val confirmTemplate = "You are going to rename file $virtualFilePath \n" + - "into existing one. This operation cannot be undone. \n" + - "Would you like to proceed?" + "into existing one. This operation cannot be undone. \n" + + "Would you like to proceed?" return Messages.showOkCancelDialog( confirmTemplate, "Warning", @@ -178,7 +178,7 @@ class ForceRenameAction : AnAction() { val selectedNodes = view.mySelectedNodesData e.presentation.isEnabledAndVisible = if (selectedNodes.size == 1) { val node = selectedNodes[0].node - (node is UssDirNode && !node.isConfigUssPath) || node is UssFileNode + (node is UssDirNode && !node.isUssMask) || node is UssFileNode } else { false } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt index aef79d5c..2c4fdb1f 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt @@ -19,162 +19,154 @@ import com.intellij.openapi.vfs.VirtualFile import eu.ibagroup.formainframe.analytics.AnalyticsService import eu.ibagroup.formainframe.analytics.events.FileAction import eu.ibagroup.formainframe.analytics.events.FileEvent -import eu.ibagroup.formainframe.config.configCrudable -import eu.ibagroup.formainframe.config.ws.DSMask -import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.attributes.FileAttributes import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes import eu.ibagroup.formainframe.dataops.attributes.RemoteMemberAttributes import eu.ibagroup.formainframe.dataops.attributes.RemoteUssAttributes import eu.ibagroup.formainframe.dataops.operations.RenameOperation -import eu.ibagroup.formainframe.explorer.FilesWorkingSet import eu.ibagroup.formainframe.explorer.ui.* -import eu.ibagroup.formainframe.utils.clone -import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey import eu.ibagroup.formainframe.utils.service // TODO: remove in v1.*.*-223 and greater +import eu.ibagroup.formainframe.vfs.MFVirtualFile /** - * Class which represents a rename action + * Class which represents a "Rename" action. + * The action is shown and triggered only on [UssFileNode], [UssDirNode] (not a USS mask), + * [LibraryNode] and [FileLikeDatasetNode] node types */ class RenameAction : AnAction() { - /** - * Overloaded method of AnAction abstract class. Tells what to do if an action was submitted - */ - override fun actionPerformed(e: AnActionEvent) { - val view = e.getExplorerView() ?: return - val selectedNode = view.mySelectedNodesData[0] - val node = selectedNode.node - var initialState = "" - if (node is DSMaskNode) { - initialState = (selectedNode.node.value as DSMask).mask - val dialog = RenameDialog(e.project, "Dataset Mask", selectedNode, this, initialState) - if (dialog.showAndGet()) { - val parentValue = selectedNode.node.parent?.value as FilesWorkingSet - val wsToUpdate = configCrudable.getByUniqueKey(parentValue.uuid)?.clone() - if (wsToUpdate != null) { - wsToUpdate.dsMasks.filter { it.mask == initialState }[0].mask = dialog.state.uppercase() - configCrudable.update(wsToUpdate) - } - } - } else if (node is LibraryNode || node is FileLikeDatasetNode) { - val attributes = selectedNode.attributes ?: return - var type = "" - if (attributes is RemoteDatasetAttributes) { - initialState = attributes.datasetInfo.name - type = "Dataset" - } else if (attributes is RemoteMemberAttributes) { - initialState = attributes.info.name - type = "Member" - } - val dialog = RenameDialog(e.project, type, selectedNode, this, initialState) - val file = node.virtualFile - if (dialog.showAndGet() && file != null) { - runRenameOperation(e.project, file, attributes, dialog.state, node) - service().trackAnalyticsEvent(FileEvent(attributes, FileAction.RENAME)) - } - } else if (selectedNode.node is UssDirNode && selectedNode.node.isConfigUssPath) { - initialState = selectedNode.node.value.path - val dialog = RenameDialog(e.project, "USS Mask", selectedNode, this, initialState) - if (dialog.showAndGet()) { - val parentValue = selectedNode.node.parent?.value as FilesWorkingSet - val wsToUpdate = configCrudable.getByUniqueKey(parentValue.uuid)?.clone() - if (wsToUpdate != null) { - wsToUpdate.ussPaths.filter { it.path == initialState }[0].path = dialog.state - configCrudable.update(wsToUpdate) - } - } - - } else if (selectedNode.node is UssDirNode || selectedNode.node is UssFileNode) { - val attributes = selectedNode.attributes as RemoteUssAttributes - val file = selectedNode.file - val dialog = RenameDialog( - e.project, - if (attributes.isDirectory) "Directory" else "File", - selectedNode, - this, - attributes.name - ) - if (dialog.showAndGet() && file != null) { - runRenameOperation(e.project, file, attributes, dialog.state, node) - service().trackAnalyticsEvent(FileEvent(attributes, FileAction.RENAME)) - } - } - } - /** * Method to run rename operation. It passes the control to rename operation runner - * @param project - current project - * @param file - a virtual file to be renamed - * @param attributes - remote attributes of the given virtual file - * @param newName - a new name of the virtual file in VFS - * @param node - an instance of explorer unit + * @param project the current project + * @param file the virtual file to be renamed + * @param type the type of the virtual file to be renamed + * @param attributes remote attributes of the given virtual file + * @param newName a new name of the virtual file in VFS + * @param node an instance of the explorer node * @throws any throwable during the processing of the request * @return Void */ private fun runRenameOperation( project: Project?, file: VirtualFile, + type: String, attributes: FileAttributes, newName: String, node: ExplorerTreeNode<*, *> ) { runBackgroundableTask( - title = "Renaming file ${file.name} to $newName", + title = "Renaming $type ${file.name} to $newName", project = project, cancellable = true ) { runCatching { - node.explorer.componentManager.service().performOperation( - operation = RenameOperation( - file = file, - attributes = attributes, - newName = newName - ), - progressIndicator = it - ) - }.onSuccess { - node.parent?.cleanCacheIfPossible(cleanBatchedQuery = true) - }.onFailure { - node.explorer.reportThrowable(it, project) + node.explorer.componentManager + .service() + .performOperation( + operation = RenameOperation( + file = file, + attributes = attributes, + newName = newName + ), + progressIndicator = it + ) + } + .onSuccess { + node.parent?.cleanCacheIfPossible(cleanBatchedQuery = true) + } + .onFailure { + node.explorer.reportThrowable(it, project) + } + } + } + + /** + * Run "Rename" action on the selected element. + * Runs only on [UssFileNode], [UssDirNode] (not a USS mask), [LibraryNode] or [FileLikeDatasetNode] node types + */ + override fun actionPerformed(e: AnActionEvent) { + val view = e.getExplorerView() ?: return + val selectedNodeData = view.mySelectedNodesData[0] + val node = selectedNodeData.node + if ( + node is LibraryNode || + node is FileLikeDatasetNode || + (node is UssDirNode && !node.isUssMask) || + node is UssFileNode + ) { + val attributes = selectedNodeData.attributes ?: return + val type: String + val state: String + val file: MFVirtualFile? + when (attributes) { + is RemoteDatasetAttributes -> { + type = "Dataset" + state = attributes.datasetInfo.name + file = node.virtualFile + } + + is RemoteMemberAttributes -> { + type = "Member" + state = attributes.info.name + file = node.virtualFile + } + + is RemoteUssAttributes -> { + type = if (attributes.isDirectory) "Directory" else "File" + state = attributes.name + file = selectedNodeData.file + } + + else -> { + throw Exception("Error during rename action execution: Unknown attributes type. $attributes") + } + } + val dialog = RenameDialog(e.project, type, selectedNodeData, this, state) + if (dialog.showAndGet() && file != null) { + runRenameOperation(e.project, file, type, attributes, dialog.state, node) + service().trackAnalyticsEvent(FileEvent(attributes, FileAction.RENAME)) } } } /** - * Method determines if an action is visible for particular virtual file in VFS + * Make "Rename" action visible if a context menu is called. + * The "Rename" action will be visible if: + * 1. A context menu is triggered in a [FileExplorerView] + * 2. Selected only one node + * 3. The node is a [UssFileNode], [UssDirNode] (not a USS mask), [LibraryNode] or [FileLikeDatasetNode] + * 4. The node is a non-migrated dataset */ override fun update(e: AnActionEvent) { val view = e.getExplorerView() ?: let { e.presentation.isEnabledAndVisible = false return } - val selectedNodes = view.mySelectedNodesData - e.presentation.isEnabledAndVisible = if (selectedNodes.size == 1) { - val node = selectedNodes[0].node - if (node is FilesWorkingSetNode || node is LoadingNode || node is LoadMoreNode) { - false - } else { - val file = selectedNodes[0].file - var isMigrated = false - if (file != null) { - val attributes = service().tryToGetAttributes(file) as? RemoteDatasetAttributes - isMigrated = attributes?.isMigrated ?: false - } - !isMigrated - } - } else { - false + val selectedNodesData = view.mySelectedNodesData + if (selectedNodesData.size != 1) { + e.presentation.isEnabledAndVisible = false + return + } + val node = selectedNodesData[0].node + if ( + node !is UssFileNode && + (node !is UssDirNode || node.isUssMask) && + node !is LibraryNode && + node !is FileLikeDatasetNode + ) { + e.presentation.isEnabledAndVisible = false + return } - if (e.presentation.isEnabledAndVisible) { - val selectedNode = selectedNodes[0] - if (selectedNode.node is DSMaskNode || (selectedNode.node is UssDirNode && selectedNode.node.isConfigUssPath)) { - e.presentation.text = "Edit" - } else { - e.presentation.text = "Rename" + val file = selectedNodesData[0].file + if (file != null) { + val attributes = service().tryToGetAttributes(file) as? RemoteDatasetAttributes + if (attributes?.isMigrated == true) { + e.presentation.isEnabledAndVisible = false + return } } + e.presentation.isEnabledAndVisible = true } /** diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddMaskDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt similarity index 72% rename from src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddMaskDialog.kt rename to src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt index 4fdf5fcf..acf81b25 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddMaskDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt @@ -20,16 +20,32 @@ import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.gridLayout.HorizontalAlign import eu.ibagroup.formainframe.common.ui.StatefulComponent import eu.ibagroup.formainframe.config.ws.MaskStateWithWS -import eu.ibagroup.formainframe.utils.* +import eu.ibagroup.formainframe.utils.MaskType +import eu.ibagroup.formainframe.utils.validateDatasetMask +import eu.ibagroup.formainframe.utils.validateForBlank +import eu.ibagroup.formainframe.utils.validateUssMask +import eu.ibagroup.formainframe.utils.validateWorkingSetMaskName import java.awt.Dimension import javax.swing.JComponent -class AddMaskDialog(project: Project?, override var state: MaskStateWithWS) : DialogWrapper(project), - StatefulComponent { +/** + * Dialog to add a new or edit an existing mask + */ +class AddOrEditMaskDialog(project: Project?, dialogTitle: String, override var state: MaskStateWithWS) : + DialogWrapper(project), StatefulComponent { + + companion object { + + // TODO: Remove when it becomes possible to mock class constructor with init section. + /** Wrapper for init() method. It is necessary only for test purposes for now. */ + private fun initialize(init: () -> Unit) { + init() + } + } init { - title = "Create Mask" - init() + title = dialogTitle + initialize { init() } } override fun createCenterPanel(): JComponent { @@ -79,11 +95,14 @@ class AddMaskDialog(project: Project?, override var state: MaskStateWithWS) : Di null } .validationOnApply { - validateForBlank(it.text, it) - ?: validateWorkingSetMaskName(it, state.ws) ?: if (state.type == MaskType.ZOS) + var validationResult = validateForBlank(it.text, it) ?: validateWorkingSetMaskName(it, state) + if (validationResult == null) { + validationResult = if (state.type == MaskType.ZOS) validateDatasetMask(it.text, component) else validateUssMask(it.text, it) + } + validationResult } .apply { focused() } .apply { diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DSMaskNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DSMaskNode.kt index 7f4cbb70..1f83234b 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DSMaskNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DSMaskNode.kt @@ -34,7 +34,7 @@ class DSMaskNode( treeStructure: ExplorerTreeStructureBase ) : RemoteMFFileFetchNode( dsMask, project, parent, workingSet, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { override fun update(presentation: PresentationData) { presentation.addText(value.mask, SimpleTextAttributes.REGULAR_ATTRIBUTES) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileExplorerView.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileExplorerView.kt index 6ba210ff..ad64b0ed 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileExplorerView.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileExplorerView.kt @@ -18,7 +18,10 @@ import com.intellij.ide.dnd.DnDTarget import com.intellij.ide.dnd.FileCopyPasteUtil import com.intellij.ide.projectView.ProjectView import com.intellij.openapi.Disposable -import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.actionSystem.ActionGroup +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.components.service import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.progress.runModalTask @@ -102,7 +105,7 @@ class FileExplorerView( internal val isCut = AtomicBoolean(false) private val cutCopyPredicate: (NodeData<*>) -> Boolean = { - it.attributes?.isCopyPossible == true && (!isCut.get() || it.node !is UssDirNode || !it.node.isConfigUssPath) + it.attributes?.isCopyPossible == true && (!isCut.get() || it.node !is UssDirNode || !it.node.isUssMask) } /** @@ -450,7 +453,7 @@ class FileExplorerView( it.unit.removeMask(it.value) } } - selected.map { it.node }.filter { it is UssDirNode && it.isConfigUssPath } + selected.map { it.node }.filter { it is UssDirNode && it.isUssMask } .filter { explorer.isUnitPresented((it as UssDirNode).unit) } .forEach { val node = it as UssDirNode @@ -472,7 +475,7 @@ class FileExplorerView( } val nodeDataAndPaths = selected .filterNot { - it.node is FilesWorkingSetNode || it.node is DSMaskNode || (it.node is UssDirNode && it.node.isConfigUssPath) + it.node is FilesWorkingSetNode || it.node is DSMaskNode || (it.node is UssDirNode && it.node.isUssMask) }.mapNotNull { Pair(it, it.file?.getParentsChain() ?: return@mapNotNull null) } @@ -535,7 +538,7 @@ class FileExplorerView( return selected.any { it.node is FilesWorkingSetNode || it.node is DSMaskNode - || (it.node is UssDirNode && it.node.isConfigUssPath) + || (it.node is UssDirNode && it.node.isUssMask) || deleteOperations.any { op -> dataOpsManager.isOperationSupported(op) } } } @@ -581,7 +584,7 @@ class FileExplorerView( * Class containing together node, corresponding file and its attributes. * @author Viktar Mushtsin. */ -data class NodeData( +data class NodeData( val node: ExplorerTreeNode, val file: MFVirtualFile?, val attributes: FileAttributes? diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt index 23e87d80..94e903bf 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt @@ -32,7 +32,7 @@ import icons.ForMainframeIcons private val migratedIcon = AllIcons.FileTypes.Any_type -/** Datasets and USS files representation as file node in the explorer tree view */ +/** PS dataset or a PDS dataset member representation as file node in the explorer tree view */ class FileLikeDatasetNode( file: MFVirtualFile, project: Project, @@ -41,7 +41,7 @@ class FileLikeDatasetNode( treeStructure: ExplorerTreeStructureBase ) : ExplorerUnitTreeNodeBase>( file, project, parent, unit, treeStructure -), MFNode { +) { override fun isAlwaysLeaf(): Boolean { return !value.isDirectory diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FilesWorkingSetNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FilesWorkingSetNode.kt index da7eb9b7..c48094b3 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FilesWorkingSetNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FilesWorkingSetNode.kt @@ -26,7 +26,7 @@ class FilesWorkingSetNode( treeStructure: ExplorerTreeStructureBase ) : WorkingSetNode( workingSet, project, parent, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { private val valueForFilesWS = value as FilesWorkingSet diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesFilterNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesFilterNode.kt index e48d2c66..11632891 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesFilterNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesFilterNode.kt @@ -34,7 +34,7 @@ class JesFilterNode( treeStructure: ExplorerTreeStructureBase ) : RemoteMFFileFetchNode( jobsFilter, project, parent, workingSet, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { override val query: RemoteQuery? get() { diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesWsNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesWsNode.kt index 55df3091..a585aac5 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesWsNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JesWsNode.kt @@ -27,7 +27,7 @@ class JesWsNode( treeStructure: ExplorerTreeStructureBase ) : WorkingSetNode( workingSet, project, parent, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { override val regularTooltip = "JES Working Set" diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JobNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JobNode.kt index 430fbb08..7569ee66 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JobNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/JobNode.kt @@ -41,7 +41,7 @@ class JobNode( treeStructure: ExplorerTreeStructureBase ) : RemoteMFFileFetchNode( library, project, parent, workingSet, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { override fun makeFetchTaskTitle(query: RemoteQuery): String { return "Fetching members for ${query.request.library.name}" } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LibraryNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LibraryNode.kt index 4cc10f77..69f8d992 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LibraryNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LibraryNode.kt @@ -16,9 +16,12 @@ import com.intellij.ide.util.treeView.AbstractTreeNode import com.intellij.openapi.project.Project import com.intellij.ui.SimpleTextAttributes import eu.ibagroup.formainframe.config.connect.ConnectionConfig -import eu.ibagroup.formainframe.dataops.* +import eu.ibagroup.formainframe.dataops.BatchedRemoteQuery +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.RemoteQuery import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes import eu.ibagroup.formainframe.dataops.fetch.LibraryQuery +import eu.ibagroup.formainframe.dataops.getAttributesService import eu.ibagroup.formainframe.explorer.FilesWorkingSet import eu.ibagroup.formainframe.utils.service import eu.ibagroup.formainframe.vfs.MFVirtualFile @@ -33,7 +36,7 @@ class LibraryNode( treeStructure: ExplorerTreeStructureBase ) : RemoteMFFileFetchNode( library, project, parent, workingSet, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { override val query: RemoteQuery? get() { diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/MFNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/MFNode.kt deleted file mode 100644 index 1f750877..00000000 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/MFNode.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This program and the accompanying materials are made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v20.html - * - * SPDX-License-Identifier: EPL-2.0 - * - * Copyright IBA Group 2020 - */ - -package eu.ibagroup.formainframe.explorer.ui - -interface MFNode { -} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/RenameDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/RenameDialog.kt index dc852f75..2c67d27f 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/RenameDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/RenameDialog.kt @@ -18,38 +18,42 @@ import com.intellij.ui.dsl.builder.bindText import com.intellij.ui.dsl.builder.panel import eu.ibagroup.formainframe.common.ui.StatefulComponent import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes -import eu.ibagroup.formainframe.explorer.FilesWorkingSet import eu.ibagroup.formainframe.explorer.actions.DuplicateMemberAction import eu.ibagroup.formainframe.explorer.actions.RenameAction -import eu.ibagroup.formainframe.utils.* +import eu.ibagroup.formainframe.utils.validateDatasetNameOnInput +import eu.ibagroup.formainframe.utils.validateForBlank +import eu.ibagroup.formainframe.utils.validateMemberName +import eu.ibagroup.formainframe.utils.validateUssFileName +import eu.ibagroup.formainframe.utils.validateUssFileNameAlreadyExists import javax.swing.JComponent import javax.swing.JTextField /** * Base class to create an instance of rename dialog object * @param type represents a virtual file type - file or directory - * @param selectedNode represents the current node object + * @param selectedNodeData represents the current node object * @param currentAction represents the current action to be performed - rename or force rename * @param state represents the current state */ -class RenameDialog(project: Project?, - type: String, - private val selectedNode: NodeData<*>, - private val currentAction: AnAction, - override var state: String +class RenameDialog( + project: Project?, + type: String, + private val selectedNodeData: NodeData<*>, + private val currentAction: AnAction, + override var state: String ) : DialogWrapper(project), StatefulComponent { - companion object { + companion object { - // TODO: Remove when it becomes possible to mock class constructor with init section. - /** Wrapper for init() method. It is necessary only for test purposes for now. */ - private fun initialize(init: () -> Unit) { - init() - } + // TODO: Remove when it becomes possible to mock class constructor with init section. + /** Wrapper for init() method. It is necessary only for test purposes for now. */ + private fun initialize(init: () -> Unit) { + init() } + } - private val node = selectedNode.node + private val node = selectedNodeData.node /** * Creates UI component of the object @@ -60,7 +64,7 @@ class RenameDialog(project: Project?, label("New name: ") textField() .bindText(this@RenameDialog::state) - .validationOnApply { validateOnBlank(it) ?: validateOnInput(it) } + .validationOnApply { validateForBlank(it) ?: validateOnInput(it) } .apply { focused() } } } @@ -75,17 +79,11 @@ class RenameDialog(project: Project?, } /** - * Sets validation rules for text field + * Validate a new name for the selected node component */ private fun validateOnInput(component: JTextField): ValidationInfo? { - val attributes = selectedNode.attributes + val attributes = selectedNodeData.attributes when (node) { - is DSMaskNode -> { - return validateDatasetMask(component.text, component) ?: validateWorkingSetMaskName( - component, - node.parent?.value as FilesWorkingSet - ) - } is LibraryNode, is FileLikeDatasetNode -> { return if (attributes is RemoteDatasetAttributes) { validateDatasetNameOnInput(component) @@ -93,23 +91,10 @@ class RenameDialog(project: Project?, validateMemberName(component) } } - is UssDirNode -> { - return if (node.isConfigUssPath) { - validateUssMask(component.text, component) ?: validateWorkingSetMaskName( - component, - node.parent?.value as FilesWorkingSet - ) - } else { - if (currentAction is RenameAction) { - validateUssFileName(component) ?: validateUssFileNameAlreadyExists(component, selectedNode) - } else { - validateUssFileName(component) - } - } - } - is UssFileNode -> { + + is UssDirNode, is UssFileNode -> { return if (currentAction is RenameAction) { - validateUssFileName(component) ?: validateUssFileNameAlreadyExists(component, selectedNode) + validateUssFileName(component) ?: validateUssFileNameAlreadyExists(component, selectedNodeData) } else { validateUssFileName(component) } @@ -118,11 +103,4 @@ class RenameDialog(project: Project?, return null } - /** - * Sets validation rule on blank for text field - */ - private fun validateOnBlank(component: JTextField): ValidationInfo? { - return validateForBlank(component) - } - } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/SpoolFileNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/SpoolFileNode.kt index 2763b803..96db8844 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/SpoolFileNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/SpoolFileNode.kt @@ -34,7 +34,7 @@ class SpoolFileNode( treeStructure: ExplorerTreeStructureBase ) : ExplorerUnitTreeNodeBase>( file, project, parent, unit, treeStructure -), MFNode { +) { override fun update(presentation: PresentationData) { val attributes = service().tryToGetAttributes(value) as? RemoteSpoolFileAttributes val spoolFile = attributes?.info diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/UssDirNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/UssDirNode.kt index 34f10c65..fe71a8fc 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/UssDirNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/UssDirNode.kt @@ -58,7 +58,7 @@ class UssDirNode( super.init() } - val isConfigUssPath = vFile == null + val isUssMask = vFile == null override val query: RemoteQuery? get() { @@ -111,9 +111,11 @@ class UssDirNode( isRootNode -> { AllIcons.Nodes.Module } + vFile != null -> { IconUtil.getIcon(vFile!!, 0, project) } + else -> { AllIcons.Nodes.Folder } @@ -122,9 +124,11 @@ class UssDirNode( isRootNode -> { value.path } + vFile != null -> { vFile!!.presentableName } + else -> { value.path.split("/").last() } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/WorkingSetNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/WorkingSetNode.kt index 3178fb14..25555474 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/WorkingSetNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/WorkingSetNode.kt @@ -27,14 +27,14 @@ private val grayscaleIcon = IconUtil.desaturate(regularIcon) private val errorIcon = LayeredIcon(grayscaleIcon, errorIconElement) /** Base implementation of working set tree node */ -abstract class WorkingSetNode( +abstract class WorkingSetNode( workingSet: WorkingSet, project: Project, parent: ExplorerTreeNode, treeStructure: ExplorerTreeStructureBase ) : ExplorerUnitTreeNodeBase, WorkingSet>( workingSet, project, parent, workingSet, treeStructure -), MFNode, RefreshableNode { +), RefreshableNode { protected var cachedChildrenInternal: MutableCollection>? = null abstract val regularTooltip: String diff --git a/src/main/kotlin/eu/ibagroup/formainframe/utils/validationFunctions.kt b/src/main/kotlin/eu/ibagroup/formainframe/utils/validationFunctions.kt index 38e80834..020b2c19 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/utils/validationFunctions.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/utils/validationFunctions.kt @@ -14,8 +14,8 @@ import com.intellij.openapi.ui.ValidationInfo import com.intellij.ui.components.JBTextField import eu.ibagroup.formainframe.config.connect.ConnectionConfig import eu.ibagroup.formainframe.config.ws.JobsFilter +import eu.ibagroup.formainframe.config.ws.MaskStateWithWS import eu.ibagroup.formainframe.config.ws.WorkingSetConfig -import eu.ibagroup.formainframe.explorer.FilesWorkingSet import eu.ibagroup.formainframe.explorer.ui.NodeData import eu.ibagroup.formainframe.explorer.ui.UssDirNode import eu.ibagroup.formainframe.explorer.ui.UssFileNode @@ -40,10 +40,10 @@ private val segmentLengthErrorText = "Each name segment (qualifier) is 1 to 8 ch private val charactersLengthExceededErrorText = "Dataset name cannot exceed 44 characters" private val segmentCharsErrorText = "$segmentLengthErrorText," + - "\nthe first of which must be alphabetic (A to Z) or national (# @ \$)." + - "\nThe remaining seven characters are either alphabetic," + - "\nnumeric (0 - 9), national, a hyphen (-)." + - "\nName segments are separated by a period (.)" + "\nthe first of which must be alphabetic (A to Z) or national (# @ \$)." + + "\nThe remaining seven characters are either alphabetic," + + "\nnumeric (0 - 9), national, a hyphen (-)." + + "\nName segments are separated by a period (.)" private val jobIdRegex = Regex("[A-Za-z0-9]+") private val volserRegex = Regex("[A-Za-z0-9]{1,6}") private val firstLetterRegex = Regex("[A-Z@\$#a-z]") @@ -120,18 +120,21 @@ fun validateWorkingSetName( } /** - * Validate working set mask name not to be the same as the existing one + * Validate working set mask name not to be the same as the existing one. + * Validation is skipped if the name of the mask stays the same as the previous one * @param component the component to check the working set mask name - * @param ws working set to check the existing masks + * @param state the mask state with working set inside */ -fun validateWorkingSetMaskName(component: JTextField, ws: FilesWorkingSet): ValidationInfo? { - val maskAlreadyExists = ws.masks.map { it.mask }.contains(component.text.uppercase()) - || ws.ussPaths.map { it.path }.contains(component.text) +fun validateWorkingSetMaskName(component: JTextField, state: MaskStateWithWS): ValidationInfo? { + val textToCheck = if (state.type == MaskType.ZOS) component.text.uppercase() else component.text + val sameMaskName = state.mask == textToCheck + val maskAlreadyExists = state.ws.masks.map { it.mask }.contains(component.text.uppercase()) + || state.ws.ussPaths.map { it.path }.contains(component.text) - return if (maskAlreadyExists) { + return if (!sameMaskName && maskAlreadyExists) { ValidationInfo( "You must provide unique mask in working set. Working Set " + - "\"${ws.name}\" already has mask - ${component.text}", component + "\"${state.ws.name}\" already has mask - ${component.text}", component ) } else { null @@ -378,7 +381,7 @@ fun validateJobFilter( */ fun validateUssFileNameAlreadyExists(component: JTextField, selectedNode: NodeData<*>): ValidationInfo? { val text: String = component.text - val childrenNodesFromParent = selectedNode.node.parent?.children + val childrenNodesFromParent = selectedNode.node.parent?.children?.filter { it != selectedNode.node } when (selectedNode.node) { is UssFileNode -> { childrenNodesFromParent?.forEach { @@ -480,7 +483,10 @@ fun validateForPositiveInteger(component: JTextField): ValidationInfo? { */ fun validateForGreaterOrEqualValue(component: JTextField, value: Int): ValidationInfo? { return if ((component.text.toIntOrNull() ?: -1) < value) { - ValidationInfo(if (value == 0) "Enter a positive number" else "Enter a number greater than or equal to $value", component) + ValidationInfo( + if (value == 0) "Enter a positive number" else "Enter a number greater than or equal to $value", + component + ) } else { null } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7fb1149f..7162b997 100755 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -123,7 +123,8 @@ Example of how to see the output:
- + @@ -493,6 +494,10 @@ Example of how to see the output:
icon="ForMainframeIcons.DatasetMask" text="Mask"/> + + + diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt index 551876d2..64510360 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt @@ -10,55 +10,30 @@ package eu.ibagroup.formainframe.explorer -import com.intellij.notification.NotificationType import com.intellij.openapi.Disposable -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.DataKey -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl -import eu.ibagroup.formainframe.api.ZosmfApi import eu.ibagroup.formainframe.config.ConfigService import eu.ibagroup.formainframe.config.ConfigStateV2 import eu.ibagroup.formainframe.config.configCrudable import eu.ibagroup.formainframe.config.connect.ConnectionConfig -import eu.ibagroup.formainframe.config.connect.CredentialService import eu.ibagroup.formainframe.config.makeCrudableWithoutListeners import eu.ibagroup.formainframe.config.ws.DSMask import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig -import eu.ibagroup.formainframe.config.ws.JobsFilter import eu.ibagroup.formainframe.config.ws.UssPath -import eu.ibagroup.formainframe.dataops.DataOpsManager -import eu.ibagroup.formainframe.dataops.Operation -import eu.ibagroup.formainframe.dataops.UnitRemoteQueryImpl -import eu.ibagroup.formainframe.dataops.attributes.* -import eu.ibagroup.formainframe.dataops.log.JobLogFetcher -import eu.ibagroup.formainframe.dataops.log.MFLogger -import eu.ibagroup.formainframe.explorer.actions.GetJobPropertiesAction -import eu.ibagroup.formainframe.explorer.actions.PurgeJobAction -import eu.ibagroup.formainframe.explorer.actions.RenameAction -import eu.ibagroup.formainframe.explorer.ui.* -import eu.ibagroup.formainframe.testServiceImpl.TestDataOpsManagerImpl -import eu.ibagroup.formainframe.ui.build.jobs.JobBuildTreeView -import eu.ibagroup.formainframe.utils.* -import eu.ibagroup.formainframe.utils.crudable.Crudable -import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey -import eu.ibagroup.formainframe.vfs.MFVirtualFile +import eu.ibagroup.formainframe.utils.gson +import eu.ibagroup.formainframe.utils.optional +import eu.ibagroup.formainframe.utils.toMutableList import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe -import io.mockk.* -import org.zowe.kotlinsdk.CancelJobPurgeOutRequest -import org.zowe.kotlinsdk.JESApi -import org.zowe.kotlinsdk.Job -import org.zowe.kotlinsdk.SpoolFile -import retrofit2.Call -import retrofit2.Response -import java.lang.IllegalStateException +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.spyk +import io.mockk.unmockkAll import java.util.* import java.util.stream.Stream @@ -353,935 +328,6 @@ class ExplorerTestSpec : ShouldSpec({ should("perform paste accepting conflicts") {} should("perform paste declining conflicts") {} } - context("explorer module: actions/RenameAction") { - val renameAction = RenameAction() - - lateinit var crudableMock: Crudable - - val selectedNodesMock = mockk>>() - val fileExplorerViewMock = mockk() - every { fileExplorerViewMock.mySelectedNodesData } returns selectedNodesMock - - lateinit var presentationText: String - var isEnabledAndVisible = false - - val anActionEventMock = mockk() - every { anActionEventMock.presentation.isEnabledAndVisible = any() } answers { - isEnabledAndVisible = firstArg() - every { anActionEventMock.presentation.isEnabledAndVisible } returns isEnabledAndVisible - } - every { anActionEventMock.presentation.text = any() } answers { - presentationText = firstArg() - } - every { anActionEventMock.project } returns mockk() - - val selectedNodeMock = mockk>() - every { selectedNodesMock[0] } returns selectedNodeMock - - val virtualFileMock = mockk() - every { virtualFileMock.name } returns "fileName" - - val explorerMock = mockk>() - every { explorerMock.componentManager } returns ApplicationManager.getApplication() - every { explorerMock.reportThrowable(any(), any()) } returns Unit - - val dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl - - var updated = false - var renamed = false - - beforeEach { - presentationText = "" - isEnabledAndVisible = false - - every { anActionEventMock.getExplorerView() } returns fileExplorerViewMock - - every { selectedNodesMock.size } returns 1 - - every { selectedNodeMock.node } returns mockk() - every { selectedNodeMock.file } returns virtualFileMock - every { selectedNodeMock.attributes } returns mockk() - - renamed = false - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes { - return mockk() - } - - override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { - renamed = true - @Suppress("UNCHECKED_CAST") - return Unit as R - } - } - - mockkObject(RenameDialog) - every { RenameDialog["initialize"](any<() -> Unit>()) } returns Unit - - mockkConstructor(RenameDialog::class) - every { anyConstructed().showAndGet() } returns true - - val configServiceMock = mockk() - mockkObject(ConfigService) - every { ConfigService.instance } returns configServiceMock - - crudableMock = mockk() - every { configServiceMock.crudable } returns crudableMock - - updated = false - every { crudableMock.update(any()) } answers { - updated = true - mockk() - } - - mockkStatic(ExplorerTreeNode::cleanCacheIfPossible) - } - afterEach { - unmockkAll() - } - - // actionPerformed - context("rename dataset mask") { - val dsMaskMock = mockk() - every { dsMaskMock.mask } returns "ZOSMFAD*" - - val dsMaskNodeMock = mockk() - every { dsMaskNodeMock.value } returns dsMaskMock - - val uuid = "uuid" - val filesWSMock = mockk() - every { filesWSMock.uuid } returns uuid - - beforeEach { - every { selectedNodeMock.node } returns dsMaskNodeMock - every { dsMaskNodeMock.parent?.value } returns filesWSMock - } - - should("perform rename on dataset mask") { - var filesWSConfig = FilesWorkingSetConfig( - "", "", mutableListOf(DSMask("ZOSMFAD*", mutableListOf())), mutableListOf() - ) - every { crudableMock.getByUniqueKey(uuid) } returns filesWSConfig - - val state = "ZOSMF*" - every { anyConstructed().state } returns state - - every { crudableMock.update(any()) } answers { - filesWSConfig = firstArg() - mockk() - } - - renameAction.actionPerformed(anActionEventMock) - - val actual = (filesWSConfig.dsMasks as MutableList)[0].mask - - assertSoftly { actual shouldBe state } - } - should("not perform rename on dataset mask if dialog is closed") { - every { anyConstructed().showAndGet() } returns false - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { updated shouldBe false } - } - should("not perform rename on dataset mask if parent node is null") { - every { dsMaskNodeMock.parent } returns null - - var throwable: Throwable? = null - runCatching { - renameAction.actionPerformed(anActionEventMock) - }.onFailure { - throwable = it - } - - val expected = - NullPointerException("null cannot be cast to non-null type eu.ibagroup.formainframe.explorer.FilesWorkingSet") - - assertSoftly { - throwable shouldBe expected - updated shouldBe false - } - } - should("not perform rename on dataset mask if working set is not found") { - every { crudableMock.getByUniqueKey(uuid) } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { updated shouldBe false } - } - should("not perform rename on dataset mask if list of DS masks is empty") { - val filesWSConfig = FilesWorkingSetConfig( - "", "", mutableListOf(), mutableListOf() - ) - every { crudableMock.getByUniqueKey(uuid) } returns filesWSConfig - - var throwable: Throwable? = null - runCatching { - renameAction.actionPerformed(anActionEventMock) - }.onFailure { - throwable = it - } - - val expected = IndexOutOfBoundsException("Index 0 out of bounds for length 0") - - assertSoftly { - throwable shouldBe expected - updated shouldBe false - } - } - should("not perform rename on dataset mask if selected mask is not found in list of DS masks") { - val filesWSConfig = FilesWorkingSetConfig( - "", "", mutableListOf(DSMask("ZOSMF*", mutableListOf())), mutableListOf() - ) - every { crudableMock.getByUniqueKey(uuid) } returns filesWSConfig - - var throwable: Throwable? = null - runCatching { - renameAction.actionPerformed(anActionEventMock) - }.onFailure { - throwable = it - } - - val expected = IndexOutOfBoundsException("Index 0 out of bounds for length 0") - - assertSoftly { - throwable shouldBe expected - updated shouldBe false - } - } - } - context("rename dataset") { - val libraryNodeMock = mockk() - every { libraryNodeMock.explorer } returns explorerMock - - val attributes = mockk() - every { attributes.datasetInfo.name } returns "dataset" - - beforeEach { - every { selectedNodeMock.node } returns libraryNodeMock - every { selectedNodeMock.attributes } returns attributes - - every { libraryNodeMock.virtualFile } returns virtualFileMock - every { libraryNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit - } - - should("perform rename on dataset") { - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe true } - } - should("not perform rename on dataset if dialog is closed") { - every { anyConstructed().showAndGet() } returns false - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe false } - } - should("not perform rename on dataset if virtual file is null") { - every { (libraryNodeMock as ExplorerTreeNode).virtualFile } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe false } - } - } - context("rename dataset member") { - val fileLikeDSNodeMock = mockk() - every { fileLikeDSNodeMock.explorer } returns explorerMock - every { fileLikeDSNodeMock.virtualFile } returns virtualFileMock - - val attributes = mockk() - every { attributes.info.name } returns "member" - - beforeEach { - every { selectedNodeMock.node } returns fileLikeDSNodeMock - every { selectedNodeMock.attributes } returns attributes - - every { fileLikeDSNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit - } - - should("perform rename on dataset member") { - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe true } - } - should("not perform rename on dataset member if attributes is null") { - every { selectedNodeMock.attributes } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe false } - } - } - context("rename USS file") { - val ussFileNodeMock = mockk() - every { ussFileNodeMock.explorer } returns explorerMock - - val attributes = mockk() - every { attributes.name } returns "ussFile" - every { attributes.isDirectory } returns false - - beforeEach { - every { selectedNodeMock.node } returns ussFileNodeMock - every { selectedNodeMock.attributes } returns attributes - - every { ussFileNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit - } - - should("perform rename on USS file") { - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe true } - } - should("perform rename on USS file but don't clean cache if parent node is null") { - every { ussFileNodeMock.parent } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe true } - } - should("not perform rename on USS file if dialog is closed") { - every { anyConstructed().showAndGet() } returns false - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe false } - } - should("not perform rename on USS file if virtual file is null") { - every { selectedNodeMock.file } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe false } - } - } - context("rename USS directory") { - val ussDirNodeMock = mockk() - every { ussDirNodeMock.explorer } returns explorerMock - every { ussDirNodeMock.isConfigUssPath } returns false - - val attributes = mockk() - every { attributes.name } returns "ussDir" - every { attributes.isDirectory } returns true - - beforeEach { - every { selectedNodeMock.node } returns ussDirNodeMock - every { selectedNodeMock.attributes } returns attributes - - every { ussDirNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit - } - - should("perform rename on USS directory") { - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe true } - } - should("not perform rename on USS directory if operation throws an exception") { - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { - override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { - throw Exception("Exception message") - } - } - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { renamed shouldBe false } - } - } - context("rename USS mask") { - val ussDirNodeMock = mockk() - every { ussDirNodeMock.explorer } returns explorerMock - every { ussDirNodeMock.isConfigUssPath } returns true - every { ussDirNodeMock.value.path } returns "/u/ZOSMFAD" - - val uuid = "uuid" - val filesWSMock = mockk() - every { filesWSMock.uuid } returns uuid - - beforeEach { - every { selectedNodeMock.node } returns ussDirNodeMock - - every { ussDirNodeMock.parent?.value } returns filesWSMock - } - - should("perform rename on USS mask") { - var filesWSConfig = FilesWorkingSetConfig( - "", "", mutableListOf(), mutableListOf(UssPath("/u/ZOSMFAD")) - ) - every { crudableMock.getByUniqueKey(uuid) } returns filesWSConfig - - val state = "/u/ZOSMF" - every { anyConstructed().state } returns state - - every { crudableMock.update(any()) } answers { - filesWSConfig = firstArg() - mockk() - } - - renameAction.actionPerformed(anActionEventMock) - - val actual = (filesWSConfig.ussPaths as MutableList)[0].path - - assertSoftly { actual shouldBe state } - } - should("not perform rename on USS mask if dialog is closed") { - every { anyConstructed().showAndGet() } returns false - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { updated shouldBe false } - } - should("not perform rename on USS mask if parent node is null") { - every { ussDirNodeMock.parent } returns null - - var throwable: Throwable? = null - runCatching { - renameAction.actionPerformed(anActionEventMock) - }.onFailure { - throwable = it - } - - val expected = - NullPointerException("null cannot be cast to non-null type eu.ibagroup.formainframe.explorer.FilesWorkingSet") - - assertSoftly { - throwable shouldBe expected - updated shouldBe false - } - } - should("not perform rename on USS mask if working set is not found") { - every { crudableMock.getByUniqueKey(uuid) } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { updated shouldBe false } - } - should("not perform rename on USS mask if list of USS masks is empty") { - val filesWSConfig = FilesWorkingSetConfig( - "", "", mutableListOf(), mutableListOf() - ) - every { crudableMock.getByUniqueKey(uuid) } returns filesWSConfig - - var throwable: Throwable? = null - runCatching { - renameAction.actionPerformed(anActionEventMock) - }.onFailure { - throwable = it - } - - val expected = IndexOutOfBoundsException("Index 0 out of bounds for length 0") - - assertSoftly { - throwable shouldBe expected - updated shouldBe false - } - } - should("not perform rename on USS mask if selected mask is not found in list of USS masks") { - val filesWSConfig = FilesWorkingSetConfig( - "", "", mutableListOf(), mutableListOf(UssPath("/u/ZOSMF")) - ) - every { crudableMock.getByUniqueKey(uuid) } returns filesWSConfig - - var throwable: Throwable? = null - runCatching { - renameAction.actionPerformed(anActionEventMock) - }.onFailure { - throwable = it - } - - val expected = IndexOutOfBoundsException("Index 0 out of bounds for length 0") - - assertSoftly { - throwable shouldBe expected - updated shouldBe false - } - } - } - should("not perform rename action if explorer view is null") { - every { anActionEventMock.getExplorerView() } returns null - - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { updated shouldBe false } - } - should("not perform rename action if selected node is not a DS mask, dataset, dataset member, USS mask, USS directory or USS file") { - renameAction.actionPerformed(anActionEventMock) - - assertSoftly { updated shouldBe false } - } - - // update - should("rename action is enabled and visible") { - renameAction.update(anActionEventMock) - } - should("rename action is enabled and visible if selected node file is null") { - every { selectedNodeMock.file } returns null - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe true } - } - should("rename action is enabled and visible if file attributes are not dataset attributes") { - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes? { - return null - } - } - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe true } - } - should("rename action is enabled and visible if selected node is dataset mask") { - every { selectedNodeMock.node } returns mockk() - - renameAction.update(anActionEventMock) - - assertSoftly { - isEnabledAndVisible shouldBe true - presentationText shouldBe "Edit" - } - } - should("rename action is enabled and visible if selected node is USS mask") { - val ussDirNodeMock = mockk() - every { ussDirNodeMock.isConfigUssPath } returns true - - every { selectedNodeMock.node } returns ussDirNodeMock - - renameAction.update(anActionEventMock) - - assertSoftly { - isEnabledAndVisible shouldBe true - presentationText shouldBe "Edit" - } - } - should("rename action is enabled and visible if selected node is USS directory") { - val ussDirNodeMock = mockk() - every { ussDirNodeMock.isConfigUssPath } returns false - - every { selectedNodeMock.node } returns ussDirNodeMock - - renameAction.update(anActionEventMock) - - assertSoftly { - isEnabledAndVisible shouldBe true - presentationText shouldBe "Rename" - } - } - should("rename action is not enabled and not visible if explorer view is null") { - every { anActionEventMock.getExplorerView() } returns null - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe false } - } - should("rename action is not enabled and not visible if selected nodes size grater than one") { - every { selectedNodesMock.size } returns 2 - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe false } - } - should("rename action is not enabled and not visible if selected node is 'files working set' node") { - every { selectedNodeMock.node } returns mockk() - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe false } - } - should("rename action is not enabled and not visible if selected node is 'loading' node") { - every { selectedNodeMock.node } returns mockk>() - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe false } - } - should("rename action is not enabled and not visible if selected node is 'load more' mode") { - every { selectedNodeMock.node } returns mockk>() - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe false } - } - should("rename action is not enabled and not visible if dataset is migrated") { - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes { - val attributesMock = mockk() - every { attributesMock.isMigrated } returns true - return attributesMock - } - } - - renameAction.update(anActionEventMock) - - assertSoftly { isEnabledAndVisible shouldBe false } - } - - // isDumbAware - should("action is dumb aware") { - val actual = renameAction.isDumbAware - - assertSoftly { actual shouldBe true } - } - } - context("explorer module: actions/PurgeJobAction") { - context("actionPerformed") { - val purgeAction = PurgeJobAction() - val mockActionEventForJesEx = mockk() - val jesExplorerView = mockk() - - val job = mockk() - every { job.jobName } returns "name" - every { job.jobId } returns "id" - val connectionConfig = mockk() - every { connectionConfig.uuid } returns "uuid" - val jobsFilter = spyk( - JobsFilter( - "owner", - "prefix", - "id" - ) - ) - mockkObject(gson) - every { gson.fromJson(any() as String, Job::class.java) } returns job - - mockkObject(CredentialService) - every { CredentialService.instance.getUsernameByKey(any()) } returns "user" - every { CredentialService.instance.getPasswordByKey(any()) } returns "pas" - - every { mockActionEventForJesEx.getExplorerView() } returns jesExplorerView - - val jobNode = mockk() - val virtualFile = mockk() - - val nodeData = spyk( - NodeData( - jobNode, - virtualFile, - null - ) - ) - every { jesExplorerView.mySelectedNodesData } returns listOf(nodeData) - - val parentNode = mockk() - val query = spyk( - UnitRemoteQueryImpl( - jobsFilter, - connectionConfig - ) - ) - every { parentNode.query } returns query - justRun { parentNode.cleanCache() } - - val jesApi = mockk() - val call = mockk>>() - mockkObject(ZosmfApi) - every { ZosmfApi.instance.getApi(any(), any()) } returns jesApi - every { jesApi.getFilteredJobs(any(), any(), any(), any(), any(), any(), any(), any()) } returns call - - val response = mockk>>() - val jobList = mutableListOf(job, job) - every { call.execute() } returns response - every { response.body() } answers { - if (jobList.isNotEmpty()) { - jobList.removeFirst() - jobList - } else { - null - } - } - every { response.isSuccessful } returns true - - every { jobNode.virtualFile } returns virtualFile - every { jobNode.parent } returns parentNode - - val explorer = mockk>() - every { jobNode.explorer } returns explorer - every { explorer.componentManager } returns ApplicationManager.getApplication() - - lateinit var dataOpsManager: TestDataOpsManagerImpl - - val project = mockk() - every { - mockActionEventForJesEx.project - } returns project - - every { - jesExplorerView.explorer - } returns explorer - - val mockRequest = mockk() - val jobAttr = spyk( - RemoteJobAttributes( - job, - "test", - mutableListOf(JobsRequester(connectionConfig, jobsFilter)) - ) - ) - every { jobAttr.clone() } returns jobAttr - - val mockActionEventForJobsLog = mockk() - val jobsLogView = mockk() - - every { mockActionEventForJobsLog.getData(any() as DataKey) } returns jobsLogView - every { mockActionEventForJobsLog.project } returns project - - val mockkLogger = mockk>() - val mockkFetcher = mockk() - - every { jobsLogView.getJobLogger() } returns mockkLogger - every { - hint(JobLogFetcher::class) - mockkLogger.logFetcher - } returns mockkFetcher - every { mockkFetcher.getCachedJobStatus() } returns job - every { jobsLogView.getConnectionConfig() } returns connectionConfig - - should("perform purge on job successfully") { - - dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes { - return jobAttr - } - - override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { - return mockRequest as R - } - - } - - var isOperationSucceededForJesEx = false - every { - explorer.showNotification(any(), any(), NotificationType.INFORMATION, any()) - } answers { - isOperationSucceededForJesEx = true - } - - var isOperationSucceededForJobsLog = false - every { - jobsLogView.showNotification(any(), any(), any(), NotificationType.INFORMATION) - } answers { - isOperationSucceededForJobsLog = true - } - - var isVisibleInJes = false - every { mockActionEventForJesEx.presentation.setVisible(true) } answers { isVisibleInJes = true } - - every { job.status } returns mockk() - var isEnabledInJobsLog = true - every { mockActionEventForJobsLog.presentation.setEnabled(false) } answers { isEnabledInJobsLog = false } - - purgeAction.actionPerformed(mockActionEventForJesEx) - purgeAction.update(mockActionEventForJesEx) - purgeAction.actionPerformed(mockActionEventForJobsLog) - purgeAction.update(mockActionEventForJobsLog) - - assertSoftly { - isOperationSucceededForJesEx shouldBe true - isOperationSucceededForJobsLog shouldBe true - isVisibleInJes shouldBe true - isEnabledInJobsLog shouldBe true - purgeAction.isDumbAware shouldBe true - } - - } - should("perform purge on job with error") { - - val updateJesAction = mockk() - val jesViewForUpdate = mockk() - every { updateJesAction.getExplorerView() } returns jesViewForUpdate - every { jesViewForUpdate.mySelectedNodesData } returns listOf() - - val updateJesAction2 = mockk() - val jesViewForUpdate2 = mockk() - every { updateJesAction2.getExplorerView() } returns jesViewForUpdate2 - val errorNode = mockk>() - every { jesViewForUpdate2.mySelectedNodesData } returns listOf( - NodeData( - errorNode, - virtualFile, - null - ) - ) - - dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes { - return jobAttr - } - - override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { - throw IllegalStateException("No operation is expected to be performed.") - } - } - - var isOperationFailedForJesEx = false - every { - explorer.showNotification(any(), any(), NotificationType.ERROR, any()) - } answers { - isOperationFailedForJesEx = true - } - - var isOperationFailedForJobsLog = false - every { - jobsLogView.showNotification(any(), any(), any(), NotificationType.ERROR) - } answers { - isOperationFailedForJobsLog = true - } - - var isOperationFailedForNoContextAction = false - val mockActionEventWithoutDataContext = mockk() - every { mockActionEventWithoutDataContext.getData(any() as DataKey) } returns null - every { mockActionEventWithoutDataContext.presentation.setEnabledAndVisible(false) } answers { - isOperationFailedForNoContextAction = true - } - - every { job.status } returns null - var isEnabledInJobsLog = true - every { mockActionEventForJobsLog.presentation.setEnabled(false) } answers { isEnabledInJobsLog = false } - - var isVisibleForJes = true - every { updateJesAction.presentation.setVisible(false) } answers { isVisibleForJes = false } - var isVisibleForJes2 = true - every { updateJesAction2.presentation.setVisible(false) } answers { isVisibleForJes2 = false } - - purgeAction.actionPerformed(mockActionEventForJesEx) - purgeAction.actionPerformed(mockActionEventForJobsLog) - purgeAction.actionPerformed(mockActionEventWithoutDataContext) - purgeAction.update(mockActionEventWithoutDataContext) - purgeAction.update(mockActionEventForJobsLog) - purgeAction.update(updateJesAction) - purgeAction.update(updateJesAction2) - - assertSoftly { - isOperationFailedForJesEx shouldBe true - isOperationFailedForJobsLog shouldBe true - isEnabledInJobsLog shouldBe false - isVisibleForJes shouldBe false - isVisibleForJes2 shouldBe false - isOperationFailedForNoContextAction shouldBe true - } - } - } - } - context("explorer module: actions/GetJobPropertiesAction") { - context("actionPerformed") { - val getPropertiesEvent = mockk() - val project = mockk() - every { getPropertiesEvent.project } returns project - - val virtualFile = mockk() - - val jesView = mockk() - val explorer = mockk>() - every { getPropertiesEvent.getExplorerView() } returns jesView - - val connectionConfig = mockk() - every { connectionConfig.uuid } returns "uuid" - - should("get job properties") { - val jobNode = mockk() - every { jobNode.virtualFile } returns virtualFile - val nodeData = spyk(NodeData(jobNode, virtualFile, null)) - - every { jesView.mySelectedNodesData } returns listOf(nodeData) - - every { jobNode.explorer } returns explorer - every { explorer.componentManager } returns ApplicationManager.getApplication() - - val job = mockk() - every { job.jobName } returns "name" - every { job.jobId } returns "id" - val jobsFilter = spyk(JobsFilter("owner", "prefix", "id")) - val jobAttr = spyk(RemoteJobAttributes(job, "test", mutableListOf(JobsRequester(connectionConfig, jobsFilter)))) - - val dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes { - return jobAttr - } - - override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { - throw IllegalStateException("No operation is expected to be performed.") - } - } - - val dialogMock = mockk() - every { dialogMock.showAndGet() } returns true - - mockkStatic(JobPropertiesDialog::class) - mockkObject(JobPropertiesDialog.Companion) - - mockkStatic(SpoolFilePropertiesDialog::class) - mockkObject(SpoolFilePropertiesDialog.Companion) - - every { JobPropertiesDialog.Companion.create(any() as Project?, any() as JobState) } returns dialogMock - every { SpoolFilePropertiesDialog.Companion.create(any() as Project?, any() as SpoolFileState) } answers { - throw IllegalStateException("Spool file properties dialog should not be used.") - } - - GetJobPropertiesAction().actionPerformed(getPropertiesEvent) - - verify { dialogMock.showAndGet() } - - } - should("get spool file properties") { - val spoolFileNode = mockk() - every { spoolFileNode.virtualFile } returns virtualFile - val nodeData = spyk(NodeData(spoolFileNode, virtualFile, null)) - - every { jesView.mySelectedNodesData } returns listOf(nodeData) - - every { spoolFileNode.explorer } returns explorer - every { explorer.componentManager } returns ApplicationManager.getApplication() - - val spoolFile = mockk() - - every { spoolFile.ddName } returns "ddname" - every { spoolFile.jobId } returns "jobid" - every { spoolFile.id } returns 1 - - mockkObject(gson) - every { gson.fromJson(any() as String, SpoolFile::class.java) } returns spoolFile - - val parentFile = mockk() - val spoolFileAttr = spyk(RemoteSpoolFileAttributes(spoolFile, parentFile)) - - val dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl - dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { - override fun tryToGetAttributes(file: VirtualFile): FileAttributes { - return spoolFileAttr - } - - override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { - throw IllegalStateException("No operation is expected to be performed.") - } - } - - val dialogMock = mockk() - every { dialogMock.showAndGet() } returns true - - mockkStatic(JobPropertiesDialog::class) - mockkObject(JobPropertiesDialog.Companion) - - mockkStatic(SpoolFilePropertiesDialog::class) - mockkObject(SpoolFilePropertiesDialog.Companion) - - every { JobPropertiesDialog.Companion.create(any() as Project?, any() as JobState) } answers { - throw IllegalStateException("Job properties dialog should not be used.") - } - every { - SpoolFilePropertiesDialog.Companion.create(any() as Project?, any() as SpoolFileState) - } returns dialogMock - - GetJobPropertiesAction().actionPerformed(getPropertiesEvent) - - verify { dialogMock.showAndGet() } - unmockkObject(gson) - } - } - } context("explorer module: actions/GetFilePropertiesAction") { // actionPerformed should("get dataset properties") {} diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt new file mode 100644 index 00000000..d2fc30da --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt @@ -0,0 +1,436 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.config.ConfigService +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.ws.DSMask +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.config.ws.MaskState +import eu.ibagroup.formainframe.config.ws.MaskStateWithWS +import eu.ibagroup.formainframe.config.ws.UssPath +import eu.ibagroup.formainframe.explorer.FilesWorkingSet +import eu.ibagroup.formainframe.explorer.ui.AddOrEditMaskDialog +import eu.ibagroup.formainframe.explorer.ui.DSMaskNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.NodeData +import eu.ibagroup.formainframe.explorer.ui.UssDirNode +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.utils.MaskType +import eu.ibagroup.formainframe.utils.crudable.Crudable +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.unmockkAll +import java.util.* + +class EditMaskActionTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + context("explorer module: actions/EditMaskAction") { + val editMaskAction = EditMaskAction() + + val uuid = "test" + val selectedNodeMock = mockk>() + val fileExplorerViewMock = mockk() + val anActionEventMock = mockk() + val explorerTreeNodeMock = mockk>() + val filesWorkingSetMock = mockk() + val configServiceMock = mockk() + val crudableMock = mockk() + val filesWorkingSetConfigMock = mockk() + + val explorerTreeStructBaseMock = object : TestExplorerTreeStructureBase(mockk(), mockk()) { + override fun registerNode(node: ExplorerTreeNode<*, *>) {} + } + + beforeEach { + every { filesWorkingSetMock.explorer } returns mockk() + + // Needed here to initialize other components somewhere (probably bug?) + UssDirNode(UssPath("test"), mockk(), explorerTreeNodeMock, filesWorkingSetMock, explorerTreeStructBaseMock) + + every { selectedNodeMock.node } returns mockk() + every { fileExplorerViewMock.mySelectedNodesData } returns listOf(selectedNodeMock) + every { anActionEventMock.getExplorerView() } returns fileExplorerViewMock + + every { filesWorkingSetMock.uuid } returns uuid + every { explorerTreeNodeMock.value } returns filesWorkingSetMock + + every { + crudableMock.getByUniqueKey(filesWorkingSetConfigMock::class.java, uuid) + } returns Optional.of(filesWorkingSetConfigMock) + + mockkObject(ConfigService) + every { ConfigService.instance } returns configServiceMock + every { configServiceMock.crudable } returns crudableMock + + every { anActionEventMock.project } returns mockk() + + mockkObject(AddOrEditMaskDialog) + every { AddOrEditMaskDialog["initialize"](any<() -> Unit>()) } returns Unit + + mockkConstructor(AddOrEditMaskDialog::class) + every { anyConstructed().showAndGet() } returns true + } + + afterEach { + clearAllMocks() + unmockkAll() + } + + context("actionPerformed") { + var updated = false + var changed = false + + beforeEach { + updated = false + changed = false + + every { + crudableMock.update(any()) + } answers { + updated = true + mockk() + } + } + context("generic") { + should("not perform edit action if explorer view is null") { + every { anActionEventMock.getExplorerView() } returns null + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + should("not perform edit action if selected node is not a DS or USS mask") { + every { selectedNodeMock.node } returns mockk>() + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + should("not perform edit action if selected node is a USS directory") { + val ussDirNode = + UssDirNode( + UssPath("test"), + mockk(), + explorerTreeNodeMock, + filesWorkingSetMock, + explorerTreeStructBaseMock, + mockk() + ) + + every { selectedNodeMock.node } returns ussDirNode + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + } + context("edit USS mask") { + lateinit var ussMaskNode: UssDirNode + + beforeEach { + updated = false + changed = false + + ussMaskNode = + UssDirNode(UssPath("test"), mockk(), explorerTreeNodeMock, filesWorkingSetMock, explorerTreeStructBaseMock) + + every { selectedNodeMock.node } returns ussMaskNode + every { filesWorkingSetConfigMock.ussPaths } returns mutableListOf(UssPath("test")) + every { filesWorkingSetConfigMock.dsMasks } returns mutableListOf() + } + should("perform edit on USS mask") { + every { + crudableMock.update(any()) + } answers { + updated = true + val wsConfToUpdate = firstArg() + if (wsConfToUpdate.ussPaths.size == 1) { + val ussPath = wsConfToUpdate.ussPaths.first() + if (ussPath.path == "test_passed") { + changed = true + } + } + mockk() + } + + every { + anyConstructed().state + } returns MaskStateWithWS(MaskState("test_passed", MaskType.USS), filesWorkingSetMock) + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe true } + assertSoftly { changed shouldBe true } + } + should("perform edit on USS mask changing mask type") { + every { + crudableMock.update(any()) + } answers { + updated = true + val wsConfToUpdate = firstArg() + if (wsConfToUpdate.dsMasks.size == 1 && wsConfToUpdate.ussPaths.isEmpty()) { + val dsMask = wsConfToUpdate.dsMasks.first() + if (dsMask.mask == "TEST_PASSED") { + changed = true + } + } + mockk() + } + + every { + anyConstructed().state + } returns MaskStateWithWS(MaskState("test_passed", MaskType.ZOS), filesWorkingSetMock) + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe true } + assertSoftly { changed shouldBe true } + } + should("not perform edit on USS mask if dialog is closed") { + every { anyConstructed().showAndGet() } returns false + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + should("not perform edit on USS mask if working set is not found") { + every { + crudableMock.getByUniqueKey(filesWorkingSetConfigMock::class.java, uuid) + } returns Optional.ofNullable(null) + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + should("not perform edit on USS mask if list of USS masks is empty") { + every { filesWorkingSetConfigMock.ussPaths } returns mutableListOf() + + var throwable: Throwable? = null + runCatching { + editMaskAction.actionPerformed(anActionEventMock) + }.onFailure { + throwable = it + } + + val expected = IndexOutOfBoundsException("Index 0 out of bounds for length 0") + + assertSoftly { + throwable shouldBe expected + updated shouldBe false + } + } + should("not perform edit on USS mask if selected mask is not found in list of USS masks") { + every { filesWorkingSetConfigMock.ussPaths } returns mutableListOf(UssPath("other")) + + var throwable: Throwable? = null + runCatching { + editMaskAction.actionPerformed(anActionEventMock) + }.onFailure { + throwable = it + } + + val expected = IndexOutOfBoundsException("Index 0 out of bounds for length 0") + + assertSoftly { + throwable shouldBe expected + updated shouldBe false + } + } + } + context("edit DS mask") { + lateinit var dsMaskNode: DSMaskNode + + beforeEach { + updated = false + changed = false + + dsMaskNode = + DSMaskNode( + DSMask("test", mutableListOf()), + mockk(), + explorerTreeNodeMock, + filesWorkingSetMock, + explorerTreeStructBaseMock + ) + + every { selectedNodeMock.node } returns dsMaskNode + every { filesWorkingSetConfigMock.ussPaths } returns mutableListOf() + every { filesWorkingSetConfigMock.dsMasks } returns mutableListOf(DSMask("test", mutableListOf())) + } + should("perform edit on DS mask") { + every { + crudableMock.update(any()) + } answers { + updated = true + val wsConfToUpdate = firstArg() + if (wsConfToUpdate.dsMasks.size == 1) { + val dsMask = wsConfToUpdate.dsMasks.first() + if (dsMask.mask == "TEST_PASSED") { + changed = true + } + } + mockk() + } + + every { + anyConstructed().state + } returns MaskStateWithWS(MaskState("test_passed", MaskType.ZOS), filesWorkingSetMock) + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe true } + assertSoftly { changed shouldBe true } + } + should("perform edit on DS mask changing mask type") { + every { + crudableMock.update(any()) + } answers { + updated = true + val wsConfToUpdate = firstArg() + if (wsConfToUpdate.ussPaths.size == 1 && wsConfToUpdate.dsMasks.isEmpty()) { + val ussPath = wsConfToUpdate.ussPaths.first() + if (ussPath.path == "test_passed") { + changed = true + } + } + mockk() + } + + every { + anyConstructed().state + } returns MaskStateWithWS(MaskState("test_passed", MaskType.USS), filesWorkingSetMock) + + editMaskAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe true } + assertSoftly { changed shouldBe true } + } + } + } + + context("update") { + var enabledAndVisible = true + + beforeEach { + enabledAndVisible = true + every { anActionEventMock.presentation } returns mockk() + every { anActionEventMock.presentation.isEnabledAndVisible = any() } answers { + enabledAndVisible = firstArg() + mockk() + } + } + should("edit action is enabled and visible for dataset mask node") { + val dsMaskNode = + DSMaskNode( + DSMask("test", mutableListOf()), + mockk(), + explorerTreeNodeMock, + filesWorkingSetMock, + explorerTreeStructBaseMock + ) + + every { selectedNodeMock.node } returns dsMaskNode + + editMaskAction.update(anActionEventMock) + + assertSoftly { enabledAndVisible shouldBe true } + } + should("edit action is enabled and visible for USS mask node") { + val ussMaskNode = + UssDirNode( + UssPath("test"), + mockk(), + explorerTreeNodeMock, + filesWorkingSetMock, + explorerTreeStructBaseMock + ) + + every { selectedNodeMock.node } returns ussMaskNode + + editMaskAction.update(anActionEventMock) + + assertSoftly { enabledAndVisible shouldBe true } + } + should("edit action is not enabled and visible for USS dir node") { + val ussDirNode = + UssDirNode( + UssPath("test"), + mockk(), + explorerTreeNodeMock, + filesWorkingSetMock, + explorerTreeStructBaseMock, + mockk() + ) + + every { selectedNodeMock.node } returns ussDirNode + + editMaskAction.update(anActionEventMock) + + assertSoftly { enabledAndVisible shouldBe false } + } + should("edit action is not enabled and visible for other types of nodes") { + every { selectedNodeMock.node } returns mockk() + + editMaskAction.update(anActionEventMock) + + assertSoftly { enabledAndVisible shouldBe false } + } + should("edit action is not enabled and visible if selected more than one node") { + every { fileExplorerViewMock.mySelectedNodesData } returns listOf(mockk(), mockk()) + + editMaskAction.update(anActionEventMock) + + assertSoftly { enabledAndVisible shouldBe false } + } + should("edit action is not enabled and not visible if explorer view is null") { + every { anActionEventMock.getExplorerView() } returns null + + editMaskAction.update(anActionEventMock) + + assertSoftly { enabledAndVisible shouldBe false } + } + } + + context("isDumbAware") { + should("action is dumb aware") { + val actual = editMaskAction.isDumbAware + + assertSoftly { actual shouldBe true } + } + } + } +}) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/GetJobPropertiesActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/GetJobPropertiesActionTestSpec.kt new file mode 100644 index 00000000..e2b203fb --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/GetJobPropertiesActionTestSpec.kt @@ -0,0 +1,179 @@ +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.ws.JobsFilter +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.Operation +import eu.ibagroup.formainframe.dataops.attributes.FileAttributes +import eu.ibagroup.formainframe.dataops.attributes.JobsRequester +import eu.ibagroup.formainframe.dataops.attributes.RemoteJobAttributes +import eu.ibagroup.formainframe.dataops.attributes.RemoteSpoolFileAttributes +import eu.ibagroup.formainframe.explorer.Explorer +import eu.ibagroup.formainframe.explorer.JesWorkingSetImpl +import eu.ibagroup.formainframe.explorer.ui.JesExplorerView +import eu.ibagroup.formainframe.explorer.ui.JobNode +import eu.ibagroup.formainframe.explorer.ui.JobPropertiesDialog +import eu.ibagroup.formainframe.explorer.ui.JobState +import eu.ibagroup.formainframe.explorer.ui.NodeData +import eu.ibagroup.formainframe.explorer.ui.SpoolFileNode +import eu.ibagroup.formainframe.explorer.ui.SpoolFilePropertiesDialog +import eu.ibagroup.formainframe.explorer.ui.SpoolFileState +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.testServiceImpl.TestDataOpsManagerImpl +import eu.ibagroup.formainframe.utils.gson +import eu.ibagroup.formainframe.utils.service +import eu.ibagroup.formainframe.vfs.MFVirtualFile +import io.kotest.core.spec.style.ShouldSpec +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk +import io.mockk.unmockkObject +import io.mockk.verify +import org.zowe.kotlinsdk.Job +import org.zowe.kotlinsdk.SpoolFile + +class GetJobPropertiesActionTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + context("explorer module: actions/GetJobPropertiesAction") { + context("actionPerformed") { + val getPropertiesEvent = mockk() + val project = mockk() + every { getPropertiesEvent.project } returns project + + val virtualFile = mockk() + + val jesView = mockk() + val explorer = mockk>() + every { getPropertiesEvent.getExplorerView() } returns jesView + + val connectionConfig = mockk() + every { connectionConfig.uuid } returns "uuid" + + should("get job properties") { + val jobNode = mockk() + every { jobNode.virtualFile } returns virtualFile + val nodeData = spyk(NodeData(jobNode, virtualFile, null)) + + every { jesView.mySelectedNodesData } returns listOf(nodeData) + + every { jobNode.explorer } returns explorer + every { explorer.componentManager } returns ApplicationManager.getApplication() + + val job = mockk() + every { job.jobName } returns "name" + every { job.jobId } returns "id" + val jobsFilter = spyk(JobsFilter("owner", "prefix", "id")) + val jobAttr = spyk(RemoteJobAttributes(job, "test", mutableListOf(JobsRequester(connectionConfig, jobsFilter)))) + + val dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl + dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { + override fun tryToGetAttributes(file: VirtualFile): FileAttributes { + return jobAttr + } + + override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { + throw IllegalStateException("No operation is expected to be performed.") + } + } + + val dialogMock = mockk() + every { dialogMock.showAndGet() } returns true + + mockkStatic(JobPropertiesDialog::class) + mockkObject(JobPropertiesDialog) + + mockkStatic(SpoolFilePropertiesDialog::class) + mockkObject(SpoolFilePropertiesDialog) + + every { JobPropertiesDialog.create(any() as Project?, any() as JobState) } returns dialogMock + every { SpoolFilePropertiesDialog.create(any() as Project?, any() as SpoolFileState) } answers { + throw IllegalStateException("Spool file properties dialog should not be used.") + } + + GetJobPropertiesAction().actionPerformed(getPropertiesEvent) + + verify { dialogMock.showAndGet() } + + } + should("get spool file properties") { + val spoolFileNode = mockk() + every { spoolFileNode.virtualFile } returns virtualFile + val nodeData = spyk(NodeData(spoolFileNode, virtualFile, null)) + + every { jesView.mySelectedNodesData } returns listOf(nodeData) + + every { spoolFileNode.explorer } returns explorer + every { explorer.componentManager } returns ApplicationManager.getApplication() + + val spoolFile = mockk() + + every { spoolFile.ddName } returns "ddname" + every { spoolFile.jobId } returns "jobid" + every { spoolFile.id } returns 1 + + mockkObject(gson) + every { gson.fromJson(any() as String, SpoolFile::class.java) } returns spoolFile + + val parentFile = mockk() + val spoolFileAttr = spyk(RemoteSpoolFileAttributes(spoolFile, parentFile)) + + val dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl + dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { + override fun tryToGetAttributes(file: VirtualFile): FileAttributes { + return spoolFileAttr + } + + override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { + throw IllegalStateException("No operation is expected to be performed.") + } + } + + val dialogMock = mockk() + every { dialogMock.showAndGet() } returns true + + mockkStatic(JobPropertiesDialog::class) + mockkObject(JobPropertiesDialog) + + mockkStatic(SpoolFilePropertiesDialog::class) + mockkObject(SpoolFilePropertiesDialog) + + every { JobPropertiesDialog.create(any() as Project?, any() as JobState) } answers { + throw IllegalStateException("Job properties dialog should not be used.") + } + every { + SpoolFilePropertiesDialog.create(any() as Project?, any() as SpoolFileState) + } returns dialogMock + + GetJobPropertiesAction().actionPerformed(getPropertiesEvent) + + verify { dialogMock.showAndGet() } + unmockkObject(gson) + } + } + } +}) \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/PurgeJobActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/PurgeJobActionTestSpec.kt new file mode 100644 index 00000000..3331aacf --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/PurgeJobActionTestSpec.kt @@ -0,0 +1,315 @@ +package eu.ibagroup.formainframe.explorer.actions + +// TODO: this test suite needs to be reworked (?) as it gives StackOverflowError. May be the problem with MockK/Kotest + +// import com.intellij.notification.NotificationType +// import com.intellij.openapi.actionSystem.AnActionEvent +// import com.intellij.openapi.actionSystem.DataKey +// import com.intellij.openapi.application.ApplicationManager +// import com.intellij.openapi.progress.ProgressIndicator +// import com.intellij.openapi.project.Project +// import com.intellij.openapi.vfs.VirtualFile +// import com.intellij.testFramework.LightProjectDescriptor +// import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +// import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +// import eu.ibagroup.formainframe.api.ZosmfApi +// import eu.ibagroup.formainframe.config.connect.ConnectionConfig +// import eu.ibagroup.formainframe.config.connect.CredentialService +// import eu.ibagroup.formainframe.config.ws.JobsFilter +// import eu.ibagroup.formainframe.dataops.DataOpsManager +// import eu.ibagroup.formainframe.dataops.Operation +// import eu.ibagroup.formainframe.dataops.UnitRemoteQueryImpl +// import eu.ibagroup.formainframe.dataops.attributes.FileAttributes +// import eu.ibagroup.formainframe.dataops.attributes.JobsRequester +// import eu.ibagroup.formainframe.dataops.attributes.RemoteJobAttributes +// import eu.ibagroup.formainframe.dataops.log.JobLogFetcher +// import eu.ibagroup.formainframe.dataops.log.MFLogger +// import eu.ibagroup.formainframe.explorer.Explorer +// import eu.ibagroup.formainframe.explorer.JesWorkingSetImpl +// import eu.ibagroup.formainframe.explorer.ui.ErrorNode +// import eu.ibagroup.formainframe.explorer.ui.JesExplorerView +// import eu.ibagroup.formainframe.explorer.ui.JesFilterNode +// import eu.ibagroup.formainframe.explorer.ui.JobNode +// import eu.ibagroup.formainframe.explorer.ui.NodeData +// import eu.ibagroup.formainframe.explorer.ui.getExplorerView +// import eu.ibagroup.formainframe.testServiceImpl.TestDataOpsManagerImpl +// import eu.ibagroup.formainframe.ui.build.jobs.JobBuildTreeView +// import eu.ibagroup.formainframe.utils.gson +// import eu.ibagroup.formainframe.utils.service +// import eu.ibagroup.formainframe.vfs.MFVirtualFile +// import io.kotest.assertions.assertSoftly +// import io.kotest.core.spec.style.ShouldSpec +// import io.kotest.matchers.shouldBe +// import io.mockk.clearAllMocks +// import io.mockk.every +// import io.mockk.justRun +// import io.mockk.mockk +// import io.mockk.mockkObject +// import io.mockk.spyk +// import org.zowe.kotlinsdk.CancelJobPurgeOutRequest +// import org.zowe.kotlinsdk.JESApi +// import org.zowe.kotlinsdk.Job +// import retrofit2.Call +// import retrofit2.Response + +// class PurgeJobActionTestSpec : ShouldSpec({ +// beforeSpec { +// // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE +// val factory = IdeaTestFixtureFactory.getFixtureFactory() +// val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR +// val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") +// val fixture = fixtureBuilder.fixture +// val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( +// fixture, +// LightTempDirTestFixtureImpl(true) +// ) +// myFixture.setUp() +// } +// afterSpec { +// clearAllMocks() +// } +// context("explorer module: actions/PurgeJobAction") { +// context("actionPerformed") { +// val purgeAction = PurgeJobAction() +// val mockActionEventForJesEx = mockk() +// val jesExplorerView = mockk() + +// val job = mockk() +// every { job.jobName } returns "name" +// every { job.jobId } returns "id" +// val connectionConfig = mockk() +// every { connectionConfig.uuid } returns "uuid" +// val jobsFilter = spyk( +// JobsFilter( +// "owner", +// "prefix", +// "id" +// ) +// ) +// mockkObject(gson) +// every { gson.hint(Job::class).fromJson(any() as String, Job::class.java) } returns job + +// mockkObject(CredentialService) +// every { CredentialService.instance.getUsernameByKey(any()) } returns "user" +// every { CredentialService.instance.getPasswordByKey(any()) } returns "pas" + +// every { mockActionEventForJesEx.getExplorerView() } returns jesExplorerView + +// val jobNode = mockk() +// val virtualFile = mockk() + +// val nodeData = spyk( +// NodeData( +// jobNode, +// virtualFile, +// null +// ) +// ) +// every { jesExplorerView.mySelectedNodesData } returns listOf(nodeData) + +// val parentNode = mockk() +// val query = spyk( +// UnitRemoteQueryImpl( +// jobsFilter, +// connectionConfig +// ) +// ) +// every { parentNode.query } returns query +// justRun { parentNode.cleanCache() } + +// val jesApi = mockk() +// val call = mockk>>() +// mockkObject(ZosmfApi) +// every { ZosmfApi.instance.hint(JESApi::class).getApi(any(), any()) } returns jesApi +// every { jesApi.getFilteredJobs(any(), any(), any(), any(), any(), any(), any(), any()) } returns call + +// val response = mockk>>() +// val jobList = mutableListOf(job, job) +// every { call.execute() } returns response +// every { +// hint(List::class) +// response.body() +// } answers { +// if (jobList.isNotEmpty()) { +// jobList.removeFirst() +// jobList +// } else { +// null +// } +// } +// every { response.isSuccessful } returns true + +// every { jobNode.virtualFile } returns virtualFile +// every { jobNode.parent } returns parentNode + +// val explorer = mockk>() +// every { jobNode.explorer } returns explorer +// every { explorer.componentManager } returns ApplicationManager.getApplication() + +// lateinit var dataOpsManager: TestDataOpsManagerImpl + +// val project = mockk() +// every { +// mockActionEventForJesEx.project +// } returns project + +// every { +// jesExplorerView.explorer +// } returns explorer + +// val mockRequest = mockk() +// val jobAttr = spyk( +// RemoteJobAttributes( +// job, +// "test", +// mutableListOf(JobsRequester(connectionConfig, jobsFilter)) +// ) +// ) +// every { jobAttr.clone() } returns jobAttr + +// val mockActionEventForJobsLog = mockk() +// val jobsLogView = mockk() + +// every { mockActionEventForJobsLog.getData(any() as DataKey) } returns jobsLogView +// every { mockActionEventForJobsLog.project } returns project + +// val mockkLogger = mockk>() +// val mockkFetcher = mockk() + +// every { jobsLogView.getJobLogger() } returns mockkLogger +// every { +// hint(JobLogFetcher::class) +// mockkLogger.logFetcher +// } returns mockkFetcher +// every { mockkFetcher.getCachedJobStatus() } returns job +// every { jobsLogView.getConnectionConfig() } returns connectionConfig + +// should("perform purge on job successfully") { + +// dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl +// dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { +// override fun tryToGetAttributes(file: VirtualFile): FileAttributes { +// return jobAttr +// } + +// override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { +// return mockRequest as R +// } + +// } + +// var isOperationSucceededForJesEx = false +// every { +// explorer.showNotification(any(), any(), NotificationType.INFORMATION, any()) +// } answers { +// isOperationSucceededForJesEx = true +// } + +// var isOperationSucceededForJobsLog = false +// every { +// jobsLogView.showNotification(any(), any(), any(), NotificationType.INFORMATION) +// } answers { +// isOperationSucceededForJobsLog = true +// } + +// var isVisibleInJes = false +// every { mockActionEventForJesEx.presentation.isVisible = true } answers { isVisibleInJes = true } + +// every { job.status } returns mockk() +// var isEnabledInJobsLog = true +// every { mockActionEventForJobsLog.presentation.isEnabled = false } answers { isEnabledInJobsLog = false } + +// purgeAction.actionPerformed(mockActionEventForJesEx) +// purgeAction.update(mockActionEventForJesEx) +// purgeAction.actionPerformed(mockActionEventForJobsLog) +// purgeAction.update(mockActionEventForJobsLog) + +// assertSoftly { +// isOperationSucceededForJesEx shouldBe true +// isOperationSucceededForJobsLog shouldBe true +// isVisibleInJes shouldBe true +// isEnabledInJobsLog shouldBe true +// purgeAction.isDumbAware shouldBe true +// } + +// } +// should("perform purge on job with error") { + +// val updateJesAction = mockk() +// val jesViewForUpdate = mockk() +// every { updateJesAction.getExplorerView() } returns jesViewForUpdate +// every { jesViewForUpdate.mySelectedNodesData } returns listOf() + +// val updateJesAction2 = mockk() +// val jesViewForUpdate2 = mockk() +// every { updateJesAction2.getExplorerView() } returns jesViewForUpdate2 +// val errorNode = mockk>() +// every { jesViewForUpdate2.mySelectedNodesData } returns listOf( +// NodeData( +// errorNode, +// virtualFile, +// null +// ) +// ) + +// dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl +// dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorer.componentManager) { +// override fun tryToGetAttributes(file: VirtualFile): FileAttributes { +// return jobAttr +// } + +// override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { +// throw IllegalStateException("No operation is expected to be performed.") +// } +// } + +// var isOperationFailedForJesEx = false +// every { +// explorer.showNotification(any(), any(), NotificationType.ERROR, any()) +// } answers { +// isOperationFailedForJesEx = true +// } + +// var isOperationFailedForJobsLog = false +// every { +// jobsLogView.showNotification(any(), any(), any(), NotificationType.ERROR) +// } answers { +// isOperationFailedForJobsLog = true +// } + +// var isOperationFailedForNoContextAction = false +// val mockActionEventWithoutDataContext = mockk() +// every { mockActionEventWithoutDataContext.getData(any() as DataKey) } returns null +// every { mockActionEventWithoutDataContext.presentation.isEnabledAndVisible = false } answers { +// isOperationFailedForNoContextAction = true +// } + +// every { job.status } returns null +// var isEnabledInJobsLog = true +// every { mockActionEventForJobsLog.presentation.isEnabled = false } answers { isEnabledInJobsLog = false } + +// var isVisibleForJes = true +// every { updateJesAction.presentation.isVisible = false } answers { isVisibleForJes = false } +// var isVisibleForJes2 = true +// every { updateJesAction2.presentation.isVisible = false } answers { isVisibleForJes2 = false } + +// purgeAction.actionPerformed(mockActionEventForJesEx) +// purgeAction.actionPerformed(mockActionEventForJobsLog) +// purgeAction.actionPerformed(mockActionEventWithoutDataContext) +// purgeAction.update(mockActionEventWithoutDataContext) +// purgeAction.update(mockActionEventForJobsLog) +// purgeAction.update(updateJesAction) +// purgeAction.update(updateJesAction2) + +// assertSoftly { +// isOperationFailedForJesEx shouldBe true +// isOperationFailedForJobsLog shouldBe true +// isEnabledInJobsLog shouldBe false +// isVisibleForJes shouldBe false +// isVisibleForJes2 shouldBe false +// isOperationFailedForNoContextAction shouldBe true +// } +// } +// } +// } +// }) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameActionTestSpec.kt new file mode 100644 index 00000000..1e0d6b5b --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameActionTestSpec.kt @@ -0,0 +1,346 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.config.ConfigService +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.Operation +import eu.ibagroup.formainframe.dataops.attributes.* +import eu.ibagroup.formainframe.explorer.Explorer +import eu.ibagroup.formainframe.explorer.ui.* +import eu.ibagroup.formainframe.testServiceImpl.TestDataOpsManagerImpl +import eu.ibagroup.formainframe.utils.* +import eu.ibagroup.formainframe.utils.crudable.Crudable +import eu.ibagroup.formainframe.vfs.MFVirtualFile +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.* +import java.util.* + +class RenameActionTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + context("explorer module: actions/RenameAction") { + val renameAction = RenameAction() + + lateinit var crudableMock: Crudable + + val fileExplorerViewMock = mockk() + val selectedNodeDataMock = mockk>() + + var isEnabledAndVisible = false + + val anActionEventMock = mockk() + every { anActionEventMock.presentation.isEnabledAndVisible = any() } answers { + isEnabledAndVisible = firstArg() + every { anActionEventMock.presentation.isEnabledAndVisible } returns isEnabledAndVisible + } + every { anActionEventMock.project } returns mockk() + + val virtualFileMock = mockk() + every { virtualFileMock.name } returns "fileName" + + val explorerMock = mockk>() + every { explorerMock.componentManager } returns ApplicationManager.getApplication() + every { explorerMock.reportThrowable(any(), any()) } returns Unit + + val dataOpsManager = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl + + var updated = false + var renamed = false + + beforeEach { + isEnabledAndVisible = false + + every { fileExplorerViewMock.mySelectedNodesData } returns listOf(selectedNodeDataMock) + + every { anActionEventMock.getExplorerView() } returns fileExplorerViewMock + + every { selectedNodeDataMock.node } returns mockk() + every { selectedNodeDataMock.file } returns virtualFileMock + every { selectedNodeDataMock.attributes } returns mockk() + + renamed = false + dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { + override fun tryToGetAttributes(file: VirtualFile): FileAttributes { + return mockk() + } + + override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { + renamed = true + @Suppress("UNCHECKED_CAST") + return Unit as R + } + } + + mockkObject(RenameDialog) + every { RenameDialog["initialize"](any<() -> Unit>()) } returns Unit + + mockkConstructor(RenameDialog::class) + every { anyConstructed().showAndGet() } returns true + + val configServiceMock = mockk() + mockkObject(ConfigService) + every { ConfigService.instance } returns configServiceMock + + crudableMock = mockk() + every { configServiceMock.crudable } returns crudableMock + + updated = false + every { crudableMock.update(any()) } answers { + updated = true + mockk() + } + + mockkStatic(ExplorerTreeNode::cleanCacheIfPossible) + } + afterEach { + unmockkAll() + } + + context("actionPerformed") { + context("rename dataset") { + val libraryNodeMock = mockk() + every { libraryNodeMock.explorer } returns explorerMock + + val attributes = mockk() + every { attributes.datasetInfo.name } returns "dataset" + + beforeEach { + every { selectedNodeDataMock.node } returns libraryNodeMock + every { selectedNodeDataMock.attributes } returns attributes + + every { libraryNodeMock.virtualFile } returns virtualFileMock + every { libraryNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit + } + + should("perform rename on dataset") { + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe true } + } + should("not perform rename on dataset if dialog is closed") { + every { anyConstructed().showAndGet() } returns false + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe false } + } + should("not perform rename on dataset if virtual file is null") { + every { (libraryNodeMock as ExplorerTreeNode).virtualFile } returns null + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe false } + } + } + context("rename dataset member") { + val fileLikeDSNodeMock = mockk() + every { fileLikeDSNodeMock.explorer } returns explorerMock + every { fileLikeDSNodeMock.virtualFile } returns virtualFileMock + + val attributes = mockk() + every { attributes.info.name } returns "member" + + beforeEach { + every { selectedNodeDataMock.node } returns fileLikeDSNodeMock + every { selectedNodeDataMock.attributes } returns attributes + + every { fileLikeDSNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit + } + + should("perform rename on dataset member") { + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe true } + } + should("not perform rename on dataset member if attributes is null") { + every { selectedNodeDataMock.attributes } returns null + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe false } + } + } + context("rename USS file") { + val ussFileNodeMock = mockk() + every { ussFileNodeMock.explorer } returns explorerMock + + val attributes = mockk() + every { attributes.name } returns "ussFile" + every { attributes.isDirectory } returns false + + beforeEach { + every { selectedNodeDataMock.node } returns ussFileNodeMock + every { selectedNodeDataMock.attributes } returns attributes + + every { ussFileNodeMock.parent?.cleanCacheIfPossible(any()) } returns Unit + } + + should("perform rename on USS file") { + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe true } + } + should("perform rename on USS file but don't clean cache if parent node is null") { + every { ussFileNodeMock.parent } returns null + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe true } + } + should("not perform rename on USS file if dialog is closed") { + every { anyConstructed().showAndGet() } returns false + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe false } + } + should("not perform rename on USS file if virtual file is null") { + every { selectedNodeDataMock.file } returns null + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { renamed shouldBe false } + } + } + should("not perform rename action if explorer view is null") { + every { anActionEventMock.getExplorerView() } returns null + + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + should("not perform rename action if selected node is not a DS mask, dataset, dataset member, USS mask, USS directory or USS file") { + renameAction.actionPerformed(anActionEventMock) + + assertSoftly { updated shouldBe false } + } + } + + context("update") { + should("rename action is enabled and visible") { + renameAction.update(anActionEventMock) + } + should("rename action is enabled and visible if selected node file is null") { + every { selectedNodeDataMock.node } returns mockk() + every { selectedNodeDataMock.file } returns null + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe true } + } + should("rename action is enabled and visible if file attributes are not dataset attributes") { + every { selectedNodeDataMock.node } returns mockk() + every { selectedNodeDataMock.file } returns mockk() + dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { + override fun tryToGetAttributes(file: VirtualFile): FileAttributes? { + return null + } + } + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe true } + } + should("rename action is enabled and visible if selected node is USS directory") { + val ussDirNodeMock = mockk() + every { ussDirNodeMock.isUssMask } returns false + + every { selectedNodeDataMock.node } returns ussDirNodeMock + + renameAction.update(anActionEventMock) + + assertSoftly { + isEnabledAndVisible shouldBe true + } + } + should("rename action is not enabled and not visible if explorer view is null") { + every { anActionEventMock.getExplorerView() } returns null + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe false } + } + should("rename action is not enabled and not visible if selected nodes size grater than one") { + every { fileExplorerViewMock.mySelectedNodesData } returns listOf(selectedNodeDataMock, selectedNodeDataMock) + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe false } + } + should("rename action is not enabled and not visible if selected node is 'files working set' node") { + every { selectedNodeDataMock.node } returns mockk() + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe false } + } + should("rename action is not enabled and not visible if selected node is 'loading' node") { + every { selectedNodeDataMock.node } returns mockk>() + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe false } + } + should("rename action is not enabled and not visible if selected node is 'load more' mode") { + every { selectedNodeDataMock.node } returns mockk>() + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe false } + } + should("rename action is not enabled and not visible if dataset is migrated") { + dataOpsManager.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { + override fun tryToGetAttributes(file: VirtualFile): FileAttributes { + val attributesMock = mockk() + every { attributesMock.isMigrated } returns true + return attributesMock + } + } + + renameAction.update(anActionEventMock) + + assertSoftly { isEnabledAndVisible shouldBe false } + } + } + + context("isDumbAware") { + should("action is dumb aware") { + val actual = renameAction.isDumbAware + + assertSoftly { actual shouldBe true } + } + } + } +}) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/dummyClasses.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/dummyClasses.kt new file mode 100644 index 00000000..cdb4f421 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/dummyClasses.kt @@ -0,0 +1,55 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.ide.projectView.TreeStructureProvider +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import eu.ibagroup.formainframe.explorer.Explorer +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeStructureBase + +open class TestExplorerTreeStructureBase( + explorer: Explorer<*, *>, + project: Project +) : ExplorerTreeStructureBase(explorer, project) { + override fun registerNode(node: ExplorerTreeNode<*, *>) { + TODO("Not yet implemented") + } + + override fun findByValue(value: V): Collection> { + TODO("Not yet implemented") + } + + override fun findByPredicate(predicate: (ExplorerTreeNode<*, *>) -> Boolean): Collection> { + TODO("Not yet implemented") + } + + override fun findByVirtualFile(file: VirtualFile): Collection> { + TODO("Not yet implemented") + } + + override fun getRootElement(): Any { + TODO("Not yet implemented") + } + + override fun commit() { + TODO("Not yet implemented") + } + + override fun hasSomethingToCommit(): Boolean { + TODO("Not yet implemented") + } + + override fun getProviders(): MutableList? { + TODO("Not yet implemented") + } +} diff --git a/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt index 66c23dca..abb8ef56 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt @@ -18,7 +18,12 @@ import com.intellij.ui.components.JBTextField import eu.ibagroup.formainframe.config.ConfigStateV2 import eu.ibagroup.formainframe.config.connect.ConnectionConfig import eu.ibagroup.formainframe.config.makeCrudableWithoutListeners -import eu.ibagroup.formainframe.config.ws.* +import eu.ibagroup.formainframe.config.ws.DSMask +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.config.ws.JobsFilter +import eu.ibagroup.formainframe.config.ws.MaskStateWithWS +import eu.ibagroup.formainframe.config.ws.UssPath +import eu.ibagroup.formainframe.config.ws.WorkingSetConfig import eu.ibagroup.formainframe.explorer.FilesWorkingSet import eu.ibagroup.formainframe.explorer.ui.NodeData import eu.ibagroup.formainframe.explorer.ui.UssDirNode @@ -29,7 +34,10 @@ import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.longs.shouldBeGreaterThan import io.kotest.matchers.shouldBe -import io.mockk.* +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.spyk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.junit.jupiter.api.Assertions.* @@ -204,6 +212,7 @@ class UtilsTestSpec : ShouldSpec({ context("validateWorkingSetMaskName") { val jTextField = JTextField() val mockWs = mockk() + val mockMaskStateWithWs = mockk() should("validate working set mask name when there are no other working set masks") { jTextField.text = "/a1" @@ -211,7 +220,11 @@ class UtilsTestSpec : ShouldSpec({ every { mockWs.ussPaths } returns listOf() every { mockWs.masks } returns listOf() - val actual = validateWorkingSetMaskName(jTextField, mockWs) + every { mockMaskStateWithWs.ws } returns mockWs + every { mockMaskStateWithWs.mask } returns "" + every { mockMaskStateWithWs.type } returns MaskType.ZOS + + val actual = validateWorkingSetMaskName(jTextField, mockMaskStateWithWs) val expected = null assertSoftly { @@ -224,7 +237,11 @@ class UtilsTestSpec : ShouldSpec({ every { mockWs.ussPaths } returns listOf(UssPath("/path1"), UssPath("/path2")) every { mockWs.masks } returns listOf(DSMask("MASK1", mutableListOf())) - val actual = validateWorkingSetMaskName(jTextField, mockWs) + every { mockMaskStateWithWs.ws } returns mockWs + every { mockMaskStateWithWs.mask } returns "" + every { mockMaskStateWithWs.type } returns MaskType.ZOS + + val actual = validateWorkingSetMaskName(jTextField, mockMaskStateWithWs) val expected = null assertSoftly { @@ -238,12 +255,52 @@ class UtilsTestSpec : ShouldSpec({ every { mockWs.masks } returns listOf(DSMask("MASK.MASK", mutableListOf())) every { mockWs.name } returns "Ws name" - val actual = validateWorkingSetMaskName(jTextField, mockWs) + every { mockMaskStateWithWs.ws } returns mockWs + every { mockMaskStateWithWs.mask } returns "" + every { mockMaskStateWithWs.type } returns MaskType.USS + + val actual = validateWorkingSetMaskName(jTextField, mockMaskStateWithWs) val expected = ValidationInfo( "You must provide unique mask in working set. Working Set " + - "\"${mockWs.name}\" already has mask - ${jTextField.text}", jTextField + "\"${mockWs.name}\" already has mask - ${jTextField.text}", jTextField ) + assertSoftly { + actual shouldBe expected + } + } + should("validate working set mask name when the mask name is not changed") { + jTextField.text = "/path1" + + every { mockWs.ussPaths } returns listOf(UssPath("/path1"), UssPath("/path2")) + every { mockWs.masks } returns listOf(DSMask("MASK.MASK", mutableListOf())) + every { mockWs.name } returns "Ws name" + + every { mockMaskStateWithWs.ws } returns mockWs + every { mockMaskStateWithWs.mask } returns "/path1" + every { mockMaskStateWithWs.type } returns MaskType.USS + + val actual = validateWorkingSetMaskName(jTextField, mockMaskStateWithWs) + val expected = null + + assertSoftly { + actual shouldBe expected + } + } + should("validate working set mask name when the mask name is not changed (dataset mask)") { + jTextField.text = "MASK.name" + + every { mockWs.ussPaths } returns listOf() + every { mockWs.masks } returns listOf(DSMask("MASK.NAME", mutableListOf())) + every { mockWs.name } returns "Ws name" + + every { mockMaskStateWithWs.ws } returns mockWs + every { mockMaskStateWithWs.mask } returns "MASK.NAME" + every { mockMaskStateWithWs.type } returns MaskType.ZOS + + val actual = validateWorkingSetMaskName(jTextField, mockMaskStateWithWs) + val expected = null + assertSoftly { actual shouldBe expected } @@ -546,8 +603,10 @@ class UtilsTestSpec : ShouldSpec({ } context("validateUssFileNameAlreadyExists") { val jTextField = JTextField() - val mockFileNode = mockk() - val mockDirNode = mockk() + val mockFileNode1 = mockk() + val mockFileNode2 = mockk() + val mockDirNode1 = mockk() + val mockDirNode2 = mockk() mockkObject(MFVirtualFileSystem) every { MFVirtualFileSystem.instance } returns mockk() val mockVirtualFile = mockk() @@ -557,14 +616,14 @@ class UtilsTestSpec : ShouldSpec({ val mockNode = spyk( NodeData( - node = mockFileNode, + node = mockFileNode1, file = mockVirtualFile, attributes = null ) ) - every { mockFileNode.parent?.children } returns listOf(mockFileNode) - every { mockFileNode.value.filenameInternal } returns "filename" + every { mockFileNode1.parent?.children } returns listOf(mockFileNode1) + every { mockFileNode1.value.filenameInternal } returns "filename" val actual = validateUssFileNameAlreadyExists(jTextField, mockNode) val expected = null @@ -574,21 +633,29 @@ class UtilsTestSpec : ShouldSpec({ } } should("validate that the USS file name is already exist") { - jTextField.text = "filename" + jTextField.text = "filename2" - val mockNode = spyk( + val mockNode1 = spyk( + NodeData( + node = mockFileNode1, + file = mockVirtualFile, + attributes = null + ) + ) + val mockNode2 = spyk( NodeData( - node = mockFileNode, + node = mockFileNode2, file = mockVirtualFile, attributes = null ) ) - every { mockFileNode.parent?.children } returns listOf(mockFileNode) - every { mockFileNode.value.filenameInternal } returns "filename" + every { mockFileNode1.parent?.children } returns listOf(mockFileNode1, mockFileNode2) + every { mockFileNode1.value.filenameInternal } returns "filename1" + every { mockFileNode2.value.filenameInternal } returns "filename2" - val actual = validateUssFileNameAlreadyExists(jTextField, mockNode) + val actual = validateUssFileNameAlreadyExists(jTextField, mockNode1) val expected = ValidationInfo("Filename already exists. Please specify another filename.", jTextField).asWarning() @@ -596,26 +663,77 @@ class UtilsTestSpec : ShouldSpec({ actual shouldBe expected } } + should("validate that the USS file name is the same as the previous one") { + jTextField.text = "filename1" + + val mockNode1 = spyk( + NodeData( + node = mockFileNode1, + file = mockVirtualFile, + attributes = null + ) + ) + + every { mockFileNode1.parent?.children } returns listOf(mockFileNode1) + every { mockFileNode1.value.filenameInternal } returns "filename1" + + + val actual = validateUssFileNameAlreadyExists(jTextField, mockNode1) + val expected = null + + assertSoftly { + actual shouldBe expected + } + } should("validate that the USS directory name is already exist") { - jTextField.text = "dirname" + jTextField.text = "dirname2" - val mockNode = spyk( + val mockNode1 = spyk( NodeData( - node = mockDirNode, + node = mockDirNode1, + file = mockVirtualFile, + attributes = null + ) + ) + val mockNode2 = spyk( + NodeData( + node = mockDirNode2, file = mockVirtualFile, attributes = null ) ) - every { mockDirNode.parent?.children } returns listOf(mockDirNode) - every { mockDirNode.value.path } returns "dirname" + every { mockDirNode1.parent?.children } returns listOf(mockDirNode1, mockDirNode2) + every { mockDirNode1.value.path } returns "dirname1" + every { mockDirNode2.value.path } returns "dirname2" - val actual = validateUssFileNameAlreadyExists(jTextField, mockNode) + val actual = validateUssFileNameAlreadyExists(jTextField, mockNode1) val expected = ValidationInfo( "Directory name already exists. Please specify another directory name.", jTextField ).asWarning() + assertSoftly { + actual shouldBe expected + } + } + should("validate that the USS directory name is the same as the previous one") { + jTextField.text = "dirname1" + + val mockNode1 = spyk( + NodeData( + node = mockDirNode1, + file = mockVirtualFile, + attributes = null + ) + ) + + every { mockDirNode1.parent?.children } returns listOf(mockDirNode1) + every { mockDirNode1.value.path } returns "dirname1" + + val actual = validateUssFileNameAlreadyExists(jTextField, mockNode1) + val expected = null + assertSoftly { actual shouldBe expected }