From d29df4c2b954cf7e4ff9f627a85adb65088527e9 Mon Sep 17 00:00:00 2001 From: Lisiankou Date: Tue, 4 Oct 2022 14:36:56 +0300 Subject: [PATCH 01/23] IJMP-565: implemented SendTso api methods and tests for them --- .../client/sdk/zostso/CollectedResponses.kt | 21 +++ .../zowe/client/sdk/zostso/SendResponse.kt | 27 +++ .../r2z/zowe/client/sdk/zostso/SendTso.kt | 168 ++++++++++++++++++ .../client/sdk/zostso/input/SendTsoParams.kt | 19 ++ .../ibagroup/r2z/zowe/zostso/SendTsoTest.kt | 87 +++++++++ .../mock/receiveMessagesFromTso.json | 74 ++++++++ src/test/resources/mock/sendMessageToTso.json | 26 +++ src/test/resources/mock/startTso.json | 76 ++++++++ 8 files changed, 498 insertions(+) create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/CollectedResponses.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/SendTsoParams.kt create mode 100644 src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt create mode 100644 src/test/resources/mock/receiveMessagesFromTso.json create mode 100644 src/test/resources/mock/sendMessageToTso.json create mode 100644 src/test/resources/mock/startTso.json diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/CollectedResponses.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/CollectedResponses.kt new file mode 100644 index 0000000..59771e8 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/CollectedResponses.kt @@ -0,0 +1,21 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoResponse + +/** + * Tso collected Responses + */ +data class CollectedResponses( + + /** + * z/OSMF synchronous most tso command response messages. + */ + val tsos: List = emptyList(), + + /** + * Appended collected messages including READY prompt at the end. + */ + val messages: String? = null +) \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt new file mode 100644 index 0000000..99e6a8e --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt @@ -0,0 +1,27 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoResponse + +/** + * The TsoSend API response + */ +data class SendResponse( + + /** + * True if the command was issued and the responses were collected. + */ + val success: Boolean, + + /** + * The list of zOSMF send API responses. May issue multiple requests or + * to ensure that all messages are collected. Each individual response is placed here. + */ + val zosmfTsoResponses: List = emptyList(), + + /** + * The command response text. + */ + val commandResponse: String? = null +) diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt new file mode 100644 index 0000000..4134174 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt @@ -0,0 +1,168 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.SendTsoParams +import okhttp3.Credentials +import okhttp3.OkHttpClient +import retrofit2.Response +import kotlin.jvm.Throws + +/** + * Class to handle sending data to TSO + */ +class SendTso( + var connection: ZOSConnection, + var httpClient: OkHttpClient = UnsafeOkHttpClient.unsafeOkHttpClient +) { + + init { + connection.checkConnection() + } + + var response: Response<*>? = null + + /** + * Create Response + * + * @param responses responses from CollectedResponses object + * @return SendResponse, see [SendResponse] + */ + @Throws(Exception::class) + private fun createResponse(responses: CollectedResponses): SendResponse { + return SendResponse( + true, + responses.tsos, + responses.messages ?: throw Exception("no responses messages exist") + ) + } + + /** + * Collects responses from address space until it reaches prompt + * + * @param tsoResponse object from first Tso response from witch responses are needed, see [TsoResponse] + * @return CollectedResponses response object, see [CollectedResponses] + * @throws Exception error executing command + */ + @Throws(Exception::class) + fun getAllResponses(tsoResponse: TsoResponse): CollectedResponses { + var tso = tsoResponse + var done = false + val messages = StringBuilder() + val tsos = mutableListOf() + tsos.add(tso) + while(!done) { + if (tso.tsoData.isNotEmpty()) { + tso.tsoData.forEach { + if (it.tsoMessage != null) { + val tsoMsg = it.tsoMessage + tsoMsg?.data?.let { data -> + messages.append(data) + messages.append("\n") + } + } else if (it.tsoPrompt != null) { + if (messages.toString().contains("IKJ56602I COMMAND SYSTEM RESTARTING DUE TO ERROR")) { + val IKJ56602I = "IKJ56602I COMMAND SYSTEM RESTARTING DUE TO ERROR" + val msg = messages.toString() + val startIndex = msg.indexOf("IKJ56602I") + messages.delete(startIndex, startIndex + IKJ56602I.length + "\nREADY".length) + } else if (messages.isNotEmpty() && messages.toString().contains("READY")) { + done = true + } + // TSO PROMPT reached without getting any data, retrying + } + } + } + if (!done) { + tso.servletKey?.let { tso = getDataFromTso(it) } ?: throw Exception("servlet key missing") + tsos.add(tso) + } + } + return CollectedResponses(tsos, messages.toString()) + } + + /** + * Retrieve tso http request response + * + * @param servletKey key of tso address space + * @return z/OSMF tso response, see [TsoResponse] + * @throws Exception error executing command + */ + @Throws(Exception::class) + private fun getDataFromTso(servletKey: String): TsoResponse { + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val tsoApi = buildApi(url, httpClient) + val call = tsoApi.receiveMessagesFromTso( + authorizationToken = Credentials.basic(connection.user, connection.password), + servletKey = servletKey + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception("Follow up TSO Messages from TSO command cannot be retrieved. " + response?.errorBody()?.string()) + } + return response?.body() as TsoResponse? ?: throw Exception("No body returned") + } + + /** + * API method to send data to already started TSO address space, but will read TSO data until a PROMPT is reached. + * + * @param command to send to the TSO address space. + * @param servletKey returned from a successful start + * @return response object, see [SendResponse] + * @throws Exception error executing command + */ + @Throws(Exception::class) + fun sendDataToTSOCollect(servletKey: String, command: String): SendResponse { + if (servletKey.isEmpty()) { + throw Exception("servletKey not specified") + } + if (command.isEmpty()) { + throw Exception("command not specified") + } + + val putResponse = sendDataToTSOCommon( + SendTsoParams(servletKey = servletKey, data = command) + ) + + val responses = getAllResponses(putResponse) + return createResponse(responses) + } + + /** + * API method to send data to already started TSO address space + * + * @param commandParams object with required parameters, see [SendTsoParams] object + * @return response object, see [TsoResponse] + * @throws Exception error executing command + */ + @Throws(Exception::class) + fun sendDataToTSOCommon(commandParams: SendTsoParams): TsoResponse { + if (commandParams.data.isEmpty()) { + throw Exception("commandParams data not specified") + } + if (commandParams.servletKey.isEmpty()) { + throw Exception("commandParams servletKey not specified") + } + + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val tsoApi = buildApi(url, httpClient) + val call = tsoApi.sendMessageToTso( + authorizationToken = Credentials.basic(connection.user, connection.password), + body = TsoData( + tsoResponse = MessageType(version = "0100", data = commandParams.data) + ), + servletKey = commandParams.servletKey + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception( + "No results from executing tso command after getting TSO address space. " + + response?.errorBody()?.string() + ) + } + return response?.body() as TsoResponse? ?: throw Exception("No body returned") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/SendTsoParams.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/SendTsoParams.kt new file mode 100644 index 0000000..281d071 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/SendTsoParams.kt @@ -0,0 +1,19 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso.input + +/** + * TSO issue command z/OSMF parameters + */ +data class SendTsoParams( + + /** + * Servlet key of an active address space. + */ + val servletKey: String, + + /** + * Data to be sent to the active address space. + */ + val data: String +) \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt new file mode 100644 index 0000000..d376aa4 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt @@ -0,0 +1,87 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.zostso + +import com.google.gson.Gson +import com.squareup.okhttp.mockwebserver.MockResponse +import com.squareup.okhttp.mockwebserver.MockWebServer +import eu.ibagroup.r2z.TsoResponse +import eu.ibagroup.r2z.zowe.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.SendTso +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import java.net.InetSocketAddress +import java.net.Proxy +import kotlin.concurrent.thread + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SendTsoTest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.setDispatcher(responseDispatcher) + thread(start = true) { + mockServer.play() + } + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun getAllResponses() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val sendTso = SendTso(connection, proxyClient) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true + }, + { MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) } + ) + + val tsoResponse = Gson().fromJson(responseDispatcher.readMockJson("startTso"), TsoResponse::class.java) + val response = sendTso.getAllResponses(tsoResponse) + + Assertions.assertEquals(tsoResponse.servletKey, response.tsos[0].servletKey) + Assertions.assertTrue(response.messages?.contains("LOGON IN PROGRESS AT 17:31:11 ON SEPTEMBER 22, 2022") == true) + } + + @Test + fun sendDataToTSOCollect() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val sendTso = SendTso(connection, proxyClient) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "PUT" + }, + { MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso")) } + ) + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" + }, + { MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) } + ) + + val servletKey = "DLIS-121-aabcaaat" + val response = sendTso.sendDataToTSOCollect(servletKey, "TIME") + + Assertions.assertEquals(servletKey, response.zosmfTsoResponses[0].servletKey) + Assertions.assertTrue( + response.commandResponse?.contains( + "IKJ56650I TIME-06:43:59 PM. CPU-00:00:00 SERVICE-595 SESSION-00:05:00 SEPTEMBER 22,2022") == true + ) + } +} \ No newline at end of file diff --git a/src/test/resources/mock/receiveMessagesFromTso.json b/src/test/resources/mock/receiveMessagesFromTso.json new file mode 100644 index 0000000..bad8fd7 --- /dev/null +++ b/src/test/resources/mock/receiveMessagesFromTso.json @@ -0,0 +1,74 @@ +{ + "servletKey": "DLIS-121-aabcaaat", + "ver": "0100", + "tsoData": [ + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* USERID PASSWORD COMMENT *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* ---------------- ------------ -------------- *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* IBMUSER - SYS1/IBMUSER FULL AUTHORITY *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* ADCDMST - ADCDMST FULL AUTHORITY *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* ADCDA THRU ADCDZ - TEST LIMITED AUTHORITY(NO OMVS)*" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* OPEN1 THRU OPEN3 - SYS1 UID(0) (NO TSO) *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "*****************************************************************" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": " " + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "READY " + } + }, + { + "TSO PROMPT": { + "VERSION": "0100", + "HIDDEN": "FALSE" + } + } + ], + "reused": false, + "timeout": false +} \ No newline at end of file diff --git a/src/test/resources/mock/sendMessageToTso.json b/src/test/resources/mock/sendMessageToTso.json new file mode 100644 index 0000000..3ba3603 --- /dev/null +++ b/src/test/resources/mock/sendMessageToTso.json @@ -0,0 +1,26 @@ +{ + "servletKey": "DLIS-121-aabcaaat", + "ver": "0100", + "tsoData": [ + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "IKJ56650I TIME-06:43:59 PM. CPU-00:00:00 SERVICE-595 SESSION-00:05:00 SEPTEMBER 22,2022" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "READY " + } + }, + { + "TSO PROMPT": { + "VERSION": "0100", + "HIDDEN": "FALSE" + } + } + ], + "reused": false, + "timeout": false +} \ No newline at end of file diff --git a/src/test/resources/mock/startTso.json b/src/test/resources/mock/startTso.json new file mode 100644 index 0000000..fddd077 --- /dev/null +++ b/src/test/resources/mock/startTso.json @@ -0,0 +1,76 @@ +{ + "servletKey": "DLIS-121-aabcaaat", + "queueID": "8192030", + "sessionID": "0x79", + "ver": "0100", + "tsoData": [ + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "IKJ56455I DLIS LOGON IN PROGRESS AT 17:31:11 ON SEPTEMBER 22, 2022" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "IKJ56951I NO BROADCAST MESSAGES" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "*****************************************************************" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* APPLICATION DEVELOPER'S CONTROLLED DISTRIBUTION (ADCD) *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* ADCD.Z23D.CLIST(ISPFCL) PRODUCES THIS MESSAGE *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* ADCD.* DATASETS CONTAIN SYSTEM CUSTOMIZATION *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* SMP/E DATASETS CAN BE LOCATED FROM 3.4 WITH DSNAME **.CSI *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* HTTP://DTSC.DFW.IBM.COM/ADCD.HTML CONTAINS DOCUMENTATION *" + } + }, + { + "TSO MESSAGE": { + "VERSION": "0100", + "DATA": "* *" + } + } + ], + "reused": false, + "timeout": false +} \ No newline at end of file From 8fcd439c31b91279eead3d8527347424aee9d146 Mon Sep 17 00:00:00 2001 From: Lisiankou Date: Thu, 20 Oct 2022 11:50:45 +0300 Subject: [PATCH 02/23] IJMP-570: implemented StartTso api methods and tests for them --- src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt | 2 +- .../kotlin/eu/ibagroup/r2z/TsoResponse.kt | 16 ++-- .../zowe/client/sdk/zostso/SendResponse.kt | 2 +- .../client/sdk/zostso/StartStopResponses.kt | 63 +++++++++++++ .../r2z/zowe/client/sdk/zostso/StartTso.kt | 89 +++++++++++++++++++ .../zowe/client/sdk/zostso/TsoConstants.kt | 38 ++++++++ .../client/sdk/zostso/input/StartTsoParams.kt | 44 +++++++++ src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt | 2 +- .../ibagroup/r2z/zowe/zostso/SendTsoTest.kt | 16 +++- .../ibagroup/r2z/zowe/zostso/StartTsoTest.kt | 79 ++++++++++++++++ 10 files changed, 336 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponses.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StartTsoParams.kt create mode 100644 src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt diff --git a/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt b/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt index 8726c76..c647dc8 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt @@ -32,7 +32,7 @@ interface TsoApi { @Header("Content-type") contentType: ContentType = ContentType.APP_JSON, @Query("proc") proc: String, @Query("chset") chset: String, - @Query("cpage") cpage: TsoCodePage, + @Query("cpage") cpage: String, @Query("rows") rows: Int, @Query("cols") cols: Int, @Query("acct") acct: String? = null, diff --git a/src/main/kotlin/eu/ibagroup/r2z/TsoResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/TsoResponse.kt index 3428281..8731d7c 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/TsoResponse.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/TsoResponse.kt @@ -89,11 +89,11 @@ data class MessageData( var messageId: String? = null, ) -enum class TsoCodePage(val codePage: String) { - IBM_1025("1025"), - IBM_1047("1047"); - - override fun toString(): String { - return codePage - } -} \ No newline at end of file +//enum class TsoCodePage(val codePage: String) { +// IBM_1025("1025"), +// IBM_1047("1047"); +// +// override fun toString(): String { +// return codePage +// } +//} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt index 99e6a8e..a3b6b40 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendResponse.kt @@ -18,7 +18,7 @@ data class SendResponse( * The list of zOSMF send API responses. May issue multiple requests or * to ensure that all messages are collected. Each individual response is placed here. */ - val zosmfTsoResponses: List = emptyList(), + val tsoResponses: List = emptyList(), /** * The command response text. diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponses.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponses.kt new file mode 100644 index 0000000..f062dbd --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponses.kt @@ -0,0 +1,63 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoResponse + +/** + * The TsoStartStop API responses + */ +class StartStopResponses( + + /** + * Response from z/OSMF to start rest call + */ + val tsoResponse: TsoResponse?, + + collectedResponses: CollectedResponses? +) { + + /** + * If an error occurs, returns the error which contains cause error. + */ + val failureResponse: String? + + /** + * Appended collected messages including READY prompt at the end. + */ + val messages: String + + /** + * True if the command was issued and the responses were collected. + */ + val success: Boolean + + /** + * Collected responses from z/OSMF + */ + var collectedResponses: List + + /** + * Servlet key from TsoResponse + */ + var servletKey: String + + init { + tsoResponse ?: throw Exception("tsoResponse is null") + + if (tsoResponse.msgData.isNotEmpty()) { + this.success = false + val zosmfMsg = tsoResponse.msgData[0] + this.failureResponse = zosmfMsg.messageText ?: "zOSMF unknown error response" + } else { + this.success = true + this.failureResponse = null + } + + this.servletKey = tsoResponse.servletKey ?: throw Exception("servletKey is missing") + + val tsoMsgLst = tsoResponse.tsoData + this.messages = tsoMsgLst.joinToString() + this.collectedResponses = collectedResponses?.tsos ?: emptyList() + } +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt new file mode 100644 index 0000000..5a5ae32 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt @@ -0,0 +1,89 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.StartTsoParams +import okhttp3.Credentials +import okhttp3.OkHttpClient +import retrofit2.Response + +/** + * Start TSO address space and receive servlet key + */ +class StartTso( + var connection: ZOSConnection, + var httpClient: OkHttpClient = UnsafeOkHttpClient.unsafeOkHttpClient +) { + + init { + connection.checkConnection() + } + + var response: Response<*>? = null + + /** + * Start TSO address space with provided parameters. + * + * @param accountNumber this key of StartTsoParams required, because it cannot be default. + * @param params optional object with required parameters, see [StartTsoParams] + * @return command response on resolve, see [StartStopResponses] + * @throws Exception error executing command + */ + @Throws(Exception::class) + fun start(accountNumber: String, params: StartTsoParams): StartStopResponses { + if (accountNumber.isEmpty()) { + throw Exception("accountNumber not specified") + } + + val customParams = StartTsoParams( + account = accountNumber, + characterSet = params.characterSet ?: TsoConstants.DEFAULT_CHSET, + codePage = params.codePage ?: TsoConstants.DEFAULT_CPAGE, + rows = params.rows ?: TsoConstants.DEFAULT_ROWS, + columns = params.columns ?: TsoConstants.DEFAULT_COLS, + logonProcedure = params.logonProcedure ?: TsoConstants.DEFAULT_PROC, + regionSize = params.regionSize ?: TsoConstants.DEFAULT_RSIZE, + ) + + val tsoResponse = startCommon(customParams) + + var collectedResponses: CollectedResponses? = null + if (tsoResponse.servletKey != null) { + val sendTso = SendTso(connection, httpClient) + collectedResponses = sendTso.getAllResponses(tsoResponse) + } + + return StartStopResponses(tsoResponse, collectedResponses) + } + + /** + * Start TSO address space with provided parameters + * + * @param commandParams object with required parameters, see [StartTsoParams] + * @return z/OSMF response object, see [TsoResponse] + * @throws Exception error executing command + */ + @Throws(Exception::class) + fun startCommon(commandParams: StartTsoParams): TsoResponse { + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val tsoApi = buildApi(url, httpClient) + val call = tsoApi.startTso( + authorizationToken = Credentials.basic(connection.user, connection.password), + acct = commandParams.account ?: throw Exception("account num not specified"), + proc = commandParams.logonProcedure ?: TsoConstants.DEFAULT_PROC, + chset = commandParams.characterSet ?: TsoConstants.DEFAULT_CHSET, + cpage = commandParams.codePage ?: TsoConstants.DEFAULT_CPAGE, + rows = commandParams.rows?.toInt() ?: TsoConstants.DEFAULT_ROWS.toInt(), + cols = commandParams.columns?.toInt() ?: TsoConstants.DEFAULT_COLS.toInt(), + rsize = commandParams.regionSize?.toInt() ?: TsoConstants.DEFAULT_RSIZE.toInt() + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception("No results from executing tso command while setting up TSO address space. " + + response?.errorBody()?.string()) + } + return response?.body() as TsoResponse? ?: throw Exception("No body returned") + } +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt new file mode 100644 index 0000000..ef5d7f8 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt @@ -0,0 +1,38 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +/** + * Constants for various tso related info + */ +object TsoConstants { + /** + * Default character-set value + */ + const val DEFAULT_CHSET = "697" + + /** + * Default number of columns value + */ + const val DEFAULT_COLS = "80" + + /** + * Default code page value + */ + const val DEFAULT_CPAGE = "1047" + + /** + * Default logonProcedure value + */ + const val DEFAULT_PROC = "IZUFPROC" + + /** + * Default number of rows value + */ + const val DEFAULT_ROWS = "24" + + /** + * Default region-size value + */ + const val DEFAULT_RSIZE = "4096" +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StartTsoParams.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StartTsoParams.kt new file mode 100644 index 0000000..48eb178 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StartTsoParams.kt @@ -0,0 +1,44 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso.input + +/** + * TSO start command z/OSMF parameters + */ +data class StartTsoParams( + + /** + * User's z/OS permission account number + */ + val account: String? = null, + + /** + * Character set for address space + */ + val characterSet: String? = null, + + /** + * Code page for tso address space + */ + val codePage: String? = null, + + /** + * Number of columns + */ + val columns: String? = null, + + /** + * Name of the logonProcedure for address space + */ + val logonProcedure: String? = null, + + /** + * Region size for tso address space + */ + val regionSize: String? = null, + + /** + * Number of rows + */ + val rows: String? = null +) diff --git a/src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt b/src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt index 2681d17..4570dd6 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt @@ -18,7 +18,7 @@ class TsoApiTest: BaseTest() { authorizationToken = BASIC_AUTH_TOKEN, proc = "IKJACCNT", chset = "697", - cpage = TsoCodePage.IBM_1047, + cpage = "1047", rows = 204, cols = 160, rsize = 50000, diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt index d376aa4..41e37b3 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt @@ -47,7 +47,9 @@ class SendTsoTest { { it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true }, - { MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) } + { + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + } ) val tsoResponse = Gson().fromJson(responseDispatcher.readMockJson("startTso"), TsoResponse::class.java) @@ -66,22 +68,28 @@ class SendTsoTest { { it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "PUT" }, - { MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso")) } + { + MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso")) + } ) responseDispatcher.injectEndpoint( { it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" }, - { MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) } + { + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + } ) val servletKey = "DLIS-121-aabcaaat" val response = sendTso.sendDataToTSOCollect(servletKey, "TIME") - Assertions.assertEquals(servletKey, response.zosmfTsoResponses[0].servletKey) + Assertions.assertEquals(servletKey, response.tsoResponses[0].servletKey) Assertions.assertTrue( response.commandResponse?.contains( "IKJ56650I TIME-06:43:59 PM. CPU-00:00:00 SERVICE-595 SESSION-00:05:00 SEPTEMBER 22,2022") == true ) + + responseDispatcher.clearValidationList() } } \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt new file mode 100644 index 0000000..947b0c8 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt @@ -0,0 +1,79 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.zostso + +import com.squareup.okhttp.mockwebserver.MockResponse +import com.squareup.okhttp.mockwebserver.MockWebServer +import eu.ibagroup.r2z.zowe.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.StartTso +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.StartTsoParams +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import java.net.InetSocketAddress +import java.net.Proxy +import kotlin.concurrent.thread + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StartTsoTest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.setDispatcher(responseDispatcher) + thread(start = true) { + mockServer.play() + } + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun start() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val startTso = StartTso(connection, proxyClient) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso.*proc=DBSPROCB.*acct=IZUACCT.*")) == true && it.method == "POST" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("startTso")) + } + ) + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + } + ) + + val startTsoParams = StartTsoParams( + characterSet = "697", + codePage = "1047", + columns = "160", + rows = "204", + logonProcedure = "DBSPROCB", + regionSize = "50000" + ) + val response = startTso.start("IZUACCT", startTsoParams) + + Assertions.assertTrue(response.success) + Assertions.assertEquals(null, response.failureResponse) + Assertions.assertEquals("DLIS-121-aabcaaat", response.tsoResponse?.servletKey) + + responseDispatcher.clearValidationList() + } + +} \ No newline at end of file From b37551eeee3a307fd466ac5a1877d7cc1373e8dc Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Thu, 27 Oct 2022 18:58:38 +0200 Subject: [PATCH 03/23] IJMP-760 1.3.0 RC 1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 74b3cbf..5859569 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.2.2 +projectVersion=1.3.0-rc.1 From 91fb49ac8d5ad66f281667321798237c6168365f Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 28 Oct 2022 19:10:56 +0300 Subject: [PATCH 04/23] IJMP-559-572: added IssueCommand, IssueTso, StopTso api-methods and tests for them --- src/main/kotlin/eu/ibagroup/r2z/ConsoleAPI.kt | 25 ++++ .../eu/ibagroup/r2z/IssueRequestBody.kt | 41 +++++++ .../kotlin/eu/ibagroup/r2z/IssueResponse.kt | 48 ++++++++ .../client/sdk/zosconsole/ConsoleResponse.kt | 49 ++++++++ .../client/sdk/zosconsole/IssueCommand.kt | 109 ++++++++++++++++++ .../zowe/client/sdk/zostso/IssueResponse.kt | 43 +++++++ .../r2z/zowe/client/sdk/zostso/IssueTso.kt | 88 ++++++++++++++ .../client/sdk/zostso/StartStopResponse.kt | 32 +++++ .../r2z/zowe/client/sdk/zostso/StopTso.kt | 82 +++++++++++++ .../zowe/client/sdk/zostso/TsoConstants.kt | 5 + .../client/sdk/zostso/input/StopTsoParams.kt | 15 +++ .../r2z/zowe/zosconsole/IssueCommandTest.kt | 94 +++++++++++++++ .../ibagroup/r2z/zowe/zostso/IssueTsoTest.kt | 100 ++++++++++++++++ .../ibagroup/r2z/zowe/zostso/StopTsoTest.kt | 67 +++++++++++ src/test/resources/mock/issueCommand.json | 1 + src/test/resources/mock/stopTso.json | 1 + 16 files changed, 800 insertions(+) create mode 100644 src/main/kotlin/eu/ibagroup/r2z/ConsoleAPI.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/IssueRequestBody.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/IssueResponse.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueResponse.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueTso.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponse.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StopTsoParams.kt create mode 100644 src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt create mode 100644 src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt create mode 100644 src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt create mode 100644 src/test/resources/mock/issueCommand.json create mode 100644 src/test/resources/mock/stopTso.json diff --git a/src/main/kotlin/eu/ibagroup/r2z/ConsoleAPI.kt b/src/main/kotlin/eu/ibagroup/r2z/ConsoleAPI.kt new file mode 100644 index 0000000..c1bcfc3 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/ConsoleAPI.kt @@ -0,0 +1,25 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z + +import eu.ibagroup.r2z.annotations.AvailableSince +import eu.ibagroup.r2z.annotations.ZVersion +import retrofit2.Call + +import retrofit2.http.Body +import retrofit2.http.Header +import retrofit2.http.PUT +import retrofit2.http.Path + +interface ConsoleAPI { + + @AvailableSince(ZVersion.ZOS_2_1) + @PUT("/zosmf/restconsoles/consoles/{consolename}") + fun issueCommand( + @Header("Authorization") authorizationToken: String, + @Header("Content-type") contentType: ContentType = ContentType.APP_JSON, + @Path("consolename") consoleName: String, + @Body body: IssueRequestBody + ): Call + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/IssueRequestBody.kt b/src/main/kotlin/eu/ibagroup/r2z/IssueRequestBody.kt new file mode 100644 index 0000000..fda03b5 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/IssueRequestBody.kt @@ -0,0 +1,41 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +/** + * The z/OSMF console API parameters. See the z/OSMF REST API documentation for full details. + */ +class IssueRequestBody( + + /** + * The z/OS console command to issue. + */ + @SerializedName("cmd") + @Expose + val cmd: String, + + /** + * The solicited keyword to look for. + */ + @SerializedName("sol-key") + @Expose + val solKey: String? = null, + + /** + * The system in the sysplex to route the command. + */ + @SerializedName("system") + @Expose + val system: String? = null, + + /** + * The method of issuing the command. + */ + @SerializedName("async") + @Expose + val async: String? = null + +) \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/IssueResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/IssueResponse.kt new file mode 100644 index 0000000..9281d63 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/IssueResponse.kt @@ -0,0 +1,48 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +/** + * z/OSMF synchronous console command response messages. See the z/OSMF REST API publication for complete details. + */ +data class IssueResponse( + + /** + * Follow-up response URL. + */ + @SerializedName("cmd-response-url") + @Expose + val cmdResponseUrl: String? = null, + + /** + * Command response text. + */ + @SerializedName("cmd-response") + @Expose + val cmdResponse: String? = null, + + /** + * The follow-up response URI. + */ + @SerializedName("cmd-response-uri") + @Expose + val cmdResponseUri: String? = null, + + /** + * The command response key used for follow-up requests. + */ + @SerializedName("cmd-response-key") + @Expose + val cmdResponseKey: String? = null, + + /** + * True if the solicited keyword requested is present. + */ + @SerializedName("sol-key-detected") + @Expose + val solKeyDetected: String? = null + +) \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt new file mode 100644 index 0000000..5387135 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt @@ -0,0 +1,49 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zosconsole + +import eu.ibagroup.r2z.IssueResponse + +/** + * The Console API response. + */ +data class ConsoleResponse( + + /** + * True if the command was issued and the responses were collected. + */ + val success: Boolean? = false, + + /** + * The list of zOSMF console API responses. May issue multiple requests (because of user request) or + * to ensure that all messages are collected. Each individual response is placed here. + */ + val zosmfResponse: IssueResponse? = null, + + /** + * If an error occurs, returns the ImperativeError, which contains case error. + */ + val failureResponse: String? = null, + + /** + * The command response text. + */ + var commandResponse: String? = null, + + /** + * The final command response key - used to "follow-up" and check for additional response messages for the command. + */ + val lastResponseKey: String? = null, + + /** + * If the solicited keyword is specified, indicates that the keyword was detected. + */ + val keywordDetected: Boolean? = false, + + /** + * The "follow-up" command response URL - you can paste this in the browser to do a "GET" using the command + * response key provided in the URI route. + */ + val cmdResponseUrl: String? = null + +) \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt new file mode 100644 index 0000000..7ad228d --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt @@ -0,0 +1,109 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zosconsole + +import eu.ibagroup.r2z.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import okhttp3.Credentials +import okhttp3.OkHttpClient +import retrofit2.Response + +/** + * Issue MVS Console commands by using a system console + */ +class IssueCommand( + var connection: ZOSConnection, + var httpClient: OkHttpClient = UnsafeOkHttpClient.unsafeOkHttpClient +) { + + init { + connection.checkConnection() + } + + var response: Response<*>? = null + + /** + * Issue an MVS console command, returns "raw" z/OSMF response + * + * @param consoleName string name of the mvs console that is used to issue the command + * @param commandParams synchronous console issue parameters, see [IssueRequestBody] object + * @return command response on resolve, see [IssueResponse] object + * @throws Exception processing error + */ + fun issueCommon(consoleName: String, commandParams: IssueRequestBody): IssueResponse { + if (consoleName.isEmpty()) { + throw Exception("consoleName not specified") + } + if (commandParams.cmd.isEmpty()) { + throw Exception("command not specified") + } + + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val consoleApi = buildApi(url, httpClient) + val call = consoleApi.issueCommand( + authorizationToken = Credentials.basic(connection.user, connection.password), + consoleName = consoleName, + body = commandParams + ) + + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception(response?.errorBody()?.string()) + } + return response?.body() as IssueResponse? ?: throw Exception("No body returned") + } + + /** + * Issue an MVS console command in default console, returns "raw" z/OSMF response + * + * @param commandParams synchronous console issue parameters, see [IssueRequestBody] object + * @return command response on resolve, see [IssueResponse] object + * @throws Exception processing error + */ + fun issueDefConsoleCommon(commandParams: IssueRequestBody): IssueResponse { + return issueCommon("defcn", commandParams) + } + + /** + * Issue an MVS console command done synchronously - meaning solicited (direct command responses) are gathered + * immediately after the command is issued. However, after (according to the z/OSMF REST API documentation) + * approximately 3 seconds the response will be returned. + * + * @param params console issue parameters, see [IssueRequestBody] object + * @return command response on resolve, see ConsoleResponse object + * @throws Exception processing error + */ + fun issue(params: IssueRequestBody): ConsoleResponse { + val resp = issueCommon("defcn", params) + val consoleResponse = ConsoleResponse( + zosmfResponse = resp, + success = true, + keywordDetected = resp.solKeyDetected?.isNotEmpty() == true, + lastResponseKey = resp.cmdResponseKey, + cmdResponseUrl = resp.cmdResponseUrl + ) + + if (resp.cmdResponse != null) { + val responseValue = resp.cmdResponse.replace('\r', '\n') + consoleResponse.commandResponse = responseValue + + if (responseValue.isNotEmpty() && (responseValue.indexOf("\n") != responseValue.length - 1)) { + consoleResponse.commandResponse = responseValue + "\n" + } + } + + return consoleResponse + } + + /** + * Simple issue console command method. Does not accept parameters, so all defaults on the z/OSMF API are taken. + * + * @param command string command to issue + * @return command response on resolve, see [ConsoleResponse] object + * @throws Exception processing error + */ + fun issueSimple(command: String): ConsoleResponse { + return issue(IssueRequestBody(cmd = command)) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueResponse.kt new file mode 100644 index 0000000..d7bfe29 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueResponse.kt @@ -0,0 +1,43 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoResponse + +/** + * The TsoSend API response + */ +data class IssueResponse( + + /** + * True if the command was issued and the responses were collected. + */ + val success: Boolean = false, + + /** + * zOSMF start TSO API response. + */ + val startResponse: StartStopResponses? = null, + + /** + * Indicates if started TSO contains "READY " message + */ + val startReady: Boolean = false, + + /** + * zOSMF stop TSO API response. + */ + val stopResponse: StartStopResponse? = null, + + /** + * The list of zOSMF send API responses. May issue multiple requests or + * to ensure that all messages are collected. Each individual response is placed here. + */ + val zosmfResponses: List? = emptyList(), + + /** + * The command response text. + */ + val commandResponses: String? = null + +) \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueTso.kt new file mode 100644 index 0000000..3fd0fd4 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/IssueTso.kt @@ -0,0 +1,88 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoResponse +import eu.ibagroup.r2z.UnsafeOkHttpClient +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.StartTsoParams +import okhttp3.OkHttpClient +import retrofit2.Response + +/** + * Class to handle issue command to TSO + */ +class IssueTso( + var connection: ZOSConnection, + var httpClient: OkHttpClient = UnsafeOkHttpClient.unsafeOkHttpClient +) { + + init { + connection.checkConnection() + } + + var response: Response<*>? = null + + /** + * API method to start a TSO address space with provided parameters, issue a command, + * collect responses until prompt is reached, and terminate the address space. + * + * @param accountNumber accounting info for Jobs + * @param command command text to issue to the TSO address space. + * @param startParams start tso parameters, see startParams object + * @return issue tso response, see IssueResponse object + * @throws Exception error executing command + */ + fun issueTsoCommand(accountNumber: String, command: String, startParams: StartTsoParams): IssueResponse { + if (accountNumber.isEmpty()) { + throw Exception("accountNumber not specified") + } + if (command.isEmpty()) { + throw Exception("command not specified") + } + + // first stage open tso servlet session to use for our tso command processing + val startTso = StartTso(connection, httpClient) + val startResponse = startTso.start(accountNumber, startParams) + + if (!startResponse.success) { + throw Exception("TSO address space failed to start. Error: ${startResponse.failureResponse ?: "Unknown error"}") + } + + val zosmfTsoResponses = mutableListOf() + zosmfTsoResponses.add(startResponse.tsoResponse ?: throw Exception("no zosmf start tso response")) + val servletKey = startResponse.servletKey + + // second stage send command to tso servlet session created in first stage and collect all tso responses + val sendTso = SendTso(connection, httpClient) + val sendResponse = sendTso.sendDataToTSOCollect(servletKey, command) + + zosmfTsoResponses.addAll(sendResponse.tsoResponses) + + // lastly save the command response to our issueResponse reference + val stopTso = StopTso(connection, httpClient) + val stopResponse = stopTso.stop(servletKey) + + return IssueResponse( + success = sendResponse.success, + startResponse = startResponse, + zosmfResponses = zosmfTsoResponses, + commandResponses = sendResponse.commandResponse ?: throw Exception("error getting command response"), + stopResponse = stopResponse + ) + } + + /** + * API method to start a TSO address space, issue a command, collect responses until prompt is reached, and + * terminate the address space. + * + * @param accountNumber accounting info for Jobs + * @param command command text to issue to the TSO address space. + * @return issue tso response, see IssueResponse object + * @throws Exception error executing command + */ + fun issueTsoCommand(accountNumber: String, command: String): IssueResponse { + return issueTsoCommand(accountNumber, command, StartTsoParams()) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponse.kt new file mode 100644 index 0000000..d2c86f9 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartStopResponse.kt @@ -0,0 +1,32 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoResponse + +/** + * The TsoStartStop API response + */ +data class StartStopResponse( + + /** + * Response from z/OSMF to start rest call + */ + val tsoResponse: TsoResponse? = null, + + /** + * Servlet key from ZosmfTsoResponse + */ + val servletKey: String? = null, + + /** + * If an error occurs, returns error which contains cause error. + */ + var failureResponse: String? = null, + + /** + * True if the command was issued and the responses were collected. + */ + val success: Boolean? = false + +) \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt new file mode 100644 index 0000000..5057f94 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt @@ -0,0 +1,82 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso + +import eu.ibagroup.r2z.TsoApi +import eu.ibagroup.r2z.TsoResponse +import eu.ibagroup.r2z.UnsafeOkHttpClient +import eu.ibagroup.r2z.buildApi +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.StopTsoParams +import okhttp3.Credentials +import okhttp3.OkHttpClient +import retrofit2.Response + +/** + * Stop active TSO address space using servlet key + */ +class StopTso( + var connection: ZOSConnection, + var httpClient: OkHttpClient = UnsafeOkHttpClient.unsafeOkHttpClient +) { + + init { + connection.checkConnection() + } + + var response: Response<*>? = null + + /** + * Stop TSO address space and populates response with [StartStopResponse] + * + * @param servletKey unique servlet entry identifier + * @return start stop response, see [StartStopResponse] object + * @throws Exception error on TSO sto command + */ + fun stop(servletKey: String): StartStopResponse { + if (servletKey.isEmpty()) { + throw Exception("servletKey not specified") + } + + val tsoResponse = stopCommon(StopTsoParams(servletKey)) + val startStopResponse = StartStopResponse( + tsoResponse = tsoResponse, + servletKey = tsoResponse.servletKey ?: "", + success = tsoResponse.servletKey != null + ) + if (tsoResponse.msgData.isNotEmpty()) { + val zosmfMsg = tsoResponse.msgData[0] + val msgText = zosmfMsg.messageText ?: TsoConstants.ZOSMF_UNKNOWN_ERROR + startStopResponse.failureResponse = msgText + } + + return startStopResponse + } + + /** + * Sends REST call to z/OSMF for stopping active TSO address space + * + * @param commandParams command parameters, see [StopTsoParams] + * @return z/OSMF response object, see [TsoResponse] object + * @throws Exception error on TSO sto command + */ + fun stopCommon(commandParams: StopTsoParams): TsoResponse { + if (commandParams.servletKey.isNullOrEmpty()) { + throw Exception("commandParams servletKey not specified") + } + + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val tsoApi = buildApi(url, httpClient) + val call = tsoApi.endTso( + authorizationToken = Credentials.basic(connection.user, connection.password), + servletKey = commandParams.servletKey + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception("Failed to stop active TSO address space. ${response?.errorBody()?.string()}") + } + + return response?.body() as TsoResponse? ?: throw Exception("No body returned") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt index ef5d7f8..2ebc5fa 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/TsoConstants.kt @@ -35,4 +35,9 @@ object TsoConstants { * Default region-size value */ const val DEFAULT_RSIZE = "4096" + + /** + * z/OSMF unknown error + */ + const val ZOSMF_UNKNOWN_ERROR = "zOSMF unknown error response" } \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StopTsoParams.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StopTsoParams.kt new file mode 100644 index 0000000..ff82dc5 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/input/StopTsoParams.kt @@ -0,0 +1,15 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zostso.input + +/** + * TSO stop command z/OSMF parameters + */ +data class StopTsoParams( + + /** + * Servlet key of an active address space + */ + val servletKey: String? = null + +) \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt new file mode 100644 index 0000000..06f58cb --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt @@ -0,0 +1,94 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.zosconsole + +import com.squareup.okhttp.mockwebserver.MockResponse +import com.squareup.okhttp.mockwebserver.MockWebServer +import eu.ibagroup.r2z.IssueRequestBody +import eu.ibagroup.r2z.zowe.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zosconsole.IssueCommand +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import java.net.InetSocketAddress +import java.net.Proxy +import kotlin.concurrent.thread + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IssueCommandTest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.setDispatcher(responseDispatcher) + thread(start = true) { + mockServer.play() + } + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun issueCommonTest() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val issueCommand = IssueCommand(connection, proxyClient) + + val issueParams = IssueRequestBody( + cmd = "d a,PEGASUS", + solKey = "PEGASUS" + ) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/restconsoles/consoles/ibmusecn")) == true + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("issueCommand")) + } + ) + + val response = issueCommand.issueCommon("ibmusecn", issueParams) + + Assertions.assertEquals("C8529621", response.cmdResponseKey) + Assertions.assertTrue( + response.cmdResponse?.contains( + " D A,PEGASUS\r\n CNZ4106I 17.06.33 DISPLAY ACTIVITY 128\r") == true + ) + + responseDispatcher.clearValidationList() + } + + @Test + fun issueSimple() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val issueCommand = IssueCommand(connection, proxyClient) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/restconsoles/consoles/defcn")) == true + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("issueCommand")) + } + ) + + val response = issueCommand.issueSimple("d a,PEGASUS") + + Assertions.assertTrue(response.success == true) + Assertions.assertEquals("C8529621", response.lastResponseKey) + + responseDispatcher.clearValidationList() + } + + + +} \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt new file mode 100644 index 0000000..0b65ea5 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt @@ -0,0 +1,100 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.zostso + +import com.squareup.okhttp.mockwebserver.MockResponse +import com.squareup.okhttp.mockwebserver.MockWebServer +import eu.ibagroup.r2z.zowe.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.IssueTso +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.StartTsoParams +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import java.net.InetSocketAddress +import java.net.Proxy +import kotlin.concurrent.thread + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IssueTsoTest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.setDispatcher(responseDispatcher) + thread(start = true) { + mockServer.play() + } + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun issueTsoTest() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val issueTso = IssueTso(connection, proxyClient) + + val startTsoParams = StartTsoParams( + characterSet = "697", + codePage = "1047", + columns = "160", + rows = "204", + logonProcedure = "DBSPROCB", + regionSize = "50000" + ) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso.*proc=DBSPROCB.*acct=IZUACCT.*")) == true && it.method == "POST" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("startTso")) + } + ) + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "PUT" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso")) + } + ) + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + } + ) + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/DLIS-121-aabcaaat")) == true && it.method == "DELETE" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("stopTso")) + } + ) + + val response = issueTso.issueTsoCommand("IZUACCT", "TIME", startTsoParams) + + Assertions.assertTrue(response.success) + Assertions.assertEquals("DLIS-121-aabcaaat", response.startResponse?.servletKey) + Assertions.assertTrue( + response.commandResponses?.contains( + "IKJ56650I TIME-06:43:59 PM. CPU-00:00:00 SERVICE-595 SESSION-00:05:00 SEPTEMBER 22,2022") == true + ) + Assertions.assertEquals("DLIS-121-aabcaaat", response.stopResponse?.servletKey) + + responseDispatcher.clearValidationList() + } + +} \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt new file mode 100644 index 0000000..a9aeba1 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt @@ -0,0 +1,67 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.zostso + +import com.squareup.okhttp.mockwebserver.MockResponse +import com.squareup.okhttp.mockwebserver.MockWebServer +import eu.ibagroup.r2z.zowe.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zostso.StopTso +import eu.ibagroup.r2z.zowe.client.sdk.zostso.input.StopTsoParams +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import java.net.InetSocketAddress +import java.net.Proxy +import kotlin.concurrent.thread + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StopTsoTest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.setDispatcher(responseDispatcher) + thread(start = true) { + mockServer.play() + } + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun stopTsoTest() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val stopTso = StopTso(connection, proxyClient) + + val stopTsoParams = StopTsoParams( + servletKey = "DLIS-121-aabcaaat" + ) + + responseDispatcher.injectEndpoint( + { + it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/DLIS-121-aabcaaat")) == true && it.method == "DELETE" + }, + { + MockResponse().setBody(responseDispatcher.readMockJson("stopTso")) + } + ) + + val stopCommonResponse = stopTso.stopCommon(stopTsoParams) + val stopResponse = stopTso.stop("DLIS-121-aabcaaat") + + Assertions.assertEquals("DLIS-121-aabcaaat", stopResponse.servletKey) + Assertions.assertEquals("DLIS-121-aabcaaat", stopCommonResponse.servletKey) + + responseDispatcher.clearValidationList() + } + +} \ No newline at end of file diff --git a/src/test/resources/mock/issueCommand.json b/src/test/resources/mock/issueCommand.json new file mode 100644 index 0000000..3a9b23c --- /dev/null +++ b/src/test/resources/mock/issueCommand.json @@ -0,0 +1 @@ +{"cmd-response-key":"C8529621","cmd-response-url":"https:\/\/172.20.2.121:10443\/zosmf\/restconsoles\/consoles\/ibmusecn\/solmsgs\/C8529621","consoleRoutcde":"","consoleMscope":"","cmd-response-uri":"\/zosmf\/restconsoles\/consoles\/ibmusecn\/solmsgs\/C8529621","cmd-response":" D A,PEGASUS\r\n CNZ4106I 17.06.33 DISPLAY ACTIVITY 128\r JOBS M\/S TS USERS SYSAS INITS ACTIVE\/MAX VTAM OAS\r 00017 00034 00003 00034 00032 00001\/00040 00046\r PEGASUS NOT FOUND","consoleAuth":"","sol-key-detected":true} \ No newline at end of file diff --git a/src/test/resources/mock/stopTso.json b/src/test/resources/mock/stopTso.json new file mode 100644 index 0000000..af69e90 --- /dev/null +++ b/src/test/resources/mock/stopTso.json @@ -0,0 +1 @@ +{"servletKey":"DLIS-121-aabcaaat","ver":"0100","reused":false,"timeout":false} \ No newline at end of file From 52ca637216722f07cbf6548e7ff61c72f6b8c594 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Mon, 31 Oct 2022 15:12:23 +0100 Subject: [PATCH 05/23] 1.3.0-rc.2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5859569..672c8f4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.1 +projectVersion=1.3.0-rc.2 From 5b6610d0b4854801e0baeefd20c0d5e17d3acff7 Mon Sep 17 00:00:00 2001 From: Valiantsin Studzenichnik Date: Tue, 1 Nov 2022 13:13:33 +0300 Subject: [PATCH 06/23] Update ZosDsn.kt listDsn returns DS names only in uppercase. Error appeared while searching DS from the list using input string in lowercase --- .../kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsn.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsn.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsn.kt index 65672b6..2e9f063 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsn.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsn.kt @@ -50,7 +50,7 @@ class ZosDsn( attribute = XIBMAttr.Type.BASE ) val dsLst: DataSetsList = zosDsnList.listDsn(dataSetSearchStr, params) - val dataSet: Dataset? = dsLst.items.filter { el -> el.name.contains(dataSetName) }.getOrNull(0) + val dataSet: Dataset? = dsLst.items.filter { el -> el.name.contains(dataSetName.uppercase()) }.getOrNull(0) return dataSet ?: emptyDataSet } From 39e13425cbfad7abaae04e1815d5c9eabfd4e5a7 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Fri, 4 Nov 2022 13:25:07 +0100 Subject: [PATCH 07/23] Merge with release/v1.2.3 --- .run/Run integration tests.run.xml | 23 +++++ .run/Run unit tests.run.xml | 23 +++++ README.md | 22 ++++- build.gradle | 46 ++++++++-- gradle.properties | 2 +- src/intTest/intTest.iml | 12 +++ .../r2z => intTest/kotlin/common}/BaseTest.kt | 11 +-- .../common}/CancelJobAndPurgeOutputTest.kt | 7 +- src/intTest/kotlin/common/CancelJobTest.kt | 58 ++++++++++++ .../kotlin/common}/DataAPITest.kt | 3 +- .../common/HoldJobFor10sThenReleaseTest.kt | 80 +++++++++++++++++ .../kotlin/common}/ListFilteredJobsTest.kt | 6 +- .../kotlin/common}/ListSpoolFilesTest.kt | 12 +-- .../kotlin/common}/ObtainJobStatusTest.kt | 12 +-- .../kotlin/common}/SpoolFileRecordsTest.kt | 12 +-- .../kotlin/common/SubmitJobRequestTest.kt | 72 +++++++++++++++ .../kotlin/common}/TestValues.kt | 14 +-- .../kotlin/common}/TsoApiTest.kt | 5 +- .../kotlin/common/ZoweConfigParsingTest.kt | 38 ++++++++ .../eu/ibagroup/r2z/UnsafeOkHttpClient.kt | 2 +- .../kotlin/eu/ibagroup/r2z/CancelJobTest.kt | 50 ----------- .../r2z/HoldJobFor10sThenReleaseTest.kt | 76 ---------------- .../eu/ibagroup/r2z/SubmitJobRequestTest.kt | 68 -------------- .../r2z/zowe/MockResponseDispatcher.kt | 10 +-- .../r2z/zowe/ZoweConfigParsingTest.kt | 31 +------ .../r2z/zowe/zosfiles/ZosDsnCopyTest.kt | 16 ++-- .../r2z/zowe/zosfiles/ZosDsnDownloadTest.kt | 18 ++-- .../r2z/zowe/zosfiles/ZosDsnListTest.kt | 18 ++-- .../ibagroup/r2z/zowe/zosfiles/ZosDsnTest.kt | 45 +++++----- .../r2z/zowe/zosjobs/CancelJobsTest.kt | 20 ++--- .../r2z/zowe/zosjobs/DeleteJobsTest.kt | 21 ++--- .../ibagroup/r2z/zowe/zosjobs/GetJobsTest.kt | 90 +++++++++---------- .../r2z/zowe/zosjobs/MonitorJobsTest.kt | 35 ++++---- .../r2z/zowe/zosjobs/SubmitJobsTest.kt | 32 ++++--- .../r2z/zowe/zosuss/ZosUssDownloadTest.kt | 16 ++-- .../r2z/zowe/zosuss/ZosUssFileList.kt | 14 ++- .../r2z/zowe/zosuss/ZosUssFileTest.kt | 18 ++-- 37 files changed, 582 insertions(+), 456 deletions(-) create mode 100644 .run/Run integration tests.run.xml create mode 100644 .run/Run unit tests.run.xml create mode 100644 src/intTest/intTest.iml rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/BaseTest.kt (89%) rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/CancelJobAndPurgeOutputTest.kt (89%) create mode 100644 src/intTest/kotlin/common/CancelJobTest.kt rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/DataAPITest.kt (99%) create mode 100644 src/intTest/kotlin/common/HoldJobFor10sThenReleaseTest.kt rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/ListFilteredJobsTest.kt (97%) rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/ListSpoolFilesTest.kt (85%) rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/ObtainJobStatusTest.kt (85%) rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/SpoolFileRecordsTest.kt (88%) create mode 100644 src/intTest/kotlin/common/SubmitJobRequestTest.kt rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/TestValues.kt (62%) rename src/{test/kotlin/eu/ibagroup/r2z => intTest/kotlin/common}/TsoApiTest.kt (98%) create mode 100644 src/intTest/kotlin/common/ZoweConfigParsingTest.kt delete mode 100644 src/test/kotlin/eu/ibagroup/r2z/CancelJobTest.kt delete mode 100644 src/test/kotlin/eu/ibagroup/r2z/HoldJobFor10sThenReleaseTest.kt delete mode 100644 src/test/kotlin/eu/ibagroup/r2z/SubmitJobRequestTest.kt diff --git a/.run/Run integration tests.run.xml b/.run/Run integration tests.run.xml new file mode 100644 index 0000000..981d9a8 --- /dev/null +++ b/.run/Run integration tests.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.run/Run unit tests.run.xml b/.run/Run unit tests.run.xml new file mode 100644 index 0000000..446fc9d --- /dev/null +++ b/.run/Run unit tests.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/README.md b/README.md index 24a52fb..d09d6f9 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,24 @@ if (response.isSuccessful){ val members = response.body(); } ``` -Please note that in order to create API stub, you have to specify that the response should be converted by gson. And that's how you can easily use r2z. \ No newline at end of file +Please note that in order to create API stub, you have to specify that the response should be converted by gson. And that's how you can easily use r2z. + +## How to run tests + +### Unit tests +To run unit tests: +``` +./gradlew test -x signArchives +``` +### Integration tests +**NOTE:** integration tests use a specific environment. To test their correctness, you need either create the compliant one, or change the tests + +Before running integration tests, you need three variables to be set up: +- ``ZOSMF_TEST_URL`` - URL of the real mainframe with z/OSMF API to run the tests +- ``ZOSMF_TEST_USERNAME`` - username with appropriate permissions to run the tests +- ``ZOSMF_TEST_PASSWORD`` - user password to run the tests + +To run integration tests: +``` +./gradlew intTest +``` diff --git a/build.gradle b/build.gradle index a605495..655bafb 100644 --- a/build.gradle +++ b/build.gradle @@ -25,14 +25,14 @@ repositories { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib" - implementation "org.jetbrains.kotlin:kotlin-reflect:1.4.21" - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' - testImplementation 'com.squareup.okhttp:mockwebserver:1.2.1' + implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.21" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' implementation 'com.squareup.retrofit2:retrofit:2.9.0', 'com.squareup.retrofit2:converter-gson:2.9.0', 'com.squareup.retrofit2:converter-scalars:2.9.0', - 'com.google.code.gson:gson:2.9.0', + 'com.google.code.gson:gson:2.10', 'com.starxg:java-keytar:1.0.0', 'org.yaml:snakeyaml:1.29', 'org.junit.jupiter:junit-jupiter-api:5.8.2' @@ -59,6 +59,43 @@ task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir } +/** + * Adds intTest source sets + */ +sourceSets { + intTest { + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir 'src/intTest/kotlin' + } + resources { + srcDir 'src/intTest/resources' + } + } +} + +/** + * Configures the integration tests to inherit the testImplementation and testRuntimeOnly in dependencies + */ +configurations { + intTestImplementation.extendsFrom testImplementation + intTestRuntimeOnly.extendsFrom testRuntimeOnly +} + +task intTest(type:Test) { + description = "Run integration tests" + group = "verification" + testClassesDirs = sourceSets.intTest.output.classesDirs + classpath = sourceSets.intTest.runtimeClasspath + useJUnitPlatform() { + excludeTags "FirstTime" + } + testLogging { + events("passed", "skipped", "failed") + } +} + artifacts { archives jar archives sourceJar @@ -151,7 +188,6 @@ def customizePom(pom) { } } - model { tasks.generatePomFileForMavenJavaPublication { destination = file("$buildDir/generated-pom.xml") diff --git a/gradle.properties b/gradle.properties index 672c8f4..a044331 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.2 +projectVersion=1.3.0-rc.3 diff --git a/src/intTest/intTest.iml b/src/intTest/intTest.iml new file mode 100644 index 0000000..0b61efc --- /dev/null +++ b/src/intTest/intTest.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/kotlin/eu/ibagroup/r2z/BaseTest.kt b/src/intTest/kotlin/common/BaseTest.kt similarity index 89% rename from src/test/kotlin/eu/ibagroup/r2z/BaseTest.kt rename to src/intTest/kotlin/common/BaseTest.kt index e359f4f..ed1d0cb 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/BaseTest.kt +++ b/src/intTest/kotlin/common/BaseTest.kt @@ -1,7 +1,8 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.BytesConverterFactory import okhttp3.Credentials import okhttp3.OkHttpClient import retrofit2.Retrofit @@ -38,7 +39,7 @@ open class BaseTest { return try { - val trustAllCerts: Array = arrayOf( + val trustAllCerts: Array = arrayOf( object : X509TrustManager { @Throws(CertificateException::class) override fun checkClientTrusted( @@ -59,10 +60,10 @@ open class BaseTest { } } ) - val sslContext: SSLContext = SSLContext.getInstance("SSL") + val sslContext: SSLContext = SSLContext.getInstance("TLSv1.2") sslContext.init(null, trustAllCerts, SecureRandom()) - val sslSocketFactory: SSLSocketFactory = sslContext.getSocketFactory() + val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory val builder = OkHttpClient.Builder() builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) builder.hostnameVerifier(object : HostnameVerifier { @@ -75,4 +76,4 @@ open class BaseTest { throw RuntimeException(e) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/CancelJobAndPurgeOutputTest.kt b/src/intTest/kotlin/common/CancelJobAndPurgeOutputTest.kt similarity index 89% rename from src/test/kotlin/eu/ibagroup/r2z/CancelJobAndPurgeOutputTest.kt rename to src/intTest/kotlin/common/CancelJobAndPurgeOutputTest.kt index e1fc466..fce47a0 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/CancelJobAndPurgeOutputTest.kt +++ b/src/intTest/kotlin/common/CancelJobAndPurgeOutputTest.kt @@ -1,7 +1,10 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.CancelJobPurgeOutRequest +import eu.ibagroup.r2z.JESApi +import eu.ibagroup.r2z.ProcessMethod import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import retrofit2.Call @@ -43,7 +46,7 @@ class CancelJobAndPurgeOutputTest : BaseTest() { println(jobStatus.status) Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) Assertions.assertNotNull(jobStatus.owner) - Assertions.assertEquals(jobStatus.owner?.toLowerCase(), "hlh") + Assertions.assertEquals(jobStatus.owner?.lowercase(), "hlh") } else { println(response.errorBody()) diff --git a/src/intTest/kotlin/common/CancelJobTest.kt b/src/intTest/kotlin/common/CancelJobTest.kt new file mode 100644 index 0000000..bcec192 --- /dev/null +++ b/src/intTest/kotlin/common/CancelJobTest.kt @@ -0,0 +1,58 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package common + +import eu.ibagroup.r2z.CancelJobRequest +import eu.ibagroup.r2z.CancelJobRequestBody +import eu.ibagroup.r2z.JESApi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +class CancelJobTest : BaseTest() { + val JOB_ID = "JOB06200" + val JOB_NAME = "NOTHINGJ" + + val JOB_CORRELATOR = "J0001561S0W1....D940967F.......:" + + // 0 - request was successful + val SUCCESSFUL_REQUEST_RESULT = "0" + + @Test + fun cancelJobTest() { + val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(getUnsafeOkHttpClient()) + .build() + + val request = retrofit.create(JESApi::class.java) + val call: Call = request + .cancelJobRequest( + BASIC_AUTH_TOKEN, + JOB_NAME, + JOB_ID, + CancelJobRequestBody() + ) + enqueueCancelJobCallAndCheckResult(call) + } + + fun enqueueCancelJobCallAndCheckResult(call: Call) { + val response = call.execute() + + if (response.isSuccessful) + { + val jobStatus: CancelJobRequest = response.body() as CancelJobRequest + println(jobStatus.status) + Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) + Assertions.assertNotNull(jobStatus.owner) + Assertions.assertEquals(jobStatus.owner?.lowercase(), "hlh") + } else + { + println(response.errorBody()) + Assertions.assertTrue(false) + } + } +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/DataAPITest.kt b/src/intTest/kotlin/common/DataAPITest.kt similarity index 99% rename from src/test/kotlin/eu/ibagroup/r2z/DataAPITest.kt rename to src/intTest/kotlin/common/DataAPITest.kt index ca9fdaf..41ae35e 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/DataAPITest.kt +++ b/src/intTest/kotlin/common/DataAPITest.kt @@ -1,7 +1,8 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.* import org.junit.jupiter.api.Test diff --git a/src/intTest/kotlin/common/HoldJobFor10sThenReleaseTest.kt b/src/intTest/kotlin/common/HoldJobFor10sThenReleaseTest.kt new file mode 100644 index 0000000..032a88b --- /dev/null +++ b/src/intTest/kotlin/common/HoldJobFor10sThenReleaseTest.kt @@ -0,0 +1,80 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ +package common + +import eu.ibagroup.r2z.* +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.* + +class HoldJobFor10sThenReleaseTest : BaseTest() { + val JOB_ID = "JOB06152" + val JOB_NAME = "NOTHINGJ" + + val JOB_CORRELATOR = "J0001561S0W1....D940967F.......:" + + // 0 - request was successful + val SUCCESSFUL_REQUEST_RESULT = 0 + + @Test + fun holdFor10sThenReleaseJobTest() { + val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(getUnsafeOkHttpClient()) + .build() + + val request = retrofit.create(JESApi::class.java) + val firstCall: Call = request + .holdJobRequest( + BASIC_AUTH_TOKEN, + JOB_NAME, + JOB_ID, + HoldJobRequestBody() + ) + + enqueueHoldCallAndCheckResult(firstCall) + + Thread.sleep(10000) + + val secondCall: Call = request + .releaseJobRequest( + BASIC_AUTH_TOKEN, + JOB_NAME, + JOB_ID, + ReleaseJobRequestBody() + ) + + enqueueReleaseCallAndCheckResult(secondCall) + } + + fun enqueueHoldCallAndCheckResult(call: Call) { + val response = call.execute() + if (response.isSuccessful) { + val jobStatus: HoldJobRequest = response.body() as HoldJobRequest + println(jobStatus.status) + Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) + Assertions.assertNotNull(jobStatus.owner) + Assertions.assertEquals(jobStatus.owner?.lowercase(Locale.getDefault()), "hlh") + } else { + println(response.errorBody()) + Assertions.assertTrue(false) + } + } + + fun enqueueReleaseCallAndCheckResult(call: Call) { + val response = call.execute() + if (response.isSuccessful) { + val jobStatus: ReleaseJobRequest = response.body() as ReleaseJobRequest + println(jobStatus.status) + Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) + Assertions.assertNotNull(jobStatus.owner) + Assertions.assertEquals(jobStatus.owner?.lowercase(Locale.getDefault()), "hlh") + } else { + println(response.errorBody()) + Assertions.assertTrue(false) + } + } +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/ListFilteredJobsTest.kt b/src/intTest/kotlin/common/ListFilteredJobsTest.kt similarity index 97% rename from src/test/kotlin/eu/ibagroup/r2z/ListFilteredJobsTest.kt rename to src/intTest/kotlin/common/ListFilteredJobsTest.kt index 4b35823..c7b9156 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/ListFilteredJobsTest.kt +++ b/src/intTest/kotlin/common/ListFilteredJobsTest.kt @@ -1,7 +1,9 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.JESApi +import eu.ibagroup.r2z.Job import org.junit.jupiter.api.* import retrofit2.Call import java.util.concurrent.TimeUnit @@ -79,4 +81,4 @@ class ListFilteredJobsTest : BaseTest() { } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/ListSpoolFilesTest.kt b/src/intTest/kotlin/common/ListSpoolFilesTest.kt similarity index 85% rename from src/test/kotlin/eu/ibagroup/r2z/ListSpoolFilesTest.kt rename to src/intTest/kotlin/common/ListSpoolFilesTest.kt index 3c810a1..51d1e9e 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/ListSpoolFilesTest.kt +++ b/src/intTest/kotlin/common/ListSpoolFilesTest.kt @@ -1,7 +1,9 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.JESApi +import eu.ibagroup.r2z.SpoolFile import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -30,12 +32,12 @@ class ListSpoolFilesTest : BaseTest(){ } fun executeCallAndCheckResult(call: Call>){ - var response = call.execute() - if(response.isSuccessful == true){ - var spoolFiles = response.body() + val response = call.execute() + if (response.isSuccessful){ + val spoolFiles = response.body() spoolFiles?.forEach { el-> println(el) } } else{ Assertions.assertTrue(false) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/ObtainJobStatusTest.kt b/src/intTest/kotlin/common/ObtainJobStatusTest.kt similarity index 85% rename from src/test/kotlin/eu/ibagroup/r2z/ObtainJobStatusTest.kt rename to src/intTest/kotlin/common/ObtainJobStatusTest.kt index 7a804dc..caa0f24 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/ObtainJobStatusTest.kt +++ b/src/intTest/kotlin/common/ObtainJobStatusTest.kt @@ -1,7 +1,10 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.JESApi +import eu.ibagroup.r2z.Job +import eu.ibagroup.r2z.UseStepData import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -16,8 +19,6 @@ class ObtainJobStatusTest : BaseTest() { val JOB_ID = "JOB05569" val JOB_NAME = "NOTHINGJ" - - @Test fun obtainStatusByNameAndIdTest() { val call = jesApi.getJob(BASIC_AUTH_TOKEN, JOB_NAME, JOB_ID, UseStepData.DISABLE) @@ -35,13 +36,12 @@ class ObtainJobStatusTest : BaseTest() { val response = call.execute() if (response.isSuccessful) { val jobStatus = response.body() as Job - jobStatus.steps?.forEach { el -> + jobStatus.steps.forEach { el -> println(el) - } Assertions.assertNotNull(jobStatus.owner) - Assertions.assertEquals(jobStatus.owner.toLowerCase(), "hlh") + Assertions.assertEquals(jobStatus.owner.lowercase(), "hlh") } else { println(response.errorBody()) diff --git a/src/test/kotlin/eu/ibagroup/r2z/SpoolFileRecordsTest.kt b/src/intTest/kotlin/common/SpoolFileRecordsTest.kt similarity index 88% rename from src/test/kotlin/eu/ibagroup/r2z/SpoolFileRecordsTest.kt rename to src/intTest/kotlin/common/SpoolFileRecordsTest.kt index 600ffe7..7b7a895 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/SpoolFileRecordsTest.kt +++ b/src/intTest/kotlin/common/SpoolFileRecordsTest.kt @@ -1,13 +1,15 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.BinaryMode +import eu.ibagroup.r2z.JESApi +import eu.ibagroup.r2z.RecordRange import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import retrofit2.Call - @TestInstance(TestInstance.Lifecycle.PER_CLASS) class SpoolFileRecordsTest : BaseTest() { val jesApi = buildApi(BASE_URL, getUnsafeOkHttpClient()) @@ -44,8 +46,8 @@ class SpoolFileRecordsTest : BaseTest() { fun executeCallAndCheckResult(call: Call) { val response = call.execute() - if (response.isSuccessful == true) { - var arr = response.body() as ByteArray + if (response.isSuccessful) { + val arr = response.body() as ByteArray println(arr.toString(Charsets.UTF_8)) } else { @@ -54,4 +56,4 @@ class SpoolFileRecordsTest : BaseTest() { } -} \ No newline at end of file +} diff --git a/src/intTest/kotlin/common/SubmitJobRequestTest.kt b/src/intTest/kotlin/common/SubmitJobRequestTest.kt new file mode 100644 index 0000000..8d6e9ed --- /dev/null +++ b/src/intTest/kotlin/common/SubmitJobRequestTest.kt @@ -0,0 +1,72 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package common + +import eu.ibagroup.r2z.JESApi +import eu.ibagroup.r2z.SubmitFileNameBody +import eu.ibagroup.r2z.SubmitJobRequest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory + +class SubmitJobRequestTest : BaseTest() { + val JOB_PATH = "//'HHAL.PLUGIN.TEST.JOBS(JOB1)'" + + @Test + fun submitJobOnZOS_System() { + val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(getUnsafeOkHttpClient()) + .build() + + val request = retrofit.create(JESApi::class.java) + val call: Call = request.submitJobRequest(BASIC_AUTH_TOKEN, + body = SubmitFileNameBody(file = JOB_PATH) + ) + enqueueSubmitJob(call) + } + + @Test + fun submitJobFromInputText() { + val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .client(getUnsafeOkHttpClient()) + .build() + + val request = retrofit.create(JESApi::class.java) + val body = "" + + "//NOTHINGJ JOB CLASS=B,MSGLEVEL=(1,1),MSGCLASS=X,\n" + + "// NOTIFY=HAL,REGION=6M\n" + + "//*********\n" + + "//STEP0 EXEC PGM=IEFBR14\n" + + "//SYSPRINT DD SYSOUT=*\n" + + "//SYSIN DD *\n" + + "//STEP1 EXEC PGM=IKJEFT01,PARM='JOB2'\n" + + "//SYSPROC DD DSN=HHAL.PLUGIN.TEST.JOBS,DISP=SHR\n" + + "//SYSTSPRT DD SYSOUT=*\n" + + "//SYSTSIN DD DUMMY,DCB=BLKSIZE=80" + val call: Call = request.submitJobRequest(BASIC_AUTH_TOKEN, body = body) + enqueueSubmitJob(call) + } + + fun enqueueSubmitJob(call: Call) { + val response = call.execute() + if (response.isSuccessful) { + val jobStatus: SubmitJobRequest = response.body() as SubmitJobRequest + println(jobStatus.jobid) + println(jobStatus.jobname) + println(jobStatus.status) + Assertions.assertNotNull(jobStatus.owner) + Assertions.assertEquals(jobStatus.owner?.lowercase(), "hlh") + } else { + println(response.errorBody()) + Assertions.assertTrue(false) + } + } +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/TestValues.kt b/src/intTest/kotlin/common/TestValues.kt similarity index 62% rename from src/test/kotlin/eu/ibagroup/r2z/TestValues.kt rename to src/intTest/kotlin/common/TestValues.kt index e6f3dab..0d9ee53 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/TestValues.kt +++ b/src/intTest/kotlin/common/TestValues.kt @@ -1,24 +1,16 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common -import com.google.gson.Gson -import com.google.gson.GsonBuilder import okhttp3.Credentials -import okhttp3.OkHttpClient import okhttp3.ResponseBody -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.converter.scalars.ScalarsConverterFactory val zosmfUrl = System.getenv("ZOSMF_TEST_URL") ?: "" val zosmfUser = System.getenv("ZOSMF_TEST_USERNAME") ?: "" val zosmfPassword = System.getenv("ZOSMF_TEST_PASSWORD") ?: "" -val basicCreds = Credentials.basic(zosmfUser, zosmfPassword) ?: "" - - +val basicCreds = Credentials.basic(zosmfUser, zosmfPassword) fun errorBodyToList(errorBody: ResponseBody) : List { return errorBody.charStream().readLines() -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt b/src/intTest/kotlin/common/TsoApiTest.kt similarity index 98% rename from src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt rename to src/intTest/kotlin/common/TsoApiTest.kt index 4570dd6..d5e0533 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/TsoApiTest.kt +++ b/src/intTest/kotlin/common/TsoApiTest.kt @@ -1,7 +1,8 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ -package eu.ibagroup.r2z +package common +import eu.ibagroup.r2z.* import org.junit.jupiter.api.Test class TsoApiTest: BaseTest() { @@ -68,4 +69,4 @@ class TsoApiTest: BaseTest() { val body = response.body() as TsoResponse assert(body.servletKey == servletKey) } -} \ No newline at end of file +} diff --git a/src/intTest/kotlin/common/ZoweConfigParsingTest.kt b/src/intTest/kotlin/common/ZoweConfigParsingTest.kt new file mode 100644 index 0000000..b033e3b --- /dev/null +++ b/src/intTest/kotlin/common/ZoweConfigParsingTest.kt @@ -0,0 +1,38 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package common + +import okhttp3.mockwebserver.MockWebServer +import eu.ibagroup.r2z.DataAPI +import eu.ibagroup.r2z.zowe.config.* +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ZoweConfigParsingTest() { + + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var keytarWrapper: KeytarWrapper + lateinit var zoweConfig: ZoweConfig + + @Test + fun readConfigAndListDatasets() { + val authToken = zoweConfig.getAuthEncoding().withBasicPrefix() + + val dataApi = buildGsonApi("http://${zoweConfig.host}:${zoweConfig.port}", proxyClient) + val response = dataApi + .listDataSets( + authorizationToken = authToken, + dsLevel = "TEST.*" + ) + .execute() + if (response.isSuccessful) { + val datasetLists = response.body() + Assertions.assertEquals(datasetLists?.items?.size, 4) + } else { + Assertions.fail("response must be successful.") + } + } + +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/UnsafeOkHttpClient.kt b/src/main/kotlin/eu/ibagroup/r2z/UnsafeOkHttpClient.kt index 69c1259..a0cb54c 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/UnsafeOkHttpClient.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/UnsafeOkHttpClient.kt @@ -43,7 +43,7 @@ object UnsafeOkHttpClient { ) // Install the all-trusting trust manager - val sslContext = SSLContext.getInstance("SSL") + val sslContext = SSLContext.getInstance("TLSv1.2") sslContext.init(null, trustAllCerts, SecureRandom()) // Create an ssl socket factory with our all-trusting manager diff --git a/src/test/kotlin/eu/ibagroup/r2z/CancelJobTest.kt b/src/test/kotlin/eu/ibagroup/r2z/CancelJobTest.kt deleted file mode 100644 index 526a22b..0000000 --- a/src/test/kotlin/eu/ibagroup/r2z/CancelJobTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ - -package eu.ibagroup.r2z - -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import retrofit2.Call -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory - -class CancelJobTest : BaseTest() { - val JOB_ID = "JOB06200" - val JOB_NAME = "NOTHINGJ" - - val JOB_CORRELATOR = "J0001561S0W1....D940967F.......:" - - // 0 - request was successful - val SUCCESSFUL_REQUEST_RESULT = "0" - - @Test - fun cancelJobTest() { - val retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .client(getUnsafeOkHttpClient()) - .build() - - val request = retrofit.create(JESApi::class.java) - val call: Call = request.cancelJobRequest(BASIC_AUTH_TOKEN, JOB_NAME, - JOB_ID, CancelJobRequestBody()) - enqueueCancelJobCallAndCheckResult(call) - } - - fun enqueueCancelJobCallAndCheckResult(call: Call) { - val response = call.execute() - - if (response.isSuccessful) - { - val jobStatus: CancelJobRequest = response.body() as CancelJobRequest - println(jobStatus.status) - Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) - Assertions.assertNotNull(jobStatus.owner) - Assertions.assertEquals(jobStatus.owner?.toLowerCase(), "hlh") - } else - { - println(response.errorBody()) - Assertions.assertTrue(false) - } - } -} diff --git a/src/test/kotlin/eu/ibagroup/r2z/HoldJobFor10sThenReleaseTest.kt b/src/test/kotlin/eu/ibagroup/r2z/HoldJobFor10sThenReleaseTest.kt deleted file mode 100644 index 5a55f18..0000000 --- a/src/test/kotlin/eu/ibagroup/r2z/HoldJobFor10sThenReleaseTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ - -import eu.ibagroup.r2z.BaseTest -import eu.ibagroup.r2z.HoldJobRequestBody -import eu.ibagroup.r2z.JESApi -import eu.ibagroup.r2z.ReleaseJobRequestBody -import eu.ibagroup.r2z.HoldJobRequest -import eu.ibagroup.r2z.ReleaseJobRequest -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import retrofit2.Call -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import java.util.* - -class HoldFor20sThenReleaseJobTest : BaseTest() { - val JOB_ID = "JOB06152" - val JOB_NAME = "NOTHINGJ" - - val JOB_CORRELATOR = "J0001561S0W1....D940967F.......:" - - // 0 - request was successful - val SUCCESSFUL_REQUEST_RESULT = 0 - - @Test - fun holdFor10sThenReleaseJobTest() { - val retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .client(getUnsafeOkHttpClient()) - .build() - - val request = retrofit.create(JESApi::class.java) - val firstCall: Call = request.holdJobRequest(BASIC_AUTH_TOKEN, - JOB_NAME, JOB_ID, HoldJobRequestBody() - ) - - enqueueHoldCallAndCheckResult(firstCall) - - Thread.sleep(10000) - - val secondCall: Call = request.releaseJobRequest(BASIC_AUTH_TOKEN, - JOB_NAME, JOB_ID, ReleaseJobRequestBody() - ) - - enqueueReleaseCallAndCheckResult(secondCall) - } - - fun enqueueHoldCallAndCheckResult(call: Call) { - val response = call.execute() - if (response.isSuccessful) { - val jobStatus: HoldJobRequest = response.body() as HoldJobRequest - println(jobStatus.status) - Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) - Assertions.assertNotNull(jobStatus.owner) - Assertions.assertEquals(jobStatus.owner?.lowercase(Locale.getDefault()), "hlh") - } else { - println(response.errorBody()) - Assertions.assertTrue(false) - } - } - - fun enqueueReleaseCallAndCheckResult(call: Call) { - val response = call.execute() - if (response.isSuccessful) { - val jobStatus: ReleaseJobRequest = response.body() as ReleaseJobRequest - println(jobStatus.status) - Assertions.assertEquals(SUCCESSFUL_REQUEST_RESULT, jobStatus.status) - Assertions.assertNotNull(jobStatus.owner) - Assertions.assertEquals(jobStatus.owner?.lowercase(Locale.getDefault()), "hlh") - } else { - println(response.errorBody()) - Assertions.assertTrue(false) - } - } -} diff --git a/src/test/kotlin/eu/ibagroup/r2z/SubmitJobRequestTest.kt b/src/test/kotlin/eu/ibagroup/r2z/SubmitJobRequestTest.kt deleted file mode 100644 index 569fe20..0000000 --- a/src/test/kotlin/eu/ibagroup/r2z/SubmitJobRequestTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ - -package eu.ibagroup.r2z - -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import retrofit2.Call -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.converter.scalars.ScalarsConverterFactory - -class SubmitJobRequestTest : BaseTest() { - val JOB_PATH = "//'HHAL.PLUGIN.TEST.JOBS(JOB1)'" - - @Test - fun submitJobOnZOS_System() { - val retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .client(getUnsafeOkHttpClient()) - .build() - - val request = retrofit.create(JESApi::class.java) - val call: Call = request.submitJobRequest(BASIC_AUTH_TOKEN, - body = SubmitFileNameBody(file = JOB_PATH)) - enqueueSubmitJob(call) - } - - @Test - fun submitJobFromInputText() { - val retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(ScalarsConverterFactory.create()) - .addConverterFactory(GsonConverterFactory.create()) - .client(getUnsafeOkHttpClient()) - .build() - - val request = retrofit.create(JESApi::class.java) - val body = "" + - "//NOTHINGJ JOB CLASS=B,MSGLEVEL=(1,1),MSGCLASS=X,\n" + - "// NOTIFY=HAL,REGION=6M\n" + - "//*********\n" + - "//STEP0 EXEC PGM=IEFBR14\n" + - "//SYSPRINT DD SYSOUT=*\n" + - "//SYSIN DD *\n" + - "//STEP1 EXEC PGM=IKJEFT01,PARM='JOB2'\n" + - "//SYSPROC DD DSN=HHAL.PLUGIN.TEST.JOBS,DISP=SHR\n" + - "//SYSTSPRT DD SYSOUT=*\n" + - "//SYSTSIN DD DUMMY,DCB=BLKSIZE=80" - val call: Call = request.submitJobRequest(BASIC_AUTH_TOKEN, body = body) - enqueueSubmitJob(call) - } - - fun enqueueSubmitJob(call: Call) { - val response = call.execute() - if (response.isSuccessful) { - val jobStatus: SubmitJobRequest = response.body() as SubmitJobRequest - println(jobStatus.jobid) - println(jobStatus.jobname) - println(jobStatus.status) - Assertions.assertNotNull(jobStatus.owner) - Assertions.assertEquals(jobStatus.owner?.toLowerCase(), "hlh") - } else { - println(response.errorBody()) - Assertions.assertTrue(false) - } - } -} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt index bd2f3cb..ff79d5c 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt @@ -1,9 +1,9 @@ package eu.ibagroup.r2z.zowe -import com.squareup.okhttp.mockwebserver.Dispatcher -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.RecordedRequest import eu.ibagroup.r2z.zowe.config.decodeFromBase64 +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest import org.junit.jupiter.api.Assertions class MockResponseDispatcher : Dispatcher() { @@ -31,8 +31,8 @@ class MockResponseDispatcher : Dispatcher() { validationList.clear() } - override fun dispatch(request: RecordedRequest?): MockResponse { - val authTokenRequest = request?.getHeader("Authorization") ?: Assertions.fail("auth token must be presented.") + override fun dispatch(request: RecordedRequest): MockResponse { + val authTokenRequest = request.getHeader("Authorization") ?: Assertions.fail("auth token must be presented.") val credentials = decodeBasicAuthToken(authTokenRequest).split(":") val usernameRequest = credentials[0] val passwordRequest = credentials[1] diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/ZoweConfigParsingTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/ZoweConfigParsingTest.kt index e43a43e..5e2eea3 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/ZoweConfigParsingTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/ZoweConfigParsingTest.kt @@ -10,15 +10,12 @@ package eu.ibagroup.r2z.zowe -import com.squareup.okhttp.mockwebserver.MockWebServer -import eu.ibagroup.r2z.DataAPI -import eu.ibagroup.r2z.buildGsonApi +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.config.* import okhttp3.OkHttpClient import org.junit.jupiter.api.* import java.net.InetSocketAddress import java.net.Proxy -import kotlin.concurrent.thread @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ZoweConfigParsingTest: ZoweConfigTestBase() { @@ -32,10 +29,8 @@ class ZoweConfigParsingTest: ZoweConfigTestBase() { fun createMockServer () { keytarWrapper = DefaultMockKeytarWrapper() mockServer = MockWebServer() - mockServer.setDispatcher(MockResponseDispatcher()) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = MockResponseDispatcher() + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -92,31 +87,13 @@ class ZoweConfigParsingTest: ZoweConfigTestBase() { Assertions.assertDoesNotThrow { zoweConfig.getAuthEncoding() } } - - @Test - fun readConfigAndListDatasets() { - val authToken = zoweConfig.getAuthEncoding().withBasicPrefix() - - val dataApi = buildGsonApi("http://${zoweConfig.host}:${zoweConfig.port}", proxyClient) - val response = dataApi.listDataSets( - authorizationToken = authToken, - dsLevel = "TEST.*" - ).execute() - if (response.isSuccessful) { - val datasetLists = response.body() - Assertions.assertEquals(datasetLists?.items?.size, 4) - } else { - Assertions.fail("response must be successful.") - } - } - fun checkZoweConfig(zoweConfig: ZoweConfig) { Assertions.assertEquals(zoweConfig.user, TEST_USER) Assertions.assertEquals(zoweConfig.password, TEST_PASSWORD) Assertions.assertEquals(zoweConfig.host, "example.host1") Assertions.assertEquals(zoweConfig.rejectUnauthorized, true) Assertions.assertEquals(zoweConfig.port, 10443) - Assertions.assertEquals(zoweConfig.protocol, "http") + Assertions.assertEquals(zoweConfig.protocol, "https") Assertions.assertEquals(zoweConfig.basePath, "/") Assertions.assertEquals(zoweConfig.encoding, 1047) Assertions.assertEquals(zoweConfig.responseTimeout, 600) diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnCopyTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnCopyTest.kt index afa462e..d8d2f01 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnCopyTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnCopyTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosfiles -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zosfiles.ZosDsnCopy @@ -24,10 +24,8 @@ class ZosDsnCopyTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -48,7 +46,7 @@ class ZosDsnCopyTest { ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/NBEL.TEST.DATA")) == true && it.method?.equals("PUT") == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/ds/NBEL.TEST.DATA HTTP/.*")) == true }, { MockResponse().setResponseCode(200) @@ -66,7 +64,7 @@ class ZosDsnCopyTest { val zosDsnCopy = ZosDsnCopy(conn, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/NBEL.TEST.DATA")) == true && it.method?.equals("PUT") == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/ds/NBEL.TEST.DATA HTTP/.*")) == true }, { MockResponse().setResponseCode(200) } @@ -77,4 +75,4 @@ class ZosDsnCopyTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnDownloadTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnDownloadTest.kt index 4d5f28e..cc45157 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnDownloadTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnDownloadTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosfiles -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zosfiles.ZosDsnDownload @@ -24,10 +24,8 @@ class ZosDsnDownloadTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -43,10 +41,10 @@ class ZosDsnDownloadTest { val zosDsnDownload = ZosDsnDownload(conn, proxyClient) val downloadParams = DownloadParams( ) - val responseBody = javaClass.classLoader.getResource("mock/downloadDsnMember.txt")?.readText() + val responseBody = javaClass.classLoader.getResource("mock/downloadDsnMember.txt")?.readText() ?: "" responseDispatcher.injectEndpoint( - { it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/TEST.JCL\\(TESTJOB\\)")) == true && - it.method?.equals("GET") == true + { + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/ds/TEST.JCL\\(TESTJOB\\) HTTP/.*")) == true }, { MockResponse().setBody(responseBody) } ) @@ -57,4 +55,4 @@ class ZosDsnDownloadTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnListTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnListTest.kt index 7ffbe0b..2f01a92 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnListTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnListTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosfiles -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zosfiles.ZosDsnList @@ -25,10 +25,8 @@ class ZosDsnListTest { fun createMockServer () { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -46,9 +44,9 @@ class ZosDsnListTest { ) val zosDsnList = ZosDsnList(conn, proxyClient) responseDispatcher.injectEndpoint({ - it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/ds.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("listDatasets")) + MockResponse().setBody(responseDispatcher.readMockJson("listDatasets") ?: "") }) val dsnList = zosDsnList.listDsn("TEST.**.TEST1", listParams) responseDispatcher.clearValidationList() @@ -61,9 +59,9 @@ class ZosDsnListTest { val listParams = ListParams() val zosDsnList = ZosDsnList(conn, proxyClient) responseDispatcher.injectEndpoint({ - it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/ds.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("listDatasetMembers")) + MockResponse().setBody(responseDispatcher.readMockJson("listDatasetMembers") ?: "") }) val dsnMembersList = zosDsnList.listDsnMembers("SYS1.PROCLIB", listParams) Assertions.assertEquals(87, dsnMembersList.items.size) diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnTest.kt index 66e6782..f9e5acb 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosfiles/ZosDsnTest.kt @@ -3,8 +3,8 @@ package eu.ibagroup.r2z.zowe.zosfiles import com.google.gson.Gson -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.CreateDataset import eu.ibagroup.r2z.DatasetOrganization import eu.ibagroup.r2z.RecordFormat @@ -28,10 +28,8 @@ class ZosDsnTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -47,8 +45,8 @@ class ZosDsnTest { val zosDsn = ZosDsn(connection, proxyClient) responseDispatcher.injectEndpoint( - { it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds.*")) == true }, - { MockResponse().setBody(responseDispatcher.readMockJson("listDatasets")) } + { it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/ds.* HTTP/.*")) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDatasets") ?: "") } ) val datasetInfo = zosDsn.getDatasetInfo("TEST.IJMP.DATASET1") Assertions.assertEquals(4500, datasetInfo.blockSize) @@ -64,8 +62,8 @@ class ZosDsnTest { val zosDsn = ZosDsn(connection, proxyClient) responseDispatcher.injectEndpoint( - { it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds.*")) == true }, - { MockResponse().setBody(responseDispatcher.readMockJson("listDatasets")) } + { it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/ds.* HTTP/.*")) == true }, + { MockResponse().setBody(responseDispatcher.readMockJson("listDatasets") ?: "") } ) val datasetInfo = zosDsn.getDatasetInfo("TEST.IJMP.DATASET5") Assertions.assertEquals(null, datasetInfo.blockSize) @@ -79,8 +77,8 @@ class ZosDsnTest { val zosDsn = ZosDsn(connection, proxyClient) responseDispatcher.injectEndpoint( - { it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/TEST.IJMP.DATASET")) == true && - it.method?.equals("DELETE") == true + { + it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/restfiles/ds/TEST.IJMP.DATASET HTTP/.*")) == true }, { MockResponse().setResponseCode(204) } ) @@ -96,7 +94,7 @@ class ZosDsnTest { val zosDsn = ZosDsn(connection, proxyClient) responseDispatcher.injectEndpoint( - { it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/TEST.IJMP.DATASET\\(TESTMEM\\)")) == true }, + { it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/restfiles/ds/TEST.IJMP.DATASET\\(TESTMEM\\) HTTP/.*")) == true }, { MockResponse().setResponseCode(204) } ) val response = zosDsn.deleteDsn("TEST.IJMP.DATASET", "TESTMEM") @@ -111,13 +109,16 @@ class ZosDsnTest { val zosDsn = ZosDsn(conn, proxyClient) val memberText = "member" val dsnText = "dataset" - responseDispatcher.injectEndpoint({ - it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/TEST\\.IJMP\\.DATASET1(\\(TEST\\))?")) == true - }, { - val textToCheck = if (it?.path?.contains(Regex("TEST.IJMP.DATASET1\\(TEST\\)")) == true) memberText else dsnText - Assertions.assertEquals(String(it?.body ?: byteArrayOf()), textToCheck) - MockResponse().setResponseCode(204) - }) + responseDispatcher.injectEndpoint( + { + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/ds/TEST\\.IJMP\\.DATASET1(\\(TEST\\))? HTTP/.*")) == true + }, + { + val textToCheck = if (it?.requestLine?.contains(Regex("TEST.IJMP.DATASET1\\(TEST\\)")) == true) memberText else dsnText + Assertions.assertEquals(it?.body?.readUtf8(), textToCheck) + MockResponse().setResponseCode(204) + } + ) val datasetResponse = zosDsn.writeDsn("TEST.IJMP.DATASET1", dsnText.toByteArray()) Assertions.assertEquals(204, datasetResponse.code()) val memberResponse = zosDsn.writeDsn("TEST.IJMP.DATASET1", "TEST", memberText.toByteArray()) @@ -139,8 +140,8 @@ class ZosDsnTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/ds/TEST.IJMP.DATASET")) == true && - Gson().fromJson(String(it.body), CreateDataset::class.java) == params + it?.requestLine?.matches(Regex("POST http://.*/zosmf/restfiles/ds/TEST.IJMP.DATASET HTTP/.*")) == true && + Gson().fromJson(it.body.readUtf8(), CreateDataset::class.java) == params }, { MockResponse().setResponseCode(201) } ) diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/CancelJobsTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/CancelJobsTest.kt index ade20c3..eb3a81d 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/CancelJobsTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/CancelJobsTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosjobs -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.* import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -24,10 +24,8 @@ class CancelJobsTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -43,10 +41,10 @@ class CancelJobsTest { val cancelJobs = CancelJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/TESTJOB2/JOB00084")) == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restjobs/jobs/TESTJOB2/JOB00084 HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("cancelJobs")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("cancelJobs") ?: "").setResponseCode(200) } ) val response = cancelJobs.cancelJob("TESTJOB2", "JOB00084", RequestVersion.SYNCHRONOUS) @@ -61,10 +59,10 @@ class CancelJobsTest { val cancelJobs = CancelJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/TESTJOB2/JOB00084")) == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restjobs/jobs/TESTJOB2/JOB00084 HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("cancelJobs")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("cancelJobs") ?: "").setResponseCode(200) } ) val response = cancelJobs.cancelJobForJob( @@ -76,4 +74,4 @@ class CancelJobsTest { ) Assertions.assertEquals("0", response.status) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/DeleteJobsTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/DeleteJobsTest.kt index cd20240..8cdc38c 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/DeleteJobsTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/DeleteJobsTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosjobs -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.Job import eu.ibagroup.r2z.RequestVersion import eu.ibagroup.r2z.zowe.* @@ -13,7 +13,6 @@ import okhttp3.OkHttpClient import org.junit.jupiter.api.* import java.net.InetSocketAddress import java.net.Proxy -import kotlin.concurrent.thread @TestInstance(TestInstance.Lifecycle.PER_CLASS) class DeleteJobsTest { @@ -26,10 +25,8 @@ class DeleteJobsTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -45,11 +42,11 @@ class DeleteJobsTest { val deleteJobs = DeleteJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/TESTJOBW/JOB00085")) == true && + it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/restjobs/jobs/TESTJOBW/JOB00085 HTTP/.*")) == true && it.getHeader("X-IBM-Job-Modify-Version") == RequestVersion.SYNCHRONOUS.value }, { - MockResponse().setBody(responseDispatcher.readMockJson("deleteJobs")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("deleteJobs") ?: "").setResponseCode(200) } ) val response = deleteJobs.deleteJob("TESTJOBW", "JOB00085", RequestVersion.SYNCHRONOUS) @@ -64,11 +61,11 @@ class DeleteJobsTest { val deleteJobs = DeleteJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/TESTJOBW/JOB00085")) == true && + it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/restjobs/jobs/TESTJOBW/JOB00085 HTTP/.*")) == true && it.getHeader("X-IBM-Job-Modify-Version") == RequestVersion.SYNCHRONOUS.value }, { - MockResponse().setBody(responseDispatcher.readMockJson("deleteJobs")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("deleteJobs") ?: "").setResponseCode(200) } ) val response = deleteJobs.deleteJobForJob( @@ -80,4 +77,4 @@ class DeleteJobsTest { ) Assertions.assertEquals("0", response.status) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/GetJobsTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/GetJobsTest.kt index 7ae3091..7cc2498 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/GetJobsTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/GetJobsTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosjobs -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.Job import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -27,10 +27,8 @@ class GetJobsTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -46,13 +44,13 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?owner=IBMUSER")) == true || - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?prefix=\\*")) == true || - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?jobid=JOB00023")) == true || - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?owner=testUser")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?owner=IBMUSER HTTP/.*")) == true || + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?prefix=\\* HTTP/.*")) == true || + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?jobid=JOB00023 HTTP/.*")) == true || + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?owner=testUser HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getJobs")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getJobs") ?: "").setResponseCode(200) } ) var jobs = getJobs.getJobsCommon(GetJobParams(owner = "IBMUSER")) @@ -77,10 +75,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?jobid=JOB00023")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?jobid=JOB00023 HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getJob")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getJob") ?: "").setResponseCode(200) } ) val job = getJobs.getJob("JOB00023") @@ -96,10 +94,10 @@ class GetJobsTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getStatus")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getStatus") ?: "").setResponseCode(200) } ) val job = getJobs.getStatusCommon(CommonJobParams("BLSJPRMI", "STC00052")) @@ -114,10 +112,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?owner=${connection.user}")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?owner=${connection.user} HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getJobsTest")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getJobsTest") ?: "").setResponseCode(200) } ) val jobs = getJobs.getJobs() @@ -133,10 +131,10 @@ class GetJobsTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getStatus")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getStatus") ?: "").setResponseCode(200) } ) val status = getJobs.getStatusValue("BLSJPRMI", "STC00052") @@ -152,10 +150,10 @@ class GetJobsTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getStatus")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getStatus") ?: "").setResponseCode(200) } ) val status = getJobs.getStatusValueForJob( @@ -182,10 +180,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getStatus")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getStatus") ?: "").setResponseCode(200) } ) val job = getJobs.getStatus(jobId = "STC00052", jobName = "BLSJPRMI") @@ -202,10 +200,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/BLSJPRMI/STC00052.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getStatus")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getStatus") ?: "").setResponseCode(200) } ) val job = getJobs.getStatusForJob(Job( @@ -229,10 +227,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?owner=ZOSMFAD")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?owner=ZOSMFAD HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getJobsByOwner")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getJobsByOwner") ?: "").setResponseCode(200) } ) val jobs = getJobs.getJobsByOwner("ZOSMFAD") @@ -247,10 +245,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?prefix=IJMP\\*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?prefix=IJMP\\* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getJobsByPrefix")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getJobsByPrefix") ?: "").setResponseCode(200) } ) val jobs = getJobs.getJobsByPrefix("IJMP*") @@ -265,10 +263,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?owner=ZOSMFAD&prefix=\\*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?owner=ZOSMFAD&prefix=\\* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getJobsByOwnerAndPrefix")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getJobsByOwnerAndPrefix") ?: "").setResponseCode(200) } ) val jobs = getJobs.getJobsByOwnerAndPrefix("ZOSMFAD" ,"*") @@ -284,10 +282,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getSpoolFiles")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getSpoolFiles") ?: "").setResponseCode(200) } ) val spoolFiles = getJobs.getSpoolFilesCommon(CommonJobParams(jobName = "NBEL", jobId = "TSU00555")) @@ -305,10 +303,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getSpoolFiles")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getSpoolFiles") ?: "").setResponseCode(200) } ) val spoolFiles = getJobs.getSpoolFilesForJob(Job( @@ -336,10 +334,10 @@ class GetJobsTest { val getJobs = GetJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("getSpoolFiles")).setResponseCode(200) + MockResponse().setBody(responseDispatcher.readMockJson("getSpoolFiles") ?: "").setResponseCode(200) } ) val spoolFiles = getJobs.getSpoolFiles("NBEL", "TSU00555") @@ -357,10 +355,10 @@ class GetJobsTest { val responseBody = javaClass.classLoader.getResource("mock/getJcl.txt")?.readText() responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files/JCL/records\\?mode=text")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files/JCL/records\\?mode=text HTTP/.*")) == true }, { - MockResponse().setBody(responseBody).setResponseCode(200) + MockResponse().setBody(responseBody ?: "").setResponseCode(200) } ) val jcl = getJobs.getJclCommon(CommonJobParams(jobName = "NBEL", jobId = "TSU00555")) @@ -375,10 +373,10 @@ class GetJobsTest { val responseBody = javaClass.classLoader.getResource("mock/getJcl.txt")?.readText() responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files/JCL/records\\?mode=text")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files/JCL/records\\?mode=text HTTP/.*")) == true }, { - MockResponse().setBody(responseBody).setResponseCode(200) + MockResponse().setBody(responseBody ?: "").setResponseCode(200) } ) val jcl = getJobs.getJcl(jobName = "NBEL", jobId = "TSU00555") @@ -393,10 +391,10 @@ class GetJobsTest { val responseBody = javaClass.classLoader.getResource("mock/getJcl.txt")?.readText() responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files/JCL/records.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/NBEL/TSU00555/files/JCL/records.* HTTP/.*")) == true }, { - MockResponse().setBody(responseBody).setResponseCode(200) + MockResponse().setBody(responseBody ?: "").setResponseCode(200) } ) val jcl = getJobs.getJclForJob( @@ -428,10 +426,10 @@ class GetJobsTest { val responseBody = javaClass.classLoader.getResource("mock/getSpoolFileContent.txt")?.readText() responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/TESTJCL/JOB09502/files/2/records.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/TESTJCL/JOB09502/files/2/records.* HTTP/.*")) == true }, { - MockResponse().setBody(responseBody).setResponseCode(200) + MockResponse().setBody(responseBody ?: "").setResponseCode(200) } ) val spoolFile = getJobs.getSpoolContentById("TESTJCL", "JOB09502", 2) @@ -445,4 +443,4 @@ class GetJobsTest { // TODO: implement!!! Use getSpoolContent mock. } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/MonitorJobsTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/MonitorJobsTest.kt index de78bd8..63a6100 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/MonitorJobsTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/MonitorJobsTest.kt @@ -3,8 +3,8 @@ package eu.ibagroup.r2z.zowe.zosjobs import com.google.gson.Gson -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.Job import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import java.net.InetSocketAddress import java.net.Proxy -import kotlin.concurrent.thread @TestInstance(TestInstance.Lifecycle.PER_CLASS) class MonitorJobsTest { @@ -29,10 +28,8 @@ class MonitorJobsTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -57,11 +54,11 @@ class MonitorJobsTest { var requestTimes = 0 responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}.*")) == true }, { ++requestTimes - val jobRsp = if (System.currentTimeMillis() - startTime < 12000) job.cloneWithChangedStatus(Job.Status.ACTIVE) + val jobRsp = if (System.currentTimeMillis() - startTime < 3000) job.cloneWithChangedStatus(Job.Status.ACTIVE) else job MockResponse().setBody(Gson().toJson(jobRsp)).setResponseCode(200) } @@ -69,7 +66,7 @@ class MonitorJobsTest { monitorJobs.waitForJobOutputStatus(job.jobName, job.jobId) val jobResponse = monitorJobs.waitForJobStatus(job.jobName, job.jobId, Job.Status.OUTPUT) Assertions.assertEquals(jobResponse.status, Job.Status.OUTPUT) - Assertions.assertEquals(6, requestTimes) + Assertions.assertEquals(3, requestTimes) responseDispatcher.clearValidationList() } @@ -85,11 +82,11 @@ class MonitorJobsTest { var requestTimes = 0 responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}.* HTTP/.*")) == true }, { ++requestTimes - val jobRsp = if (System.currentTimeMillis() - startTime < 12000) job.cloneWithChangedStatus(Job.Status.ACTIVE) + val jobRsp = if (System.currentTimeMillis() - startTime < 3000) job.cloneWithChangedStatus(Job.Status.ACTIVE) else job MockResponse().setBody(Gson().toJson(jobRsp)).setResponseCode(200) } @@ -97,7 +94,7 @@ class MonitorJobsTest { monitorJobs.waitForJobOutputStatus(job) val jobResponse = monitorJobs.waitForJobStatus(job, Job.Status.OUTPUT) Assertions.assertEquals(jobResponse.status, Job.Status.OUTPUT) - Assertions.assertEquals(4, requestTimes) + Assertions.assertEquals(3, requestTimes) responseDispatcher.clearValidationList() } @@ -113,7 +110,7 @@ class MonitorJobsTest { val mockSpoolFileContent = javaClass.classLoader.getResource("mock/getJcl.txt")?.readText() responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs\\?prefix=IJMP05&jobid=JOB09502")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs\\?prefix=IJMP05&jobid=JOB09502 HTTP/.*")) == true }, { MockResponse().setBody(mockJobString).setResponseCode(200) @@ -121,7 +118,7 @@ class MonitorJobsTest { ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}/files.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}/files.* HTTP/.*")) == true }, { MockResponse().setBody(mockSpoolFiles).setResponseCode(200) @@ -129,15 +126,15 @@ class MonitorJobsTest { ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}/files/2/records")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}/files/2/records HTTP/.*")) == true }, { - MockResponse().setBody(mockSpoolFileContent).setResponseCode(200) + MockResponse().setBody(mockSpoolFileContent ?: "").setResponseCode(200) } ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restjobs/jobs/${job.jobName}/${job.jobId}.* HTTP/.*")) == true }, { MockResponse().setBody(Gson().toJson(job)).setResponseCode(200) @@ -149,4 +146,4 @@ class MonitorJobsTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/SubmitJobsTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/SubmitJobsTest.kt index e750c45..6518e43 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/SubmitJobsTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosjobs/SubmitJobsTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosjobs -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.Intrdr_Recfm import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -26,10 +26,8 @@ class SubmitJobsTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -46,10 +44,10 @@ class SubmitJobsTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs")) == true && - String(it.body).matches(Regex(".*\"file\":\"//'TEST.JOBS\\(JOBNAME\\)'\".*")) + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restjobs/jobs HTTP/.*")) == true && + it.body.toString().matches(Regex(".*\"file\":\"//'TEST.JOBS\\(JOBNAME\\)'\".*")) }, - { MockResponse().setBody(responseDispatcher.readMockJson("submitJobs")) } + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobs") ?: "") } ) val response = submitJobs.submitJob("//'TEST.JOBS(JOBNAME)'") Assertions.assertEquals("ZOSMFAD", response.owner) @@ -66,10 +64,10 @@ class SubmitJobsTest { jobDataSet = "//'TEST.JCL(TESTJOB)'" ) responseDispatcher.injectEndpoint({ - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs")) == true && - String(it.body).matches(Regex(".*\"file\":\"//'TEST.JCL\\(TESTJOB\\)'\".*")) + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restjobs/jobs HTTP/.*")) == true && + it.body.toString().matches(Regex(".*\"file\":\"//'TEST.JCL\\(TESTJOB\\)'\".*")) }, { - MockResponse().setBody(responseDispatcher.readMockJson("submitJobCommonResponse")) + MockResponse().setBody(responseDispatcher.readMockJson("submitJobCommonResponse") ?: "") }) val jobSubmitResponse = submitJobs.submitJobCommon(params) Assertions.assertEquals("TESTJOB", jobSubmitResponse.jobname) @@ -83,11 +81,11 @@ class SubmitJobsTest { val submitJobs = SubmitJobs(connection, proxyClient) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs")) == true && + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restjobs/jobs HTTP/.*")) == true && it.getHeader("X-IBM-Intrdr-Recfm") == Intrdr_Recfm.F.value && it.getHeader("X-IBM-Intrdr-Lrecl") == "F" }, - { MockResponse().setBody(responseDispatcher.readMockJson("submitJobs")).setResponseCode(201) } + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobs") ?: "").setResponseCode(201) } ) val response = submitJobs.submitJcl( javaClass.classLoader.getResource("mock/submitJcl.txt")?.readText() ?: "nothing", @@ -104,11 +102,11 @@ class SubmitJobsTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restjobs/jobs")) == true && + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restjobs/jobs HTTP/.*")) == true && it.getHeader("X-IBM-Intrdr-Recfm") == Intrdr_Recfm.F.value && it.getHeader("X-IBM-Intrdr-Lrecl") == "F" }, - { MockResponse().setBody(responseDispatcher.readMockJson("submitJobs")).setResponseCode(201) } + { MockResponse().setBody(responseDispatcher.readMockJson("submitJobs") ?: "").setResponseCode(201) } ) val params = SubmitJclParams( jcl = javaClass.classLoader.getResource("mock/submitJcl.txt")?.readText() ?: "nothing", @@ -119,4 +117,4 @@ class SubmitJobsTest { Assertions.assertEquals("ZOSMFAD", response.owner) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssDownloadTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssDownloadTest.kt index 99d14a2..bd67e1b 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssDownloadTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssDownloadTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosfiles -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zosfiles.ZosDsnDownload @@ -25,10 +25,8 @@ class ZosUssDownloadTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -43,10 +41,10 @@ class ZosUssDownloadTest { val conn = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") val zosUssFileDownload = ZosUssFileDownload(conn, proxyClient) - val responseBody = javaClass.classLoader.getResource("mock/downloadDsnMember.txt")?.readText() + val responseBody = javaClass.classLoader.getResource("mock/downloadDsnMember.txt")?.readText() ?: "" responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/fs/u/IJMP/text.txt")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/fs/u/IJMP/text.txt HTTP/.*")) == true }, { MockResponse().setBody(responseBody) } ) @@ -57,4 +55,4 @@ class ZosUssDownloadTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileList.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileList.kt index 72dd8ce..9a2e000 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileList.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileList.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosfiles -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zosfiles.ZosDsnList @@ -27,10 +27,8 @@ class ZosUssFileList { fun createMockServer () { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -46,9 +44,9 @@ class ZosUssFileList { val listParams = UssListParams() val zosUssFileList = ZosUssFileList(conn, proxyClient) responseDispatcher.injectEndpoint({ - it?.path?.matches(Regex("http://.*/zosmf/restfiles/fs.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/restfiles/fs.* HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("listDatasets")) + MockResponse().setBody(responseDispatcher.readMockJson("listDatasets") ?: "") }) val fileList = zosUssFileList.listFiles("/u/STV", listParams) responseDispatcher.clearValidationList() diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt index 4748c68..a4c8192 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt @@ -3,8 +3,8 @@ package eu.ibagroup.r2z.zowe.zosuss import com.google.gson.Gson -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.* import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -26,10 +26,8 @@ class ZosUssFileTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -46,7 +44,7 @@ class ZosUssFileTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/fs/u/IJMP/text.txt")) == true + it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/restfiles/fs/u/IJMP/text.txt HTTP/.*")) == true }, { MockResponse().setResponseCode(204) } ) @@ -67,7 +65,7 @@ class ZosUssFileTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/fs/u/IJMP/text.txt")) == true //&& + it?.requestLine?.matches(Regex("POST http://.*/zosmf/restfiles/fs/u/IJMP/text.txt HTTP/.*")) == true //&& //Gson().fromJson(String(it.body), CreateUssFile::class.java) == params }, { MockResponse().setResponseCode(201) } @@ -89,7 +87,7 @@ class ZosUssFileTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/fs/u/IJMP/newdir")) == true //&& + it?.requestLine?.matches(Regex("POST http://.*/zosmf/restfiles/fs/u/IJMP/newdir HTTP/.*")) == true //&& //Gson().fromJson(String(it.body), CreateUssFile::class.java) == params }, { MockResponse().setResponseCode(201) } @@ -108,7 +106,7 @@ class ZosUssFileTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restfiles/fs/u/IJMP/text.txt")) == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/fs/u/IJMP/text.txt HTTP/.*")) == true }, { MockResponse().setResponseCode(201) } ) From 6b63e9d958f3c2f1a4ff60f0f806cfc8f06cc12a Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Fri, 4 Nov 2022 13:26:54 +0100 Subject: [PATCH 08/23] Version bump --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a044331..e9546fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.3 +projectVersion=1.3.0-rc.4 From 70dec0dd14887228a74370034f82a126e7b9a7d1 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Fri, 4 Nov 2022 13:40:55 +0100 Subject: [PATCH 09/23] Some tests fixes --- .../r2z/zowe/zosconsole/IssueCommandTest.kt | 20 ++++++------- .../ibagroup/r2z/zowe/zostso/IssueTsoTest.kt | 28 +++++++++---------- .../ibagroup/r2z/zowe/zostso/SendTsoTest.kt | 24 ++++++++-------- .../ibagroup/r2z/zowe/zostso/StartTsoTest.kt | 20 ++++++------- .../ibagroup/r2z/zowe/zostso/StopTsoTest.kt | 16 +++++------ 5 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt index 06f58cb..218d59c 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosconsole/IssueCommandTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zosconsole -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.IssueRequestBody import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -24,10 +24,8 @@ class IssueCommandTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -49,10 +47,10 @@ class IssueCommandTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restconsoles/consoles/ibmusecn")) == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restconsoles/consoles/ibmusecn HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("issueCommand")) + MockResponse().setBody(responseDispatcher.readMockJson("issueCommand") ?: "") } ) @@ -74,10 +72,10 @@ class IssueCommandTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/restconsoles/consoles/defcn")) == true + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restconsoles/consoles/defcn HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("issueCommand")) + MockResponse().setBody(responseDispatcher.readMockJson("issueCommand") ?: "") } ) @@ -91,4 +89,4 @@ class IssueCommandTest { -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt index 0b65ea5..4651cea 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/IssueTsoTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zostso -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zostso.IssueTso @@ -24,10 +24,8 @@ class IssueTsoTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -53,34 +51,34 @@ class IssueTsoTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso.*proc=DBSPROCB.*acct=IZUACCT.*")) == true && it.method == "POST" + it?.requestLine?.matches(Regex("POST http://.*/zosmf/tsoApp/tso.*proc=DBSPROCB.*acct=IZUACCT.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("startTso")) + MockResponse().setBody(responseDispatcher.readMockJson("startTso") ?: "") } ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "PUT" + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/tsoApp/tso/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso")) + MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso") ?: "") } ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" + it?.requestLine?.matches(Regex("GET http://.*/zosmf/tsoApp/tso/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso") ?: "") } ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/DLIS-121-aabcaaat")) == true && it.method == "DELETE" + it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/tsoApp/tso/DLIS-121-aabcaaat HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("stopTso")) + MockResponse().setBody(responseDispatcher.readMockJson("stopTso") ?: "") } ) @@ -97,4 +95,4 @@ class IssueTsoTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt index 41e37b3..951f6f7 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/SendTsoTest.kt @@ -3,8 +3,8 @@ package eu.ibagroup.r2z.zowe.zostso import com.google.gson.Gson -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.TsoResponse import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection @@ -25,10 +25,8 @@ class SendTsoTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -45,10 +43,10 @@ class SendTsoTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true + it?.requestLine?.matches(Regex("GET http://.*/zosmf/tsoApp/tso/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso") ?: "") } ) @@ -66,18 +64,18 @@ class SendTsoTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "PUT" + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/tsoApp/tso/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso")) + MockResponse().setBody(responseDispatcher.readMockJson("sendMessageToTso") ?: "") } ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" + it?.requestLine?.matches(Regex("GET http://.*/zosmf/tsoApp/tso/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso") ?: "") } ) @@ -92,4 +90,4 @@ class SendTsoTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt index 947b0c8..ddc513b 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StartTsoTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zostso -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zostso.StartTso @@ -24,10 +24,8 @@ class StartTsoTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -44,18 +42,18 @@ class StartTsoTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso.*proc=DBSPROCB.*acct=IZUACCT.*")) == true && it.method == "POST" + it?.requestLine?.matches(Regex("POST http://.*/zosmf/tsoApp/tso.*proc=DBSPROCB.*acct=IZUACCT.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("startTso")) + MockResponse().setBody(responseDispatcher.readMockJson("startTso") ?: "") } ) responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/.*")) == true && it.method == "GET" + it?.requestLine?.matches(Regex("GET http://.*/zosmf/tsoApp/tso/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso")) + MockResponse().setBody(responseDispatcher.readMockJson("receiveMessagesFromTso") ?: "") } ) @@ -76,4 +74,4 @@ class StartTsoTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt index a9aeba1..e35c8b1 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zostso/StopTsoTest.kt @@ -2,8 +2,8 @@ package eu.ibagroup.r2z.zowe.zostso -import com.squareup.okhttp.mockwebserver.MockResponse -import com.squareup.okhttp.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import eu.ibagroup.r2z.zowe.* import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import eu.ibagroup.r2z.zowe.client.sdk.zostso.StopTso @@ -24,10 +24,8 @@ class StopTsoTest { fun createMockServer() { mockServer = MockWebServer() responseDispatcher = MockResponseDispatcher() - mockServer.setDispatcher(responseDispatcher) - thread(start = true) { - mockServer.play() - } + mockServer.dispatcher = responseDispatcher + mockServer.start() val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) proxyClient = OkHttpClient.Builder().proxy(proxy).build() } @@ -48,10 +46,10 @@ class StopTsoTest { responseDispatcher.injectEndpoint( { - it?.path?.matches(Regex("http://.*/zosmf/tsoApp/tso/DLIS-121-aabcaaat")) == true && it.method == "DELETE" + it?.requestLine?.matches(Regex("DELETE http://.*/zosmf/tsoApp/tso/DLIS-121-aabcaaat HTTP/.*")) == true }, { - MockResponse().setBody(responseDispatcher.readMockJson("stopTso")) + MockResponse().setBody(responseDispatcher.readMockJson("stopTso") ?: "") } ) @@ -64,4 +62,4 @@ class StopTsoTest { responseDispatcher.clearValidationList() } -} \ No newline at end of file +} From 1203a426c063a78451a675179bea119b55d90501 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Fri, 4 Nov 2022 14:32:45 +0100 Subject: [PATCH 10/23] Version bump --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e9546fa..82b9ee6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.4 +projectVersion=1.3.0-rc.5 From a0e187c0fcafff2c4cbddfdbe897aaba53f2d25a Mon Sep 17 00:00:00 2001 From: Valiantsin Studzenichnik Date: Fri, 18 Nov 2022 13:16:11 +0300 Subject: [PATCH 11/23] IJMP-801 Add bytes converter --- gradle.properties | 2 +- .../kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 82b9ee6..ecaf1e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.5 +projectVersion=1.3.0-rc.6 diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt index 5bbe6dc..4898298 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt @@ -72,7 +72,7 @@ class ZosUssFile ( */ fun writeToFile(filePath: String, text: ByteArray): Response<*> { val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" - val dataApi = buildApi(url, httpClient) + val dataApi = buildApiWithBytesConverter(url, httpClient) val call = dataApi.writeToUssFile( authorizationToken = Credentials.basic(connection.user, connection.password), filePath = FilePath(filePath), From 632b103c29c7a1413376c4ce216fd7e91ea21e25 Mon Sep 17 00:00:00 2001 From: Valiantsin Studzenichnik Date: Tue, 29 Nov 2022 12:24:32 +0300 Subject: [PATCH 12/23] IJMP-771-write-binary --- .../r2z/zowe/client/sdk/zosuss/ZosUssFile.kt | 24 +++++++++++++++++++ .../r2z/zowe/zosuss/ZosUssFileTest.kt | 18 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt index 5bbe6dc..ff9bfa6 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt @@ -84,4 +84,28 @@ class ZosUssFile ( } return response ?: throw Exception("No response returned") } + + /** + * Writes to USS binary file. Creates new if not exist + * + * @param filePath path of the file or directory (e.g. u/jiahj/text.txt) + * @param inputFile file to be written to + * @return http response object + * @throws Exception error processing request + */ + fun writeToFileBin(filePath: String, inputFile: ByteArray): Response<*> { + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val dataApi = buildApi(url, httpClient) + val call = dataApi.writeToUssFile( + authorizationToken = Credentials.basic(connection.user, connection.password), + filePath = FilePath(filePath), + body = inputFile, + xIBMDataType = XIBMDataType(XIBMDataType.Type.BINARY) + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception(response?.errorBody()?.string()) + } + return response ?: throw Exception("No response returned") + } } diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt index a4c8192..76c30b6 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssFileTest.kt @@ -115,5 +115,23 @@ class ZosUssFileTest { responseDispatcher.clearValidationList() } + + @Test + fun writeToFileBin() { + val connection = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val zosUssFile = ZosUssFile(connection, proxyClient) + val text = "Hello There!" + + responseDispatcher.injectEndpoint( + { + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/fs/u/IJMP/file HTTP/.*")) == true + }, + { MockResponse().setResponseCode(201) } + ) + val response = zosUssFile.writeToFileBin("/u/IJMP/file", text.toByteArray()) + Assertions.assertEquals(201, response.code()) + + responseDispatcher.clearValidationList() + } } From 874c9f03a67c7cb2fb5da303ec20ff51335c89d8 Mon Sep 17 00:00:00 2001 From: Valiantsin Studzenichnik Date: Tue, 6 Dec 2022 17:04:15 +0300 Subject: [PATCH 13/23] IJMP-790 USS copying functionality --- src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt | 2 +- .../r2z/zowe/client/sdk/zosuss/ZosUssCopy.kt | 108 ++++++++++++++++++ .../r2z/zowe/zosuss/ZosUssCopyTest.kt | 86 ++++++++++++++ 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssCopy.kt create mode 100644 src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssCopyTest.kt diff --git a/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt b/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt index 11af75a..0e2599d 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt @@ -461,7 +461,7 @@ interface DataAPI { @Header("Authorization") authorizationToken: String, @Header("X-IBM-BPXK-AUTOCVT") xIBMBpxkAutoCvt: XIBMBpxkAutoCvt? = null, @Body body: CopyDataUSS.CopyFromFileOrDir, - @Path("filepath-name") filePath: FilePath, + @Path("filepath-name", encoded = true) filePath: FilePath, ): Call /** diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssCopy.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssCopy.kt new file mode 100644 index 0000000..db3506d --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssCopy.kt @@ -0,0 +1,108 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.client.sdk.zosuss + +import eu.ibagroup.r2z.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import okhttp3.Credentials +import okhttp3.OkHttpClient +import retrofit2.Response + +class ZosUssCopy ( + var connection: ZOSConnection, + var httpClient: OkHttpClient = UnsafeOkHttpClient.unsafeOkHttpClient +) { + init { + connection.checkConnection() + } + + var response: Response<*>? = null + + /** + * Copies USS file + * + * @param filePath path of the file or directory (e.g. u/jiahj/text.txt) + * @param destPath path where to copy the file + * @param replace if true file in the target destination will be overwritten + * @return http response object + * @throws Exception error processing request + */ + fun copy(filePath: String, destPath: String, replace: Boolean): Response<*> { + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val dataApi = buildApi(url, httpClient) + val call = dataApi.copyUssFile( + authorizationToken = Credentials.basic(connection.user, connection.password), + body = CopyDataUSS.CopyFromFileOrDir( + from = filePath, + overwrite = replace + ), + filePath = FilePath(destPath) + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception(response?.errorBody()?.string()) + } + return response ?: throw Exception("No response returned") + } + + /** + * Copies USS file to sequential dataset + * + * @param filePath path of the file or directory (e.g. u/jiahj/text.txt) + * @param dsn dataset where to copy the file + * @param replace if true information in the target dataset will be replaced + * @return http response object + * @throws Exception error processing request + */ + fun copyToDS(filePath: String, dsn: String, replace: Boolean): Response<*> { + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val dataApi = buildApi(url, httpClient) + val call = dataApi.copyToDatasetFromUss( + authorizationToken = Credentials.basic(connection.user, connection.password), + body = CopyDataZOS.CopyFromFile( + file = CopyDataZOS.CopyFromFile.File( + fileName = filePath + ), + replace = replace + ), + toDatasetName = dsn + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception(response?.errorBody()?.string()) + } + return response ?: throw Exception("No response returned") + } + + /** + * Copies USS file to dataset member + * + * @param filePath path of the file or directory (e.g. u/jiahj/text.txt) + * @param dsn dataset where to copy the file + * @param member dataset member where to copy the file + * @param replace if true information in the target member will be replaced + * @return http response object + * @throws Exception error processing request + */ + fun copyToMember(filePath: String, dsn: String, member: String, replace: Boolean): Response<*> { + val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" + val dataApi = buildApi(url, httpClient) + val call = dataApi.copyToDatasetMemberFromUssFile( + authorizationToken = Credentials.basic(connection.user, connection.password), + body = CopyDataZOS.CopyFromFile( + file = CopyDataZOS.CopyFromFile.File( + fileName = filePath + ), + replace = replace + ), + toDatasetName = dsn, + memberName = member + ) + response = call.execute() + if (response?.isSuccessful != true) { + throw Exception(response?.errorBody()?.string()) + } + return response ?: throw Exception("No response returned") + } + +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssCopyTest.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssCopyTest.kt new file mode 100644 index 0000000..7e45279 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/zosuss/ZosUssCopyTest.kt @@ -0,0 +1,86 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z.zowe.zosfiles + +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import eu.ibagroup.r2z.zowe.* +import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection +import eu.ibagroup.r2z.zowe.client.sdk.zosuss.ZosUssCopy +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import java.net.InetSocketAddress +import java.net.Proxy + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ZosUssCopyTest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.dispatcher = responseDispatcher + mockServer.start() + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun testCopyFile() { + val conn = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val zosUssCopy = ZosUssCopy(conn, proxyClient) + responseDispatcher.injectEndpoint( + { + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/fs/u/IJMP/destFile HTTP/.*")) == true + }, { + MockResponse().setResponseCode(204) + } + ) + val response = zosUssCopy.copy("/u/IJMP/fileToCopy", "/u/IJMP/destFile", replace = true) + Assertions.assertEquals(204, response.code()) + + responseDispatcher.clearValidationList() + } + + @Test + fun testCopyFileToDS() { + val conn = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val zosUssCopy = ZosUssCopy(conn, proxyClient) + responseDispatcher.injectEndpoint( + { + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/ds/IJMP.TEST.DATA HTTP/.*")) == true + }, { + MockResponse().setResponseCode(204) + } + ) + val response = zosUssCopy.copyToDS("/u/IJMP/fileToCopy", "IJMP.TEST.DATA", replace = true) + Assertions.assertEquals(204, response.code()) + + responseDispatcher.clearValidationList() + } + + @Test + fun testCopyFileToMember() { + val conn = ZOSConnection(TEST_HOST, TEST_PORT, TEST_USER, TEST_PASSWORD, "http") + val zosUssCopy = ZosUssCopy(conn, proxyClient) + responseDispatcher.injectEndpoint( + { + it?.requestLine?.matches(Regex("PUT http://.*/zosmf/restfiles/ds/IJMP.TEST.DATA\\(TESTMEM\\) HTTP/.*")) == true + }, { + MockResponse().setResponseCode(204) + } + ) + val response = zosUssCopy.copyToMember("/u/IJMP/fileToCopy", "IJMP.TEST.DATA", "TESTMEM", replace = true) + Assertions.assertEquals(204, response.code()) + + responseDispatcher.clearValidationList() + } +} From 3aac12469cdb0cd35df19b550eafb1bd96217d26 Mon Sep 17 00:00:00 2001 From: Valiantsin Studzenichnik Date: Tue, 6 Dec 2022 17:11:59 +0300 Subject: [PATCH 14/23] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 82b9ee6..f549b24 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.5 +projectVersion=1.3.0-rc.7 From 2d4b2d7ded2e158dc1e09126799abf03584975fb Mon Sep 17 00:00:00 2001 From: Valiantsin Studzenichnik Date: Wed, 7 Dec 2022 14:04:13 +0300 Subject: [PATCH 15/23] IJMP-820 Bugfix --- gradle.properties | 2 +- .../eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index b5b8d62..ab0241b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.8 +projectVersion=1.3.0-rc.9 diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt index eea5be1..0f6051b 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt @@ -95,12 +95,13 @@ class ZosUssFile ( */ fun writeToFileBin(filePath: String, inputFile: ByteArray): Response<*> { val url = "${connection.protocol}://${connection.host}:${connection.zosmfPort}" - val dataApi = buildApi(url, httpClient) + val dataApi = buildApiWithBytesConverter(url, httpClient) val call = dataApi.writeToUssFile( authorizationToken = Credentials.basic(connection.user, connection.password), filePath = FilePath(filePath), body = inputFile, - xIBMDataType = XIBMDataType(XIBMDataType.Type.BINARY) + xIBMDataType = XIBMDataType(XIBMDataType.Type.BINARY), + contentType = "application/octet-stream" ) response = call.execute() if (response?.isSuccessful != true) { From 4f596df586e49d9bc00d0c31e0bcae8a20d848db Mon Sep 17 00:00:00 2001 From: VKrus Date: Thu, 22 Dec 2022 12:41:44 +0100 Subject: [PATCH 16/23] IJMP-748: added adapter for plugins in InfoResponse. --- .../kotlin/eu/ibagroup/r2z/InfoResponse.kt | 26 +++++- .../kotlin/eu/ibagroup/r2z/InfoAPITest.kt | 83 +++++++++++++++++++ .../r2z/zowe/MockResponseDispatcher.kt | 20 +++-- src/test/resources/mock/infoResponse.json | 26 ++++++ .../mock/infoResponsePluginsError.json | 13 +++ 5 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 src/test/kotlin/eu/ibagroup/r2z/InfoAPITest.kt create mode 100644 src/test/resources/mock/infoResponse.json create mode 100644 src/test/resources/mock/infoResponsePluginsError.json diff --git a/src/main/kotlin/eu/ibagroup/r2z/InfoResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/InfoResponse.kt index 26a7c93..7ee561f 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/InfoResponse.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/InfoResponse.kt @@ -2,8 +2,13 @@ package eu.ibagroup.r2z +import com.google.gson.Gson +import com.google.gson.TypeAdapter import com.google.gson.annotations.Expose +import com.google.gson.annotations.JsonAdapter import com.google.gson.annotations.SerializedName +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter import eu.ibagroup.r2z.annotations.ZVersion data class InfoResponse ( @@ -24,6 +29,7 @@ data class InfoResponse ( val zosmfHostname: String = "null", @SerializedName("plugins") + @JsonAdapter(PluginsAdapter::class) @Expose val plugins: List = emptyList(), @@ -46,4 +52,22 @@ data class InfoResponse ( "04.28.00" -> ZVersion.ZOS_2_5 else -> ZVersion.ZOS_2_1 } -} \ No newline at end of file +} + +class PluginsAdapter : TypeAdapter>() { + private val gson = Gson() + override fun write(out: JsonWriter?, value: List?) { + gson.toJson(value, List::class.java, out) + } + + override fun read(reader: JsonReader): List { + var result = listOf() + runCatching { + result = gson.fromJson(reader, List::class.java) + }.onFailure { + reader.skipValue() + } + return result + } + +} diff --git a/src/test/kotlin/eu/ibagroup/r2z/InfoAPITest.kt b/src/test/kotlin/eu/ibagroup/r2z/InfoAPITest.kt new file mode 100644 index 0000000..719398d --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/r2z/InfoAPITest.kt @@ -0,0 +1,83 @@ +// Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ + +package eu.ibagroup.r2z + +import com.google.gson.GsonBuilder +import eu.ibagroup.r2z.zowe.* +import okhttp3.OkHttpClient +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.lang.Exception +import java.net.InetSocketAddress +import java.net.Proxy + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class InfoAPITest { + lateinit var mockServer: MockWebServer + lateinit var proxyClient: OkHttpClient + lateinit var responseDispatcher: MockResponseDispatcher + lateinit var infoAPI: InfoAPI + + @BeforeAll + fun createMockServer() { + mockServer = MockWebServer() + responseDispatcher = MockResponseDispatcher() + mockServer.dispatcher = responseDispatcher + mockServer.start() + val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(mockServer.hostName, mockServer.port)) + proxyClient = OkHttpClient.Builder().proxy(proxy).build() + val gson = GsonBuilder().create() + infoAPI = Retrofit.Builder() + .baseUrl("http://${TEST_HOST}:${TEST_PORT}") + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(proxyClient) + .build() + .create(InfoAPI::class.java) + } + + @AfterEach + fun cleanupTest() { + responseDispatcher.clearValidationList() + } + + @AfterAll + fun stopMockServer() { + mockServer.shutdown() + } + + @Test + fun testPluginsAdapterForArrayResponse() { + responseDispatcher.injectEndpoint( + { true }, + { MockResponse().setBody(responseDispatcher.readMockJson("infoResponse") ?: "") } + ) + responseDispatcher.removeAuthorizationFromZosmfEndpoint("info") + + val response = infoAPI.getSystemInfo().execute() + Assertions.assertEquals(response.isSuccessful, true) + val result = response.body() ?: throw Exception("Response for info request should not be empty") + Assertions.assertEquals(result.plugins.size, 3) + } + + @Test + fun testPluginsAdapterForObjectResponse() { + responseDispatcher.injectEndpoint( + { true }, + { MockResponse().setBody(responseDispatcher.readMockJson("infoResponsePluginsError") ?: "") } + ) + responseDispatcher.removeAuthorizationFromZosmfEndpoint("info") + + val response = infoAPI.getSystemInfo().execute() + Assertions.assertEquals(response.isSuccessful, true) + val result = response.body() ?: throw Exception("Response for info request should not be empty") + Assertions.assertEquals(result.plugins.size, 0) + } +} \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt b/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt index ff79d5c..c3fd7d9 100644 --- a/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt +++ b/src/test/kotlin/eu/ibagroup/r2z/zowe/MockResponseDispatcher.kt @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Assertions class MockResponseDispatcher : Dispatcher() { var validationList = mutableListOfBoolean, (RecordedRequest?)->MockResponse>>() + private var endpointsWithoutAuthorization = mutableSetOf() private fun getResourceText(resourcePath: String): String? { return javaClass.classLoader.getResource(resourcePath)?.readText() @@ -23,6 +24,10 @@ class MockResponseDispatcher : Dispatcher() { return base64Credentials.decodeFromBase64() } + fun removeAuthorizationFromZosmfEndpoint (endpoint: String) { + endpointsWithoutAuthorization.add(endpoint) + } + fun injectEndpoint (acceptable: (RecordedRequest?)->Boolean, handler: (RecordedRequest?)->MockResponse) { validationList.add(Pair(acceptable, handler)) } @@ -32,12 +37,15 @@ class MockResponseDispatcher : Dispatcher() { } override fun dispatch(request: RecordedRequest): MockResponse { - val authTokenRequest = request.getHeader("Authorization") ?: Assertions.fail("auth token must be presented.") - val credentials = decodeBasicAuthToken(authTokenRequest).split(":") - val usernameRequest = credentials[0] - val passwordRequest = credentials[1] - Assertions.assertEquals(usernameRequest, TEST_USER) - Assertions.assertEquals(passwordRequest, TEST_PASSWORD) + val zosmfPath = request.requestLine.split("/zosmf/").last() + if (endpointsWithoutAuthorization.none { zosmfPath.contains(it) }) { + val authTokenRequest = request.getHeader("Authorization") ?: Assertions.fail("auth token must be presented.") + val credentials = decodeBasicAuthToken(authTokenRequest).split(":") + val usernameRequest = credentials[0] + val passwordRequest = credentials[1] + Assertions.assertEquals(usernameRequest, TEST_USER) + Assertions.assertEquals(passwordRequest, TEST_PASSWORD) + } return validationList.firstOrNull { it.first(request) }?.second?.let { it(request) } ?: return MockResponse().setResponseCode(404) } diff --git a/src/test/resources/mock/infoResponse.json b/src/test/resources/mock/infoResponse.json new file mode 100644 index 0000000..3d383cb --- /dev/null +++ b/src/test/resources/mock/infoResponse.json @@ -0,0 +1,26 @@ +{ + "zos_version": "04.26.00", + "zosmf_port": "10443", + "zosmf_version": "26", + "zosmf_hostname": "S0W1.DAL-EBIS.IHOST.COM", + "plugins": [ + { + "pluginVersion": "HSMA230;PH15438P;2019-09-16T04:53:43", + "pluginDefaultName": "z/OS Operator Consoles", + "pluginStatus": "ACTIVE" + }, + { + "pluginVersion": "HSMA234;PH09032P;2019-03-04T16:37:21", + "pluginDefaultName": "Software Deployment", + "pluginStatus": "ACTIVE" + }, + { + "pluginVersion": "HSMA230;PI96931P;2018-05-22T06:55:26", + "pluginDefaultName": "Variables", + "pluginStatus": "ACTIVE" + } + ], + "zosmf_saf_realm": "SAFRealm", + "zosmf_full_version": "26.0", + "api_version": "1" +} \ No newline at end of file diff --git a/src/test/resources/mock/infoResponsePluginsError.json b/src/test/resources/mock/infoResponsePluginsError.json new file mode 100644 index 0000000..de5af88 --- /dev/null +++ b/src/test/resources/mock/infoResponsePluginsError.json @@ -0,0 +1,13 @@ +{ + "zos_version": "04.28.00", + "zosmf_port": "1443", + "zosmf_version": "28", + "zosmf_hostname": "stuff", + "plugins": { + "msgId": "IZUG612E", + "msgText": "IZUG612E" + }, + "zosmf_saf_realm": "SAFRealm", + "zosmf_full_version": "28.0", + "api_version": "1" +} \ No newline at end of file From 1e5a908f481200510939e8ab826775e075a845f3 Mon Sep 17 00:00:00 2001 From: "vkrus@ibagroup.eu" <4a666187> Date: Thu, 12 Jan 2023 14:25:23 +0300 Subject: [PATCH 17/23] changed r2z version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ab0241b..3148848 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.9 +projectVersion=1.3.0-rc.10 From 76d81f61214a515a1e0ef050723694fc9c2f6abf Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Wed, 18 Jan 2023 13:03:51 +0100 Subject: [PATCH 18/23] Removed codePage from Zowe Config --- src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt index 8e1abe7..aa8e861 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt @@ -13,7 +13,6 @@ package eu.ibagroup.r2z.zowe.config import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.annotations.SerializedName -import eu.ibagroup.r2z.CodePage import eu.ibagroup.r2z.zowe.client.sdk.core.ZOSConnection import java.io.File import java.lang.IllegalStateException @@ -312,10 +311,6 @@ class ZoweConfig( get() = searchProperty("responseTimeout") { zosmf(); base() } as Long? ?: 600 set(el) { updateProperty("responseTimeout", el) { zosmf(); base() } } - var codePage: CodePage - get() = CodePage.valueOf("IBM_${(searchProperty("codePage") { tso(); base() } as String).filter { it.isDigit() }}") - set(el) { updateProperty("codePage", el.codePage.filter { it.isDigit() }) { tso(); base() } } - /** * Searches profile by its path. For example if profile has path "gr1.example" then it will search * profile "example" in "gr1" group. From 11e416c63204bd0f983a6b882e48e1c2ac4253aa Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Thu, 2 Feb 2023 17:41:38 +0100 Subject: [PATCH 19/23] IJMP-852 Added Dokka --- build.gradle | 6 +++--- gradle.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 655bafb..dd6aad4 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'java' id 'org.jetbrains.kotlin.jvm' version '1.6.21' + id 'org.jetbrains.dokka' version '1.7.20' } apply plugin: 'java' @@ -26,7 +27,7 @@ repositories { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib" implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.21" - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' implementation 'com.squareup.retrofit2:retrofit:2.9.0', @@ -34,8 +35,7 @@ dependencies { 'com.squareup.retrofit2:converter-scalars:2.9.0', 'com.google.code.gson:gson:2.10', 'com.starxg:java-keytar:1.0.0', - 'org.yaml:snakeyaml:1.29', - 'org.junit.jupiter:junit-jupiter-api:5.8.2' + 'org.yaml:snakeyaml:1.33' } test { diff --git a/gradle.properties b/gradle.properties index 3148848..0d8cdfa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ signing.keyId=FADC1195 signing.password=key_pass signing.secretKeyRingFile=path/to/secret/key -projectVersion=1.3.0-rc.10 +projectVersion=1.3.0-rc.11 From ff8e7d6d88888ed8d8ed58f6de28317f2c228dd0 Mon Sep 17 00:00:00 2001 From: Uladzislau Kalesnikau Date: Mon, 13 Feb 2023 18:31:03 +0300 Subject: [PATCH 20/23] Resolve IJMP-852 "Feature/ docs" --- .gitignore | 1 + README.md | 8 +++ build.gradle | 22 ++++++- dokka/assets/logo-styles.css | 15 +++++ dokka/assets/zowe-icon.png | Bin 0 -> 58679 bytes dokka/templates/includes/footer.ftl | 15 +++++ dokka/templates/includes/header.ftl | 25 ++++++++ dokka/templates/includes/page_metadata.ftl | 6 ++ src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt | 44 ++++++++----- src/main/kotlin/eu/ibagroup/r2z/InfoAPI.kt | 4 +- src/main/kotlin/eu/ibagroup/r2z/SystemsApi.kt | 5 +- .../kotlin/eu/ibagroup/r2z/SystemsResponse.kt | 30 ++++----- src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt | 58 +++++++++--------- .../r2z/zowe/client/sdk/core/ZOSConnection.kt | 4 +- .../client/sdk/zosconsole/ConsoleResponse.kt | 4 +- .../client/sdk/zosconsole/IssueCommand.kt | 16 ++--- .../client/sdk/zosfiles/ZosDsnDownload.kt | 6 +- .../zowe/client/sdk/zosfiles/ZosDsnList.kt | 6 +- .../r2z/zowe/client/sdk/zosjobs/CancelJobs.kt | 4 +- .../r2z/zowe/client/sdk/zosjobs/DeleteJobs.kt | 4 +- .../r2z/zowe/client/sdk/zosjobs/GetJobs.kt | 20 +++--- .../zowe/client/sdk/zosjobs/MonitorJobs.kt | 33 ++++------ .../r2z/zowe/client/sdk/zosjobs/SubmitJobs.kt | 8 +-- .../r2z/zowe/client/sdk/zostso/SendTso.kt | 14 ++--- .../r2z/zowe/client/sdk/zostso/StartTso.kt | 10 +-- .../r2z/zowe/client/sdk/zostso/StopTso.kt | 8 +-- .../r2z/zowe/client/sdk/zosuss/ZosUssFile.kt | 2 +- .../eu/ibagroup/r2z/zowe/config/ZoweConfig.kt | 53 ++++++++-------- .../eu/ibagroup/r2z/zowe/config/utils.kt | 22 +++---- 29 files changed, 269 insertions(+), 178 deletions(-) create mode 100644 dokka/assets/logo-styles.css create mode 100644 dokka/assets/zowe-icon.png create mode 100644 dokka/templates/includes/footer.ftl create mode 100644 dokka/templates/includes/header.ftl create mode 100644 dokka/templates/includes/page_metadata.ftl diff --git a/.gitignore b/.gitignore index b1adc24..35b386c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .idea/*.iml .idea/modules .idea/.gitignore +.idea/codeStyles build .gradle .gradle.properties diff --git a/README.md b/README.md index d09d6f9..d07662f 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,14 @@ if (response.isSuccessful){ ``` Please note that in order to create API stub, you have to specify that the response should be converted by gson. And that's how you can easily use r2z. +## Documentation with Dokka + +To build Dokka documentattion, run: +``` +./gradlew dokkaHtml +``` +Docs will be added to *build/dokka/html* + ## How to run tests ### Unit tests diff --git a/build.gradle b/build.gradle index dd6aad4..6419668 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,7 @@ // Copyright © 2020 IBA Group, a.s. All rights reserved. Use of this source code is governed by Eclipse Public License – v 2.0 that can be found at: https://www.eclipse.org/legal/epl-2.0/ +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id 'java' @@ -42,13 +45,28 @@ test { useJUnitPlatform() } -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { +tasks.withType(KotlinCompile).all { kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() - languageVersion = org.jetbrains.kotlin.config.LanguageVersion.LATEST_STABLE.versionString + languageVersion = LanguageVersion.LATEST_STABLE.versionString } } +tasks.withType(DokkaTask.class) { + String dokkaBaseConfiguration = """ + { + "footerMessage": "(c) 2022 IBA Group", + "templatesDir": "${file("dokka/templates").getAbsolutePath().replace('\\', '/')}", + "customAssets": ["${file("dokka/assets/zowe-icon.png").getAbsolutePath().replace('\\', '/')}"], + "customStyleSheets": ["${file("dokka/assets/logo-styles.css").getAbsolutePath().replace('\\', '/')}"] + } + """ + pluginsMapConfiguration.set( + // fully qualified plugin name to json configuration + ["org.jetbrains.dokka.base.DokkaBase": dokkaBaseConfiguration] + ) +} + task sourceJar(type: Jar) { classifier "sources" from sourceSets.main.allSource diff --git a/dokka/assets/logo-styles.css b/dokka/assets/logo-styles.css new file mode 100644 index 0000000..0f410c7 --- /dev/null +++ b/dokka/assets/logo-styles.css @@ -0,0 +1,15 @@ +.library-name a { + position: relative; + margin-left: 55px; +} + +.library-name a::before { + content: ''; + background: url("../images/zowe-icon.png") center no-repeat; + background-size: contain; + position: absolute; + width: 50px; + height: 50px; + top: -18px; + left: -55px; +} diff --git a/dokka/assets/zowe-icon.png b/dokka/assets/zowe-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5b979db31df8d195a853b5c8669170c1eb351596 GIT binary patch literal 58679 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 + + diff --git a/dokka/templates/includes/header.ftl b/dokka/templates/includes/header.ftl new file mode 100644 index 0000000..87938fd --- /dev/null +++ b/dokka/templates/includes/header.ftl @@ -0,0 +1,25 @@ +<#import "source_set_selector.ftl" as source_set_selector> +<#macro display> + + diff --git a/dokka/templates/includes/page_metadata.ftl b/dokka/templates/includes/page_metadata.ftl new file mode 100644 index 0000000..67b4018 --- /dev/null +++ b/dokka/templates/includes/page_metadata.ftl @@ -0,0 +1,6 @@ +<#macro display> + ${pageName} - Zowe Client Kotlin SDK Documentation + <@template_cmd name="pathToRoot"> + + + diff --git a/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt b/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt index 0e2599d..e92b6b7 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/DataAPI.kt @@ -259,9 +259,12 @@ interface DataAPI { /** * Copy from - to - * SEQ -> SEQ - * PDS MEMBER -> SEQ (overwrites content) - * PDS MEMBER or MEMBERS -> PDS (adds or replaces) + * + * **SEQ** -> **SEQ** + * + * **PDS MEMBER** -> **SEQ** (overwrites content) + * + * **PDS MEMBER or MEMBERS** -> **PDS** (adds or replaces) */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/ds/{to-data-set-name}") @@ -275,10 +278,14 @@ interface DataAPI { /** * Volser for uncatalogued datasets + * * Copy from - to - * SEQ -> SEQ - * PDS MEMBER -> SEQ - * PDS MEMBER or MEMBERS -> PDS + * + * **SEQ** -> **SEQ** + * + * **PDS MEMBER** -> **SEQ** + * + * **PDS MEMBER** or **MEMBERS** -> **PDS** */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/ds/-({to-volser})/{to-data-set-name}") @@ -292,8 +299,9 @@ interface DataAPI { ): Call /** - * SEQ -> PDS MEMBER - * PDS MEMBER -> PDS MEMBER + * **SEQ** -> **PDS MEMBER** + * + * **PDS MEMBER** -> **PDS MEMBER** */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/ds/{to-data-set-name}({member-name})") @@ -307,9 +315,11 @@ interface DataAPI { ): Call /** - * Volser for uncatalogued datasets - * SEQ -> PDS MEMBER - * PDS MEMBER -> PDS MEMBER + * Volser for uncatalogued + * + * **SEQ** -> **PDS MEMBER** + * + * **PDS MEMBER** -> **PDS MEMBER** */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/ds/-({to-volser})/{to-data-set-name}({member-name})") @@ -324,7 +334,7 @@ interface DataAPI { ): Call /** - * USS FILE -> SEQ (truncates contents) + * **USS FILE** -> **SEQ** (truncates contents) */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/ds/{to-data-set-name}") @@ -337,7 +347,7 @@ interface DataAPI { ): Call /** - * USS FILE -> PDS MEMBER + * **USS FILE** -> **PDS MEMBER** */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/ds/{to-data-set-name}({member-name})") @@ -465,9 +475,11 @@ interface DataAPI { ): Call /** - * SEQ -> USS FILE - * PDS MEMBER -> USS FILE - * PDS -> USS DIR doesn't work + * **SEQ** -> **USS FILE** + * + * **PDS MEMBER** -> **USS FILE** + * + * **WARNING:** PDS -> USS DIR doesn't work */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/restfiles/fs/{filepath-name}") diff --git a/src/main/kotlin/eu/ibagroup/r2z/InfoAPI.kt b/src/main/kotlin/eu/ibagroup/r2z/InfoAPI.kt index 764c2d3..cac58b8 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/InfoAPI.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/InfoAPI.kt @@ -11,10 +11,10 @@ interface InfoAPI { /** * An API function to get an information of the system where z/OSMF is currently running - * @return a wrapped instance of InfoResponse + * @return a wrapped instance of [InfoResponse] */ @AvailableSince(ZVersion.ZOS_2_1) @GET("zosmf/info") fun getSystemInfo() : Call -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/SystemsApi.kt b/src/main/kotlin/eu/ibagroup/r2z/SystemsApi.kt index 77664bb..0d8f9c1 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/SystemsApi.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/SystemsApi.kt @@ -12,8 +12,8 @@ interface SystemsApi { /** * An API function to get all available systems defined to z/OSMF - * @param authToken - is a base 64 encoding representation of : - * @return a wrapped instance of SystemsResponse + * @param authToken is a base 64 encoding representation of *userid*:*password* + * @return a wrapped instance of [SystemsResponse] */ @AvailableSince(ZVersion.ZOS_2_1) @GET("zosmf/resttopology/systems") @@ -22,4 +22,3 @@ interface SystemsApi { ): Call } - diff --git a/src/main/kotlin/eu/ibagroup/r2z/SystemsResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/SystemsResponse.kt index ee90fe0..0464553 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/SystemsResponse.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/SystemsResponse.kt @@ -13,14 +13,14 @@ import eu.ibagroup.r2z.annotations.ZVersion data class SystemsResponse( /** - * @param items - a serialized list of SystemZOSInfo instances defined to z/OSMF + * @param items a serialized list of SystemZOSInfo instances defined to z/OSMF */ @SerializedName("items") @Expose val items: List = emptyList(), /** - * @param numRows - a serialized number of returned objects + * @param numRows a serialized number of returned objects */ @SerializedName("numRows") @Expose @@ -42,55 +42,55 @@ data class SystemsResponse( } /** - * A data class which represents a single SystemZOSInfo instance defined to z/OSMF + * A data class which represents a single [SystemZOSInfo] instance defined to z/OSMF */ data class SystemZOSInfo( /** - * @param systemNickName - a serialized unique name assigned to the system definition + * @param systemNickName a serialized unique name assigned to the system definition */ @SerializedName("systemNickName") @Expose val systemNickName: String = "null", /** - * @param systemName - a serialized name specified for the system on the SYSNAME parameter in the IEASYSxx parmlib member + * @param systemName a serialized name specified for the system on the SYSNAME parameter in the IEASYSxx parmlib member */ @SerializedName("systemName") @Expose val systemName: String = "null", /** - * @param sysplexName - a serialized name of the sysplex where the z/OS® system is a member + * @param sysplexName a serialized name of the sysplex where the z/OS® system is a member */ @SerializedName("sysplexName") @Expose val sysplexName: String = "null", /** - * @param groupNames - a serialized comma-separated list of the groups to which the system is assigned + * @param groupNames a serialized comma-separated list of the groups to which the system is assigned */ @SerializedName("groupNames") @Expose val groupNames: String = "null", /** - * @param url - a serialized URL used to access the z/OSMF instance that resides in the same sysplex as the system identified by the systemName attribute + * @param url a serialized URL used to access the z/OSMF instance that resides in the same sysplex as the system identified by the systemName attribute */ @SerializedName("url") @Expose val url: String = "null", /** - * @param zosVR - a serialized version and release of the z/OS image installed on the system + * @param zosVR a serialized version and release of the z/OS image installed on the system */ @SerializedName("zosVR") @Expose val zosVR: String = "null", /** - * @param jesMemberName - a serialized JES2 multi-access spool (MAS) member name or JES3 complex member name + * @param jesMemberName a serialized JES2 multi-access spool (MAS) member name or JES3 complex member name * that is assigned to the primary job entry subsystem (JES) that is running on the system */ @SerializedName("jesMemberName") @@ -98,28 +98,28 @@ data class SystemZOSInfo( val jesMemberName: String = "null", /** - * @param jesType - a serialized type for the primary job entry subsystem running on the system. The type is either JES2 or JES3 + * @param jesType a serialized type for the primary job entry subsystem running on the system. The type is either JES2 or JES3 */ @SerializedName("jesType") @Expose val jesType: String = "null", /** - * @param cpcName - a serialized name specified for the central processor complex (CPC) at the support element (SE) of that processor complex + * @param cpcName a serialized name specified for the central processor complex (CPC) at the support element (SE) of that processor complex */ @SerializedName("cpcName") @Expose val cpcName: String = "null", /** - * @param cpcSerial - a serialized serial number of the CPC + * @param cpcSerial a serialized serial number of the CPC */ @SerializedName("spcSerial") @Expose val cpcSerial: String = "null", /** - * @param httpProxyName - a serialized name of the HTTP proxy definition that specifies the settings required to access the system + * @param httpProxyName a serialized name of the HTTP proxy definition that specifies the settings required to access the system * through an HTTP or SOCKS proxy server */ @SerializedName("httpProxyName") @@ -127,7 +127,7 @@ data class SystemZOSInfo( val httpProxyName: String = "null", /** - * @param ftpDestinationName - a serialized name of the server definition that specifies the settings required to access the FTP or SFTP server + * @param ftpDestinationName a serialized name of the server definition that specifies the settings required to access the FTP or SFTP server * that is running on the system */ @SerializedName("ftpDestinationName") diff --git a/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt b/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt index c647dc8..c044d9c 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/TsoApi.kt @@ -11,19 +11,19 @@ interface TsoApi { /** * An API function to start a new TSO address space (session) - * @param authorizationToken - is a base 64 encoding representation of : - * @param contentType - content type of the request - * @param proc - a procedure name - * @param chset - a charset which should be used - * @param cpage - a codepage which should be used - * @param rows - a number of rows available for a new session - * @param cols - a number of columns available for a new session - * @param acct - an account number - * @param ugrp - an user group - * @param rsize - a region size - * @param appsessid - an application session identifier - * @param system - a system name - * @return a wrapped instance of TsoResponse + * @param authorizationToken is a base 64 encoding representation of : + * @param contentType content type of the request + * @param proc a procedure name + * @param chset a charset which should be used + * @param cpage a codepage which should be used + * @param rows a number of rows available for a new session + * @param cols a number of columns available for a new session + * @param acct an account number + * @param ugrp an user group + * @param rsize a region size + * @param appsessid an application session identifier + * @param system a system name + * @return a wrapped instance of [TsoResponse] */ @AvailableSince(ZVersion.ZOS_2_1) @POST("/zosmf/tsoApp/tso") @@ -44,12 +44,12 @@ interface TsoApi { /** * An API function to send a message to TSO address space - * @param authorizationToken - is a base 64 encoding representation of : - * @param contentType - content type of the request - * @param body - wrapped instance of TsoData class - * @param servletKey - Unique identifier for the servlet entry - * @param readReply - is an optional parameter that indicates whether the service should send the message and immediately check for a response (default) or just send the message. - * @return a wrapped instance of TsoResponse + * @param authorizationToken is a base 64 encoding representation of *userid*:*password* + * @param contentType content type of the request + * @param body wrapped instance of TsoData class + * @param servletKey unique identifier for the servlet entry + * @param readReply is an optional parameter that indicates whether the service should send the message and immediately check for a response (default) or just send the message. + * @return a wrapped instance of [TsoResponse] */ @AvailableSince(ZVersion.ZOS_2_1) @PUT("/zosmf/tsoApp/tso/{servletKey}") @@ -63,10 +63,10 @@ interface TsoApi { /** * An API function to receive messages from TSO address space - * @param authorizationToken - is a base 64 encoding representation of : - * @param contentType - content type of the request - * @param servletKey - Unique identifier for the servlet entry - * @return a wrapped instance of TsoResponse + * @param authorizationToken is a base 64 encoding representation of *userid*:*password* + * @param contentType content type of the request + * @param servletKey unique identifier for the servlet entry + * @return a wrapped instance of [TsoResponse] */ @AvailableSince(ZVersion.ZOS_2_1) @GET("/zosmf/tsoApp/tso/{servletKey}") @@ -78,11 +78,11 @@ interface TsoApi { /** * An API function to close the TSO session - * @param authorizationToken - is a base 64 encoding representation of : - * @param contentType - content type of the request - * @param servletKey - Unique identifier for the servlet entry - * @param tsoForceCancel - is an optional parameter that indicates whether to use the CANCEL or LOGOFF command to end the TSO/E address space - * @return a wrapped instance of TsoResponse + * @param authorizationToken is a base 64 encoding representation of *userid*:*password* + * @param contentType content type of the request + * @param servletKey unique identifier for the servlet entry + * @param tsoForceCancel is an optional parameter that indicates whether to use the CANCEL or LOGOFF command to end the TSO/E address space + * @return a wrapped instance of [TsoResponse] */ @AvailableSince(ZVersion.ZOS_2_1) @DELETE("/zosmf/tsoApp/tso/{servletKey}") @@ -93,4 +93,4 @@ interface TsoApi { @Query("tsoforcecancel") tsoForceCancel: Boolean? = null, ): Call -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/core/ZOSConnection.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/core/ZOSConnection.kt index d466993..a3fcb9b 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/core/ZOSConnection.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/core/ZOSConnection.kt @@ -6,7 +6,7 @@ package eu.ibagroup.r2z.zowe.client.sdk.core * z/OS Connection information placeholder * * @author Frank Giordano - * @version 1.0 + * @author Uladzislau Kalesnikau */ class ZOSConnection( /** @@ -22,7 +22,7 @@ class ZOSConnection( */ val user: String, /** - * machine host username's password with access to backend z/OS instance + * machine host username\'s password with access to backend z/OS instance */ val password: String, /** diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt index 5387135..a2968c8 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/ConsoleResponse.kt @@ -21,7 +21,7 @@ data class ConsoleResponse( val zosmfResponse: IssueResponse? = null, /** - * If an error occurs, returns the ImperativeError, which contains case error. + * If an error occurs, returns the [ImperativeError], which contains case error. */ val failureResponse: String? = null, @@ -46,4 +46,4 @@ data class ConsoleResponse( */ val cmdResponseUrl: String? = null -) \ No newline at end of file +) diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt index 7ad228d..b2faa52 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosconsole/IssueCommand.kt @@ -26,8 +26,8 @@ class IssueCommand( * Issue an MVS console command, returns "raw" z/OSMF response * * @param consoleName string name of the mvs console that is used to issue the command - * @param commandParams synchronous console issue parameters, see [IssueRequestBody] object - * @return command response on resolve, see [IssueResponse] object + * @param commandParams [IssueRequestBody] synchronous console issue parameters + * @return [IssueResponse] command response on resolve * @throws Exception processing error */ fun issueCommon(consoleName: String, commandParams: IssueRequestBody): IssueResponse { @@ -56,8 +56,8 @@ class IssueCommand( /** * Issue an MVS console command in default console, returns "raw" z/OSMF response * - * @param commandParams synchronous console issue parameters, see [IssueRequestBody] object - * @return command response on resolve, see [IssueResponse] object + * @param commandParams [IssueRequestBody] synchronous console issue parameters + * @return [IssueResponse] command response on resolve * @throws Exception processing error */ fun issueDefConsoleCommon(commandParams: IssueRequestBody): IssueResponse { @@ -69,8 +69,8 @@ class IssueCommand( * immediately after the command is issued. However, after (according to the z/OSMF REST API documentation) * approximately 3 seconds the response will be returned. * - * @param params console issue parameters, see [IssueRequestBody] object - * @return command response on resolve, see ConsoleResponse object + * @param params [IssueRequestBody] console issue parameters + * @return [ConsoleResponse] on resolve * @throws Exception processing error */ fun issue(params: IssueRequestBody): ConsoleResponse { @@ -99,11 +99,11 @@ class IssueCommand( * Simple issue console command method. Does not accept parameters, so all defaults on the z/OSMF API are taken. * * @param command string command to issue - * @return command response on resolve, see [ConsoleResponse] object + * @return [ConsoleResponse] object on resolve * @throws Exception processing error */ fun issueSimple(command: String): ConsoleResponse { return issue(IssueRequestBody(cmd = command)) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnDownload.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnDownload.kt index 25f66b7..528e46c 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnDownload.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnDownload.kt @@ -14,7 +14,7 @@ import retrofit2.Response import java.io.InputStream /** - * ZosDsnDownload class that provides download DataSet function + * [ZosDsnDownload] class that provides download DataSet function */ class ZosDsnDownload ( var connection: ZOSConnection, @@ -32,7 +32,7 @@ class ZosDsnDownload ( * * @param datasetName name of a sequential dataset e.g. DATASET.SEQ.DATA * or a dataset member e.g. DATASET.LIB(MEMBER)) - * @param params download params parameters, see DownloadParams + * @param params [DownloadParams] * @return a content stream * @throws Exception error processing request */ @@ -59,4 +59,4 @@ class ZosDsnDownload ( return (response?.body() as ResponseBody).byteStream() ?: throw Exception("No stream returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnList.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnList.kt index dc4c8b5..9f4eec8 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnList.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosfiles/ZosDsnList.kt @@ -12,7 +12,7 @@ import retrofit2.Response /** * ZosDsnList class that provides Dataset member list function * - * @property connection connection information, see ZOSConnection object + * @property conenction [ZOSConnection] object connection information * @property httpClient okHttpClient */ class ZosDsnList( @@ -30,7 +30,7 @@ class ZosDsnList( * Get a list of Dataset names * * @param dataSetName name of a dataset - * @param params list parameters, see ListParams object + * @param params [ListParams] object * @return A String list of Dataset names * @throws Exception error processing request */ @@ -57,7 +57,7 @@ class ZosDsnList( * Get a list of members from a Dataset * * @param datasetName name of a dataset - * @param listParams list parameters, see ListParams object + * @param listParams [ListParams] object * @return list of member names * @throws Exception error processing request */ diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/CancelJobs.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/CancelJobs.kt index 46e0843..dc7e715 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/CancelJobs.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/CancelJobs.kt @@ -51,7 +51,7 @@ class CancelJobs( /** * Cancel a job that resides in a z/OS data set. * - * @param params cancel job parameters, see ModifyJobParams object + * @param params [ModifyJobParams] cancel job parameters * @return job document with details about the canceled job * @throws Exception error canceling */ @@ -70,4 +70,4 @@ class CancelJobs( } return response?.body() as CancelJobRequest? ?: throw Exception("No body returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/DeleteJobs.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/DeleteJobs.kt index 5c79ba0..6050aa2 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/DeleteJobs.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/DeleteJobs.kt @@ -51,7 +51,7 @@ class DeleteJobs( /** * Delete a job that resides in a z/OS data set. * - * @param params delete job parameters, see ModifyJobParams object + * @param params [ModifyJobParams] delete job parameters * @return job document with details about the deleted job * @throws Exception error on deleting */ @@ -72,4 +72,4 @@ class DeleteJobs( } return response?.body() as CancelJobPurgeOutRequest? ?: throw Exception("No body returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt index ed4e0e4..6e5ffc0 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt @@ -37,7 +37,7 @@ class GetJobs( /** * Get jobs that match a job name by prefix. Defaults to job(s) owned by the user ID in the session. * - * @param prefix job name prefix for which to list jobs. Supports wildcard e.g. JOBNM* + * @param prefix job name prefix for which to list jobs. Supports wildcard e.g. JOBNM\* * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ @@ -52,7 +52,7 @@ class GetJobs( * Get jobs that are owned by a certain user or pattern of users. * * @param owner owner for which to get jobs. Supports wildcard e.g. - * IBMU* returns jobs owned by all users whose ID beings with "IBMU" + * IBMU\* returns jobs owned by all users whose ID beings with "IBMU" * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ @@ -67,9 +67,9 @@ class GetJobs( * Get a list of jobs that match an owner and prefix. * * @param owner owner for which to get jobs. Supports wildcard e.g. - * IBMU* returns jobs owned by all users whose ID beings with "IBMU" + * IBMU\* returns jobs owned by all users whose ID beings with "IBMU" * @param prefix prefix for which to get jobs. Supports wildcard e.g. - * JOBNM* returns jobs with names starting with "JOBNM" + * JOBNM\* returns jobs with names starting with "JOBNM" * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ @@ -105,7 +105,7 @@ class GetJobs( /** * Get jobs filtered by owner and prefix. * - * @param params get job parameters, see GetJobParams object + * @param params [GetJobParams] object * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ @@ -188,7 +188,7 @@ class GetJobs( /** * Get the status and other details (e.g. owner, return code) for a job. * - * @param params common job parameters, see CommonJobParams object + * @param params [CommonJobParams] object * @return job document (matching job) * @throws Exception error getting job status */ @@ -223,7 +223,7 @@ class GetJobs( * Get a list of all job spool files for a job. * * @param params common job parameters, see CommonJobParams object - * @return list of SpoolFile objects + * @return list of [SpoolFile] objects * @throws Exception error on getting spool files info */ @Suppress("UNCHECKED_CAST") @@ -248,7 +248,7 @@ class GetJobs( * other APIs such as SubmitJobs. * * @param job job for which you would like to get a list of job spool files - * @return list of SpoolFile objects + * @return list of [SpoolFile] objects * @throws Exception error on getting spool files info */ fun getSpoolFilesForJob(job: Job): List { @@ -264,7 +264,7 @@ class GetJobs( /** * Get the JCL that was used to submit a job. * - * @param params common job parameters, see CommonJobParams object + * @param params [CommonJobParams] object * @return JCL content * @throws Exception error on getting jcl content */ @@ -366,4 +366,4 @@ class GetJobs( } return String(response?.body() as ByteArray? ?: throw Exception("No body returned")) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/MonitorJobs.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/MonitorJobs.kt index 1e0b414..cbca20c 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/MonitorJobs.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/MonitorJobs.kt @@ -34,9 +34,7 @@ class MonitorJobs( /** * Given a Job document (has jobname/jobid), waits for the status of the job to be "OUTPUT". This API will poll for * the OUTPUT status once every 3 seconds indefinitely. If the polling interval/duration is NOT sufficient, use - * "waitForStatusCommon" to adjust. - *

- * See JSDoc for "waitForStatusCommon" for full details on polling and other logic. + * [waitForStatusCommon] to adjust. * * @param job document of the z/OS job to wait for (see z/OSMF Jobs APIs for details) * @return job document @@ -52,10 +50,7 @@ class MonitorJobs( /** * Given the jobname/jobid, waits for the status of the job to be "OUTPUT". This API will poll for the OUTPUT status * once every 3 seconds indefinitely. If the polling interval/duration is NOT sufficient, use - * "waitForStatusCommon" to adjust. - * - * - * See JavaDoc for "waitForStatusCommon" for full details on polling and other logic. + * [waitForStatusCommon] to adjust. * * @param jobName the z/OS jobname of the job to wait for output status (see z/OSMF Jobs APIs for details) * @param jobId the z/OS jobid of the job to wait for output status (see z/OSMF Jobs APIS for details) @@ -78,7 +73,7 @@ class MonitorJobs( * than the current status of the job, then the method returns immediately (since the job will never enter the * requested status) with the current status of the job. * - * @param params monitor jobs parameters, see MonitorJobWaitForParams object + * @param params [MonitorJobWaitForParams] object * @return job document * @throws Exception error processing wait check request */ @@ -98,7 +93,7 @@ class MonitorJobs( /** * "Polls" (sets timeouts and continuously checks) for the status of the job to match the desired status. * - * @param params monitor jobs params, see MonitorJobWaitForParams + * @param params [MonitorJobWaitForParams] object * @return job document * @throws Exception error processing poll check request */ @@ -128,7 +123,7 @@ class MonitorJobs( /** * Checks the status of the job for the expected status (OR that the job has progressed passed the expected status). * - * @param params monitor jobs params, see MonitorJobWaitForParams + * @param params [MonitorJobWaitForParams] object * @return boolean true when the job status is obtained * @throws Exception error processing check request */ @@ -168,7 +163,7 @@ class MonitorJobs( /** * Checks if the given message is within the job output within line limit. * - * @param params monitor jobs params, see MonitorJobWaitForParams + * @param params [MonitorJobWaitForParams] object * @param message message string * @return boolean message found status * @throws Exception error processing check request @@ -201,7 +196,7 @@ class MonitorJobs( /** * "Polls" (sets timeouts and continuously checks) for the given message within the job output. * - * @param params monitor jobs params, see MonitorJobWaitForParams + * @param params [MonitorJobWaitForParams] object * @param message message string * @return boolean message found status * @throws Exception error processing poll check request @@ -230,7 +225,7 @@ class MonitorJobs( /** * Determines if a given job is in a running state or not. * - * @param params monitor jobs params, see MonitorJobWaitForParams + * @param params [MonitorJobWaitForParams] object * @return true if in running state * @throws Exception error processing running status check */ @@ -246,7 +241,7 @@ class MonitorJobs( /** * Given jobname/jobid, checks for the desired message continuously (based on the interval and attempts specified). * - * @param params monitor jobs parameters, see MonitorJobWaitForParams object + * @param params [MonitorJobWaitForParams] object * @param message message string * @return job document * @throws Exception error processing wait check request @@ -268,7 +263,7 @@ class MonitorJobs( /** * Given a Job document (has jobname/jobid), waits for the given message from the job. This API will poll for * the given message once every 3 seconds for at least 1000 times. If the polling interval/duration is NOT - * sufficient, use "waitForMessageCommon" method to adjust. + * sufficient, use [waitForMessageCommon] method to adjust. * * @param job document of the z/OS job to wait for (see z/OSMF Jobs APIs for details) * @param message message string @@ -290,9 +285,7 @@ class MonitorJobs( /** * Given a Job document (has jobname/jobid), waits for the given status of the job. This API will poll for * the given status once every 3 seconds for at least 1000 times. If the polling interval/duration is NOT - * sufficient, use "waitForStatusCommon" method to adjust. - *

- * See JavaDoc for "waitForStatusCommon" for full details on polling and other logic. + * sufficient, use [waitForStatusCommon] method to adjust. * * @param job document of the z/OS job to wait for (see z/OSMF Jobs APIs for details) * @param statusType status type, see JobStatus.Type object @@ -309,7 +302,7 @@ class MonitorJobs( /** * Given the jobname/jobid, waits for the given status of the job. This API will poll for the given status once * every 3 seconds for at least 1000 times. If the polling interval/duration is NOT sufficient, use - * "waitForStatusCommon" method to adjust. + * [waitForStatusCommon] method to adjust. * * @param jobName the z/OS jobname of the job to wait for output status (see z/OSMF Jobs APIs for details) * @param jobId the z/OS jobid of the job to wait for output status (see z/OSMF Jobs APIS for details) @@ -328,4 +321,4 @@ class MonitorJobs( )) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/SubmitJobs.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/SubmitJobs.kt index e501b43..13472b5 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/SubmitJobs.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/SubmitJobs.kt @@ -27,7 +27,7 @@ class SubmitJobs( /** * Submit a job that resides in a z/OS data set. * - * @param jobDataSet job Dataset to be translated into SubmitJobParams object + * @param jobDataSet job Dataset to be translated into [SubmitJobParams] object * @return job document with details about the submitted job * @throws Exception error on submitting */ @@ -38,7 +38,7 @@ class SubmitJobs( /** * Submit a job that resides in a z/OS data set. * - * @param params submit job parameters, see SubmitJobParams object + * @param params [SubmitJobParams] object * @return job document with details about the submitted job * @throws Exception error on submitting */ @@ -75,7 +75,7 @@ class SubmitJobs( /** * Submit a JCL string to run * - * @param params submit jcl parameters, see SubmitJclParams object + * @param params [SubmitJclParams] object * @return job document with details about the submitted job * @throws Exception error on submitting */ @@ -98,4 +98,4 @@ class SubmitJobs( return response?.body() as SubmitJobRequest? ?: throw Exception("No body returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt index 4134174..6727b25 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/SendTso.kt @@ -28,7 +28,7 @@ class SendTso( * Create Response * * @param responses responses from CollectedResponses object - * @return SendResponse, see [SendResponse] + * @return [SendResponse] */ @Throws(Exception::class) private fun createResponse(responses: CollectedResponses): SendResponse { @@ -43,7 +43,7 @@ class SendTso( * Collects responses from address space until it reaches prompt * * @param tsoResponse object from first Tso response from witch responses are needed, see [TsoResponse] - * @return CollectedResponses response object, see [CollectedResponses] + * @return [CollectedResponses] response object * @throws Exception error executing command */ @Throws(Exception::class) @@ -87,7 +87,7 @@ class SendTso( * Retrieve tso http request response * * @param servletKey key of tso address space - * @return z/OSMF tso response, see [TsoResponse] + * @return z/OSMF [TsoResponse] * @throws Exception error executing command */ @Throws(Exception::class) @@ -110,7 +110,7 @@ class SendTso( * * @param command to send to the TSO address space. * @param servletKey returned from a successful start - * @return response object, see [SendResponse] + * @return [SendResponse] object * @throws Exception error executing command */ @Throws(Exception::class) @@ -133,8 +133,8 @@ class SendTso( /** * API method to send data to already started TSO address space * - * @param commandParams object with required parameters, see [SendTsoParams] object - * @return response object, see [TsoResponse] + * @param commandParams [SendTsoParams] object with required parameters + * @return [TsoResponse] object * @throws Exception error executing command */ @Throws(Exception::class) @@ -165,4 +165,4 @@ class SendTso( return response?.body() as TsoResponse? ?: throw Exception("No body returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt index 5a5ae32..2e2ef6b 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StartTso.kt @@ -27,8 +27,8 @@ class StartTso( * Start TSO address space with provided parameters. * * @param accountNumber this key of StartTsoParams required, because it cannot be default. - * @param params optional object with required parameters, see [StartTsoParams] - * @return command response on resolve, see [StartStopResponses] + * @param params optional [StartTsoParams] object with required parameters + * @return [StartStopResponses] command response on resolve * @throws Exception error executing command */ @Throws(Exception::class) @@ -61,8 +61,8 @@ class StartTso( /** * Start TSO address space with provided parameters * - * @param commandParams object with required parameters, see [StartTsoParams] - * @return z/OSMF response object, see [TsoResponse] + * @param commandParams [StartTsoParams] object with required parameters + * @return [TsoResponse] z/OSMF response object * @throws Exception error executing command */ @Throws(Exception::class) @@ -86,4 +86,4 @@ class StartTso( } return response?.body() as TsoResponse? ?: throw Exception("No body returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt index 5057f94..c21c2b3 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zostso/StopTso.kt @@ -30,7 +30,7 @@ class StopTso( * Stop TSO address space and populates response with [StartStopResponse] * * @param servletKey unique servlet entry identifier - * @return start stop response, see [StartStopResponse] object + * @return [StartStopResponse] object * @throws Exception error on TSO sto command */ fun stop(servletKey: String): StartStopResponse { @@ -56,8 +56,8 @@ class StopTso( /** * Sends REST call to z/OSMF for stopping active TSO address space * - * @param commandParams command parameters, see [StopTsoParams] - * @return z/OSMF response object, see [TsoResponse] object + * @param commandParams [StopTsoParams] command parameters + * @return [TsoResponse] z/OSMF response object * @throws Exception error on TSO sto command */ fun stopCommon(commandParams: StopTsoParams): TsoResponse { @@ -79,4 +79,4 @@ class StopTso( return response?.body() as TsoResponse? ?: throw Exception("No body returned") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt index 0f6051b..5b94d70 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosuss/ZosUssFile.kt @@ -43,7 +43,7 @@ class ZosUssFile ( * Creates a new file or directory with specified parameters * * @param filePath path of the file or directory (e.g. u/jiahj/text.txt) - * @param params create USS file parameters, see CreateUssFile class + * @param params [CreateUssFile] parameters * @return http response object * @throws Exception error processing request */ diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt index aa8e861..1e7f5ee 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/config/ZoweConfig.kt @@ -39,7 +39,7 @@ class ZoweConfig( /** * Builder class for setting the sequence of profiles to search property by name. - * @param propName - property name to search. + * @param propName property name to search. */ inner class PropertyBuilder(val propName: String) { var profilesToSearchProp = mutableListOf() @@ -116,10 +116,10 @@ class ZoweConfig( /** * Searches for a property with creating profiles sequence to search. * @see search - * @param propName - property name to search. - * @param block - extension function for PropertyBuilder class. This parameter is needed for - * creating a sequence of profiles to search by invoking corresponding methods - * in the right order. + * @param propName property name to search. + * @param block extension function for PropertyBuilder class. This parameter is needed for + * creating a sequence of profiles to search by invoking corresponding methods + * in the right order. * @return Property value. */ fun searchProperty (propName: String, block: PropertyBuilder.() -> Unit): Any? { @@ -129,10 +129,10 @@ class ZoweConfig( /** * Searches for a property and updates it in found profile with creating profiles sequence to search. * @see set - * @param propName - property name to search. - * @param block - extension function for PropertyBuilder class. This parameter is needed for - * creating a sequence of profiles to search by invoking corresponding methods - * in the right order. + * @param propName property name to search. + * @param block extension function for PropertyBuilder class. This parameter is needed for + * creating a sequence of profiles to search by invoking corresponding methods + * in the right order. * @return Nothing. */ fun updateProperty (propName: String, propValue: Any?, block: PropertyBuilder.() -> Unit) { @@ -143,7 +143,7 @@ class ZoweConfig( * Extracts and decodes config object of all files from credential storage. * @see KeytarWrapper * @see DefaultKeytarWrapper - * @param keytar - instance of KeytarWrapper class. This param is needed for accessing credential storage. + * @param keytar instance of [KeytarWrapper]. This param is needed for accessing credential storage. * @return Map where key is config file path and value is map of secure properties. * For example: * { @@ -173,8 +173,8 @@ class ZoweConfig( /** * Extracts secure properties from secure store by zowe config file path in current instance. * @see readZoweCredentialsFromStorage - * @param filePath - path of zowe.config.json file. Secure props will be extracted by this parameter. - * @param keytar - instance of KeytarWrapper class. This param is needed for accessing credential storage. + * @param filePath path of zowe.config.json file. Secure props will be extracted by this parameter. + * @param keytar instance of [KeytarWrapper]. This param is needed for accessing credential storage. * @return Nothing. */ fun extractSecureProperties (filePath: String, keytar: KeytarWrapper = DefaultKeytarWrapper()) { @@ -195,9 +195,9 @@ class ZoweConfig( /** * Updates secure object for provided file in credential object and save these changes to credential storage. * @see readZoweCredentialsFromStorage - * @param filePath - path of zowe.config.json file. Secure props will be saved - * inside this property of connection object. - * @param keytar - instance of KeytarWrapper class. This param is needed for accessing credential storage. + * @param filePath path of zowe.config.json file. Secure props will be saved + * inside this property of connection object. + * @param keytar instance of [KeytarWrapper]. This param is needed for accessing credential storage. * @return Nothing. */ fun saveSecureProperties (filePath: String, keytar: KeytarWrapper = DefaultKeytarWrapper()) { @@ -227,9 +227,9 @@ class ZoweConfig( /** * Extracts secure properties from secure store by zowe config file path in current instance. * @see readZoweCredentialsFromStorage - * @param filePathTokens - path of zowe.config.json file splitted by delimiter. - * Secure props will be extracted by this parameter. - * @param keytar - instance of KeytarWrapper class. This param is needed for accessing credential storage. + * @param filePathTokens path of zowe.config.json file splitted by delimiter. + * Secure props will be extracted by this parameter. + * @param keytar instance of [KeytarWrapper]. This param is needed for accessing credential storage. * @return Nothing. */ fun extractSecureProperties (filePathTokens: Array, keytar: KeytarWrapper = DefaultKeytarWrapper()) { @@ -239,9 +239,9 @@ class ZoweConfig( /** * Updates secure object for provided file in credential object and save these changes to credential storage. * @see readZoweCredentialsFromStorage - * @param filePath - path of zowe.config.json file splitted by delimiter. - * Secure props will be saved inside this property of connection object. - * @param keytar - instance of KeytarWrapper class. This param is needed for accessing credential storage. + * @param filePath path of zowe.config.json file splitted by delimiter. + * Secure props will be saved inside this property of connection object. + * @param keytar instance of [KeytarWrapper]. This param is needed for accessing credential storage. * @return Nothing. */ fun saveSecureProperties (filePathTokens: Array, keytar: KeytarWrapper = DefaultKeytarWrapper()) { @@ -249,7 +249,7 @@ class ZoweConfig( } /** - * Deserializes current instance of ZoweConfig to json string without secure properties. + * Deserializes current [ZoweConfig] instance to JSON string without secure properties. * @return String with deserialized object. */ fun toJson (): String { @@ -264,9 +264,8 @@ class ZoweConfig( } /** - * Creates ZOSConnection based on zowe config or throws exception if data is not correct. - * @return ZOSConnection instance - * @see ZOSConnection + * Creates [ZOSConnection] based on zowe config or throws exception if data is not correct. + * @return [ZOSConnection] instance */ fun toZosConnection(): ZOSConnection { if (host?.isEmpty() != false || port == null || user?.isEmpty() != false || password == null || protocol.isEmpty()){ @@ -314,8 +313,8 @@ class ZoweConfig( /** * Searches profile by its path. For example if profile has path "gr1.example" then it will search * profile "example" in "gr1" group. - * @param searchPath - path to search profile - * @return found profile or null if searchPath is not valid or no one profile exists by this path + * @param searchPath path to search profile + * @return found profile or null if *searchPath* is not valid or no one profile exists by this path */ fun profile(searchPath: String?): ZoweConfigProfile? { searchPath ?: return null diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/config/utils.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/config/utils.kt index 8c7cfa0..3f2d050 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/config/utils.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/config/utils.kt @@ -79,9 +79,9 @@ private fun formProfiles (profiles: Map?) { } /** - * Parses json string to ZoweConfig object model. - * @param configString - json string with zowe config. - * @return ZoweConfig object model. + * Parses JSON string to [ZoweConfig] object model. + * @param configString JSON string with zowe config. + * @return [ZoweConfig] object model. */ fun parseConfigJson(configString: String): ZoweConfig { val zoweConfig = Gson().fromJson(configString, ZoweConfig::class.java) @@ -105,32 +105,32 @@ fun parseConfigJson (inputStream: InputStream): ZoweConfig = parseConfigJson(Str interface KeytarWrapper { /** * Returns a password by service name and account. - * @param service - service name. - * @param account - account name. + * @param service service name. + * @param account account name. * @return extracted password. */ fun getPassword(service: String, account: String): String /** * Updates or creates password for account of service. - * @param service - service name. - * @param account - account name + * @param service service name. + * @param account account name * @return Nothing. */ fun setPassword(service: String, account: String, password: String) /** * Removes credentials for account in service. If account in service is single than it removes a service. - * @param service - service name. - * @param account - account name. + * @param service service name. + * @param account account name. * @return true if success and false otherwise. */ fun deletePassword(service: String, account: String): Boolean /** * Extracts all credentials for service. - * @param service - service name. - * @return Map where key is account name in service and value is password for this account. + * @param service service name. + * @return [Map] where key is account name in service and value is password for this account. */ fun getCredentials(service: String): Map } From 99b371332d82fc1955c8063ad881a1427125ab13 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Tue, 14 Feb 2023 14:29:18 +0100 Subject: [PATCH 21/23] Some changes for docs --- .../eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt index 6e5ffc0..ada490b 100644 --- a/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt +++ b/src/main/kotlin/eu/ibagroup/r2z/zowe/client/sdk/zosjobs/GetJobs.kt @@ -37,7 +37,7 @@ class GetJobs( /** * Get jobs that match a job name by prefix. Defaults to job(s) owned by the user ID in the session. * - * @param prefix job name prefix for which to list jobs. Supports wildcard e.g. JOBNM\* + * @param prefix job name prefix for which to list jobs. Supports wildcard e.g. JOBNM* * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ @@ -52,7 +52,7 @@ class GetJobs( * Get jobs that are owned by a certain user or pattern of users. * * @param owner owner for which to get jobs. Supports wildcard e.g. - * IBMU\* returns jobs owned by all users whose ID beings with "IBMU" + * IBMU* returns jobs owned by all users whose ID beings with "IBMU" * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ @@ -67,9 +67,9 @@ class GetJobs( * Get a list of jobs that match an owner and prefix. * * @param owner owner for which to get jobs. Supports wildcard e.g. - * IBMU\* returns jobs owned by all users whose ID beings with "IBMU" + * IBMU* returns jobs owned by all users whose ID beings with "IBMU" * @param prefix prefix for which to get jobs. Supports wildcard e.g. - * JOBNM\* returns jobs with names starting with "JOBNM" + * JOBNM* returns jobs with names starting with "JOBNM" * @return list of job objects (matching jobs) * @throws Exception error on getting a list of jobs */ From 1c00eb698ea471469f499829845e52806544e911 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Wed, 22 Feb 2023 15:27:36 +0100 Subject: [PATCH 22/23] IJMP-894 Added deprecation warning and notes + CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2b87adc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# R2Z Changelog + +All notable changes to the R2Z will be documented in this file. + +## `1.3.0 (2023-02-22)` + +* Deprecation: This library is deprecated since 1.3.0 + + +* Feature: GitHub issue #3: Zowe Kotlin SDK: Java-like Kotlin API ([56788c1a](https://github.com/zowe/zowe-explorer-intellij/commit/56788c1a)) +* Feature: Implement ZosUssFile (copying methods - from uss to dsn and to uss folder) ([2bb0b257](https://github.com/zowe/zowe-explorer-intellij/commit/2bb0b257)) + + +* Bugfix: GitHub issue #9: Error Creating Connection ([bbc16d72](https://github.com/zowe/zowe-client-kotlin-sdk/commit/bbc16d72)) diff --git a/README.md b/README.md index d07662f..744be64 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# WARNING: THIS REPOSITORY IS MARKED AS 'DEPRECATED'. CONSIDER SWITCHING TO ZOWE CLIENT KOTLIN SDK WITH THE SIMILAR APPROACH TO WORK WITH Z/OSMF REST API ([link](https://github.com/zowe/zowe-client-kotlin-sdk)) + ## zOSMF Retrofit Library This library covert zOSMF Rest API with kotlin object oriented code using Retrofit. r2z will allow you to send http requests to your zOSMF. From 73b10e6a7a2211cb7ad177354ab4a224738c0f89 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Wed, 22 Feb 2023 15:28:47 +0100 Subject: [PATCH 23/23] IJMP-894 Added deprecation note --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 744be64..dc2418b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# WARNING: THIS REPOSITORY IS MARKED AS 'DEPRECATED'. CONSIDER SWITCHING TO ZOWE CLIENT KOTLIN SDK WITH THE SIMILAR APPROACH TO WORK WITH Z/OSMF REST API ([link](https://github.com/zowe/zowe-client-kotlin-sdk)) +# WARNING: THIS REPOSITORY IS MARKED AS 'DEPRECATED' AND WON'T BE MAINTAINED. CONSIDER SWITCHING TO ZOWE CLIENT KOTLIN SDK WITH THE SIMILAR APPROACH TO WORK WITH Z/OSMF REST API ([link](https://github.com/zowe/zowe-client-kotlin-sdk)) ## zOSMF Retrofit Library This library covert zOSMF Rest API with kotlin object oriented code using Retrofit. r2z will allow you to send http requests to your zOSMF.