diff --git a/build.gradle b/build.gradle index 0a39705..ee9c558 100644 --- a/build.gradle +++ b/build.gradle @@ -265,6 +265,8 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { } test { + systemProperty("LOG_HTTP", project.findProperty("logHttp") ?: "false") + systemProperty("LOG_INTERNAL", project.findProperty("logInternal") ?: "false") useTestNG() afterTest { desc, result -> diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/AbstractClientTest.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/AbstractClientTest.kt index a1457b1..1b9f96e 100644 --- a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/AbstractClientTest.kt +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/AbstractClientTest.kt @@ -19,10 +19,12 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout +import okhttp3.OkHttpClient import org.eclipse.hara.ddiclient.api.* -import org.eclipse.hara.ddiclient.integrationtest.utils.Action -import org.eclipse.hara.ddiclient.integrationtest.utils.ManagementClient +import org.eclipse.hara.ddiclient.integrationtest.api.management.Action +import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementClient import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils +import org.eclipse.hara.ddiclient.integrationtest.utils.addOkhttpLogger import org.joda.time.Duration import org.testng.Assert import java.io.File @@ -70,7 +72,8 @@ abstract class AbstractClientTest { deploymentPermitProvider, listOf(eventListener, *messageListeners.toTypedArray()), listOf(updater), - downloadBehavior + downloadBehavior, + httpBuilder = OkHttpClient.Builder().addOkhttpLogger() ) client } diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/DdiClientHttpRequestsTest.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/DdiClientHttpRequestsTest.kt index 996193d..1263634 100644 --- a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/DdiClientHttpRequestsTest.kt +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/DdiClientHttpRequestsTest.kt @@ -7,7 +7,6 @@ * * SPDX-License-Identifier: EPL-2.0 */ - package org.eclipse.hara.ddiclient.integrationtest import kotlinx.coroutines.CancellationException @@ -22,7 +21,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import org.eclipse.hara.ddi.security.Authentication import org.eclipse.hara.ddiclient.api.ConfigDataProvider import org.eclipse.hara.ddiclient.api.DeploymentPermitProvider @@ -35,13 +33,14 @@ import org.eclipse.hara.ddiclient.api.MessageListener import org.eclipse.hara.ddiclient.api.MessageListener.Message.Event.Polling import org.eclipse.hara.ddiclient.api.MessageListener.Message.State.Idle import org.eclipse.hara.ddiclient.api.Updater -import org.eclipse.hara.ddiclient.integrationtest.utils.ManagementApi -import org.eclipse.hara.ddiclient.integrationtest.utils.ManagementClient -import org.eclipse.hara.ddiclient.integrationtest.utils.ServerSystemConfig +import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementApi +import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementClient +import org.eclipse.hara.ddiclient.integrationtest.api.management.ServerSystemConfig import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.basic import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.gatewayToken -import org.eclipse.hara.ddiclient.integrationtest.utils.log +import org.eclipse.hara.ddiclient.integrationtest.utils.addOkhttpLogger +import org.eclipse.hara.ddiclient.integrationtest.utils.internalLog import org.eclipse.hara.ddiclient.integrationtest.utils.logCurrentFunctionName import org.testng.Assert import org.testng.annotations.AfterTest @@ -74,8 +73,6 @@ class DdiClientHttpRequestsTest { companion object { const val TEST_TARGET_ID = "DoubleToken" const val TEST_TARGET_SECURITY_TOKEN = "r2m3ixxc86a2v4q81wntpyhr78zy08we" - const val LOG_HTTP: Boolean = false - const val LOG_INTERNAL: Boolean = false } private val messageListener: MessageListener @@ -464,7 +461,7 @@ class DdiClientHttpRequestsTest { } } if (expectedMessages.isEmpty() && expectedServerResponses.isEmpty()) { - "INTERNAL: All expected messages received".internalLog() + "All expected messages received".internalLog() checkExpectedMessagesJob?.cancel() if (lastTest) { safeStopClient() @@ -485,21 +482,4 @@ class DdiClientHttpRequestsTest { data class HaraMessage(val message: MessageListener.Message) : ExpectedMessage() data class OkHttpMessage(val code: Int, val authHeader: String?) : ExpectedMessage() } - - private fun OkHttpClient.Builder.addOkhttpLogger() { - val logger = HttpLoggingInterceptor.Logger { message -> - if (LOG_HTTP) { - "OkHttp: $message".log() - } - } - addInterceptor(HttpLoggingInterceptor(logger).apply { - level = HttpLoggingInterceptor.Level.BODY - }) - } - - private fun String.internalLog() { - if (LOG_INTERNAL) { - "INTERNAL: $this".log() - } - } } diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/HawkbitTimeForcedTest.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/HawkbitTimeForcedTest.kt new file mode 100644 index 0000000..9d96ee5 --- /dev/null +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/HawkbitTimeForcedTest.kt @@ -0,0 +1,319 @@ +/* + * Copyright © 2017-2024 Kynetics LLC + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hara.ddiclient.integrationtest + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import okhttp3.OkHttpClient +import org.eclipse.hara.ddiclient.api.ConfigDataProvider +import org.eclipse.hara.ddiclient.api.DeploymentPermitProvider +import org.eclipse.hara.ddiclient.api.DirectoryForArtifactsProvider +import org.eclipse.hara.ddiclient.api.DownloadBehavior +import org.eclipse.hara.ddiclient.api.HaraClient +import org.eclipse.hara.ddiclient.api.HaraClientData +import org.eclipse.hara.ddiclient.api.HaraClientDefaultImpl +import org.eclipse.hara.ddiclient.api.MessageListener +import org.eclipse.hara.ddiclient.api.Updater +import org.eclipse.hara.ddiclient.integrationtest.api.management.Action +import org.eclipse.hara.ddiclient.integrationtest.api.management.ActionStatus +import org.eclipse.hara.ddiclient.integrationtest.api.management.AssignDistributionType +import org.eclipse.hara.ddiclient.integrationtest.api.management.HawkbitAssignDistributionBody +import org.eclipse.hara.ddiclient.integrationtest.api.management.HawkbitTargetInfo +import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementApi +import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementClient +import org.eclipse.hara.ddiclient.integrationtest.api.management.ServerSystemConfig +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.basic +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.endMessagesOnSuccessUpdate +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.firstActionWithAssignmentEntry +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.messagesOnSoftDownloadAuthorization +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.waitingForDownloadAuthorizationMessage +import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.waitingForUpdateAuthorizationMessage +import org.eclipse.hara.ddiclient.integrationtest.utils.addOkhttpLogger +import org.eclipse.hara.ddiclient.integrationtest.utils.internalLog +import org.testng.Assert +import org.testng.annotations.BeforeTest +import org.testng.annotations.Test +import java.io.File +import java.lang.Exception +import kotlin.coroutines.cancellation.CancellationException +import kotlin.time.Duration.Companion.seconds + +class HawkbitTimeForcedTest { + + private lateinit var managementApi: ManagementApi + private var actionId: Int = 22 + + + private var assertServerActionsScope = CoroutineScope(Dispatchers.IO) + private var assertServerActionsOnCompleteJob: Deferred? = null + private var testScope = CoroutineScope(Dispatchers.Default) + + private val throwableScope = CoroutineScope(Dispatchers.Default) + private var throwableJob: Deferred? = null + + + companion object { + const val TARGET_ID = "TimeForceTest" + const val DISTRIBUTION_ID = 3 + } + + @BeforeTest + fun setup() = runBlocking { + managementApi = ManagementClient.newInstance(TestUtils.hawkbitUrl) + runBlocking { + managementApi.setPollingTime(basic, ServerSystemConfig("00:00:10")) + } + } + + private fun defaultClientFromTargetId( + directoryDataProvider: DirectoryForArtifactsProvider = TestUtils.directoryDataProvider, + configDataProvider: ConfigDataProvider = TestUtils.configDataProvider, + updater: Updater = TestUtils.updater, + messageListeners: List = emptyList(), + deploymentPermitProvider: DeploymentPermitProvider = object : + DeploymentPermitProvider {}, + downloadBehavior: DownloadBehavior = TestUtils.downloadBehavior + ): (String) -> HaraClient = { targetId -> + val clientData = HaraClientData( + TestUtils.tenantName, + targetId, + TestUtils.hawkbitUrl, + TestUtils.gatewayToken) + + val client = HaraClientDefaultImpl() + + val eventListener = object : MessageListener { + override fun onMessage(message: MessageListener.Message) { + "Message received: $message".internalLog() + when (message) { + + is MessageListener.Message.Event.UpdateFinished, + MessageListener.Message.State.CancellingUpdate -> { + testScope.launch { + try { + assertServerActionsOnCompleteJob?.await() + } catch (ignored: CancellationException) { + } + } + } + + else -> { + } + } + } + } + + client.init( + clientData, + directoryDataProvider, + configDataProvider, + deploymentPermitProvider, + listOf(eventListener, *messageListeners.toTypedArray()), + listOf(updater), + downloadBehavior, + httpBuilder = OkHttpClient.Builder().addOkhttpLogger() + ) + client + } + + @Test(enabled = true, timeOut = 150_000) + fun testTimeForcedUpdateWhileWaitingForDownloadAuthorization() = runBlocking { + reCreateTestTargetOnServer() + + assignDistributionToTheTarget() + + val client = createHaraClientWithPermissions(downloadAllowed = false) + + val deployment = createTargetTestDeployment(testingForUpdateAuthorization = false) + + startTheTestAndWaitForResult(client, deployment) + + } + + @Test(enabled = true, timeOut = 150_000) + fun testTimeForcedUpdateWhileWaitingForUpdateAuthorization() = runBlocking { + reCreateTestTargetOnServer() + + assignDistributionToTheTarget() + + val client = createHaraClientWithPermissions(downloadAllowed = true) + + val deployment = createTargetTestDeployment(testingForUpdateAuthorization = true) + + startTheTestAndWaitForResult(client, deployment) + } + + private fun createHaraClientWithPermissions( + downloadAllowed: Boolean = false): HaraClient { + + val deploymentBehavior = object : DeploymentPermitProvider { + override fun downloadAllowed() = CompletableDeferred(downloadAllowed) + override fun updateAllowed() = CompletableDeferred(false) + } + return defaultClientFromTargetId( + deploymentPermitProvider = deploymentBehavior).invoke(TARGET_ID) + } + + private suspend fun startTheTestAndWaitForResult(client: HaraClient, + deployment: TestUtils.TargetDeployments) { + client.startAsync() + assertServerActionsOnCompleteJob = assertServerActionsOnComplete(client, deployment) + + testScope.async { + while (assertServerActionsOnCompleteJob?.isCompleted == false) { + delay(1.seconds) + } + }.await() + } + + private fun createTargetTestDeployment( + testingForUpdateAuthorization: Boolean): TestUtils.TargetDeployments { + + val authorizationMessage: Array = if (testingForUpdateAuthorization) { + mutableSetOf(*messagesOnSoftDownloadAuthorization).apply { + add(waitingForUpdateAuthorizationMessage) + }.toTypedArray() + } else { + arrayOf(waitingForDownloadAuthorizationMessage) + } + + val actionsOnFinish = ActionStatus(setOf( + *endMessagesOnSuccessUpdate, + *messagesOnSuccessfullyDownloadTimeForceDistribution, + ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.retrieved, + listOf( + "Update Server: Target retrieved update action and should start now the download.") + ), + *authorizationMessage, + firstActionWithAssignmentEntry, + )) + + val filesDownloadedPairedToServerFile = setOf( + TestUtils.pathResolver.fromArtifact(actionId.toString()).invoke( + TestUtils.test1Artifact) to TestUtils.locationOfFileNamed("test1")) + + + return TestUtils.TargetDeployments( + targetId = TARGET_ID, + targetToken = "", + deploymentInfo = listOf( + TestUtils.TargetDeployments.DeploymentInfo( + actionId = actionId, + actionStatusOnStart = ActionStatus( + setOf( + firstActionWithAssignmentEntry + )), + actionStatusOnFinish = actionsOnFinish, + filesDownloadedPairedWithServerFile = + filesDownloadedPairedToServerFile + ) + ) + ) + } + + private suspend fun reCreateTestTargetOnServer() { + runCatching { + managementApi.deleteTarget(basic, TARGET_ID) + } + runCatching { + managementApi.createTarget(basic, listOf(HawkbitTargetInfo(TARGET_ID))) + } + } + + private suspend fun assignDistributionToTheTarget() { + val timeForcedTime: Long = 10.seconds.inWholeMilliseconds + val distributionBody = HawkbitAssignDistributionBody( + DISTRIBUTION_ID, AssignDistributionType.TIME_FORCED, + System.currentTimeMillis() + timeForcedTime) + val response = + managementApi.assignDistributionToTarget(basic, TARGET_ID, distributionBody) + if (response.assignedActions.isNotEmpty()) { + actionId = response.assignedActions.first().id + } + } + + private fun assertServerActionsOnComplete( + client: HaraClient, + deployment: TestUtils.TargetDeployments): Deferred { + return assertServerActionsScope.async(start = CoroutineStart.LAZY) { + val deploymentInfo = deployment.deploymentInfo.first() + while (managementApi.getActionAsync(basic, deployment.targetId, + deploymentInfo.actionId).status != Action.Status.finished + ) { + delay(5.seconds) + } + + val actionStatus = + managementApi.getTargetActionStatusAsync(basic, deployment.targetId, + deploymentInfo.actionId) + assertEquals(actionStatus.content, + deploymentInfo.actionStatusOnFinish.content) + + deploymentInfo.filesDownloadedPairedWithServerFile.forEach { (fileDownloaded, serverFile) -> + assertEquals(File(fileDownloaded).readText(), + File(serverFile).readText()) + File(fileDownloaded).deleteRecursively() + } + + client.safeStopClient() + } + } + + private suspend fun assertEquals(actual: Any?, expected: Any?) { + throwableJob = throwableScope.async { + Assert.assertEquals(actual, expected) + } + try { + throwableJob?.await() + } catch (ignored: CancellationException) { + } + } + + private val messagesOnSuccessfullyDownloadTimeForceDistribution = arrayOf( + ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.running, + listOf("Successfully downloaded all files") + ), + ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.running, + listOf( + "Successfully downloaded file with md5 ${ + TestUtils.md5OfFileNamed("test1") + }" + ) + ), + ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.download, + listOf( + "Update Server: Target downloads /${TestUtils.tenantNameToLower}/controller/v1/$TARGET_ID/softwaremodules/1/artifacts/test_1") + ), + ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.running, + listOf("Start downloading 1 files") + ) + ) + + + private fun HaraClient.safeStopClient() { + try { + stop() + } catch (ignored: Exception) { + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulForcedUpdate.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulForcedUpdate.kt index 86f30a4..876c057 100644 --- a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulForcedUpdate.kt +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulForcedUpdate.kt @@ -9,7 +9,7 @@ */ package org.eclipse.hara.ddiclient.integrationtest -import org.eclipse.hara.ddiclient.integrationtest.utils.ActionStatus +import org.eclipse.hara.ddiclient.integrationtest.api.management.ActionStatus import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.defaultActionStatusOnStart import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.endMessagesOnSuccessUpdate diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulSoftUpdateWithDownloadAndUpdateAlwaysAllowed.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulSoftUpdateWithDownloadAndUpdateAlwaysAllowed.kt index 9347b6b..72d32db 100644 --- a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulSoftUpdateWithDownloadAndUpdateAlwaysAllowed.kt +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/SuccessfulSoftUpdateWithDownloadAndUpdateAlwaysAllowed.kt @@ -10,7 +10,7 @@ package org.eclipse.hara.ddiclient.integrationtest -import org.eclipse.hara.ddiclient.integrationtest.utils.ActionStatus +import org.eclipse.hara.ddiclient.integrationtest.api.management.ActionStatus import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.defaultActionStatusOnStart import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.endMessagesOnSuccessUpdate diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/api/management/ManagementApi.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/api/management/ManagementApi.kt new file mode 100644 index 0000000..50db2b8 --- /dev/null +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/api/management/ManagementApi.kt @@ -0,0 +1,140 @@ +/* + * Copyright © 2017-2024 Kynetics LLC + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hara.ddiclient.integrationtest.api.management + +import okhttp3.OkHttpClient +import org.eclipse.hara.ddiclient.integrationtest.utils.addOkhttpLogger +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path +import java.util.concurrent.Executors + +interface ManagementApi { + companion object { + const val BASE_V1_REQUEST_MAPPING = "/rest/v1" + } + + @GET("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/actions/{actionId}/status") + suspend fun getTargetActionStatusAsync( + @Header("Authorization") auth: String, + @Path("targetId") targetId: String, + @Path("actionId") actionId: Int + ): ActionStatus + + @GET("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/actions/{actionId}") + suspend fun getActionAsync( + @Header("Authorization") auth: String, + @Path("targetId") targetId: String, + @Path("actionId") actionId: Int + ): Action + + @DELETE("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/actions/{actionId}") + suspend fun deleteTargetActionAsync( + @Header("Authorization") auth: String, + @Path("targetId") targetId: String, + @Path("actionId") actionId: Int + ) + + @PUT("$BASE_V1_REQUEST_MAPPING/system/configs/authentication.gatewaytoken.enabled") + suspend fun setGatewayTokenAuthorizationEnabled( + @Header("Authorization") auth: String, + @Body body: ServerSystemConfig + ) + + @PUT("$BASE_V1_REQUEST_MAPPING/system/configs/authentication.targettoken.enabled") + suspend fun setTargetTokenAuthorizationEnabled( + @Header("Authorization") auth: String, + @Body body: ServerSystemConfig + ) + + @PUT("$BASE_V1_REQUEST_MAPPING/system/configs/pollingTime") + suspend fun setPollingTime( + @Header("Authorization") auth: String, + @Body body: ServerSystemConfig + ) + + @POST("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/assignedDS") + suspend fun assignDistributionToTarget( + @Header("Authorization") auth: String, + @Path("targetId") targetId: String, + @Body body: HawkbitAssignDistributionBody + ): HawkbitAssignDistributionResponse + + @DELETE("$BASE_V1_REQUEST_MAPPING/targets/{targetId}") + suspend fun deleteTarget( + @Header("Authorization") auth: String, + @Path("targetId") targetId: String + ) + + @POST("$BASE_V1_REQUEST_MAPPING/targets") + suspend fun createTarget( + @Header("Authorization") auth: String, + @Body body: List + ): HawkbitTargetInfo +} + +object ManagementClient { + + fun newInstance(url: String): ManagementApi { + return object : ManagementApi { + private val delegate: ManagementApi = Retrofit.Builder().baseUrl(url) + .client(OkHttpClient.Builder().addOkhttpLogger().build()) + .addConverterFactory(GsonConverterFactory.create()) + .callbackExecutor(Executors.newSingleThreadExecutor()) + .build() + .create(ManagementApi::class.java) + + override suspend fun getTargetActionStatusAsync(auth: String, targetId: String, actionId: Int): ActionStatus { + return delegate.getTargetActionStatusAsync(auth, targetId, actionId) + } + + override suspend fun getActionAsync(auth: String, targetId: String, actionId: Int): Action { + return delegate.getActionAsync(auth, targetId, actionId) + } + + override suspend fun deleteTargetActionAsync(auth: String, targetId: String, actionId: Int): Unit { + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. + } + + override suspend fun setGatewayTokenAuthorizationEnabled(auth: String, + body: ServerSystemConfig) { + delegate.setGatewayTokenAuthorizationEnabled(auth, body) + } + + override suspend fun setTargetTokenAuthorizationEnabled(auth: String, + body: ServerSystemConfig) { + delegate.setTargetTokenAuthorizationEnabled(auth, body) + } + + override suspend fun setPollingTime(auth: String, body: ServerSystemConfig) { + delegate.setPollingTime(auth, body) + } + + override suspend fun assignDistributionToTarget(auth: String, targetId: String, body: HawkbitAssignDistributionBody): HawkbitAssignDistributionResponse { + return delegate.assignDistributionToTarget(auth, targetId, body) + } + + override suspend fun deleteTarget(auth: String, targetId: String) { + return delegate.deleteTarget(auth, targetId) + } + + override suspend fun createTarget(auth: String, + body: List): HawkbitTargetInfo { + return delegate.createTarget(auth, body) + } + } + } +} diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/api/management/ManagementApiModels.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/api/management/ManagementApiModels.kt new file mode 100644 index 0000000..d6f2edc --- /dev/null +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/api/management/ManagementApiModels.kt @@ -0,0 +1,63 @@ +/* + * Copyright © 2017-2024 Kynetics LLC + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hara.ddiclient.integrationtest.api.management + +import com.google.gson.annotations.SerializedName + +data class ActionStatus(val content: Set, val total: Int = content.size, + val size: Int = content.size) { + data class ContentEntry(val type: Type, val messages: List) { + enum class Type { + finished, error, warning, pending, running, canceled, retrieved, canceling, download + } + } +} + +data class Action(val status: Status) { + enum class Status { + finished, pending + } +} + +data class ServerSystemConfig(val value: Any) + +data class HawkbitAssignDistributionBody( + val id: Int, + val type: AssignDistributionType, + @Suppress("SpellCheckingInspection") + @SerializedName("forcetime") + val forceTime: Long, + val maintenanceWindow: MaintenanceWindow? = null) + +data class HawkbitAssignDistributionResponse( + val alreadyAssigned: Int, + val assignedActions: List, + val assigned: Int) + + +data class HawkbitTargetInfo( + val name: String, + val controllerId: String = name, + val securityToken: String= "") + +data class AssignedAction(val id: Int) + +enum class AssignDistributionType { + @SerializedName("soft") + SOFT, + @SerializedName("forced") + FORCED, + @SerializedName("timeforced") + TIME_FORCED, + @SerializedName("downloadonly") + DOWNLOAD_ONLY, +} + +data class MaintenanceWindow(val schedule: String?, val duration: String, val timezone: String) \ No newline at end of file diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/KotlinExFunctions.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/KotlinExFunctions.kt index 7b1544c..22de11f 100644 --- a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/KotlinExFunctions.kt +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/KotlinExFunctions.kt @@ -19,6 +19,11 @@ val currentTime: String fun String.log() { println("$currentTime: $this") } +fun String.internalLog() { + if (LOG_INTERNAL) { + "INTERNAL: $this".log() + } +} @Suppress("NOTHING_TO_INLINE") inline fun logCurrentFunctionName() { diff --git a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/Utils.kt b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/Utils.kt index 1fd8a66..2219c80 100644 --- a/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/Utils.kt +++ b/src/test/kotlin/org/eclipse/hara/ddiclient/integrationtest/utils/Utils.kt @@ -14,126 +14,20 @@ import org.eclipse.hara.ddiclient.api.PathResolver import org.eclipse.hara.ddiclient.api.Updater import okhttp3.Credentials import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import org.eclipse.hara.ddiclient.api.ConfigDataProvider import org.eclipse.hara.ddiclient.api.DirectoryForArtifactsProvider import org.eclipse.hara.ddiclient.api.DownloadBehavior -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.http.Body -import retrofit2.http.DELETE -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.PUT -import retrofit2.http.Path +import org.eclipse.hara.ddiclient.integrationtest.api.management.ActionStatus import java.io.File import java.util.* -import java.util.concurrent.Executors + +val LOG_HTTP: Boolean = System.getProperty("LOG_HTTP", "false").toBoolean() +val LOG_INTERNAL: Boolean = System.getProperty("LOG_INTERNAL", "false").toBoolean() /** * @author Daniele Sergio */ - -data class ActionStatus(val content: Set, val total: Int = content.size, val size: Int = content.size) { - data class ContentEntry(val type: Type, val messages: List) { - enum class Type { - finished, error, warning, pending, running, canceled, retrieved, canceling, download - } - } -} - -data class Action(val status: Status){ - enum class Status { - finished, pending - } -} - -data class ServerSystemConfig(val value: Any) - -interface ManagementApi { - companion object { - const val BASE_V1_REQUEST_MAPPING = "/rest/v1" - } - - @GET("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/actions/{actionId}/status") - suspend fun getTargetActionStatusAsync( - @Header("Authorization") auth: String, - @Path("targetId") targetId: String, - @Path("actionId") actionId: Int - ): ActionStatus - - @GET("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/actions/{actionId}") - suspend fun getActionAsync( - @Header("Authorization") auth: String, - @Path("targetId") targetId: String, - @Path("actionId") actionId: Int - ): Action - - @DELETE("$BASE_V1_REQUEST_MAPPING/targets/{targetId}/actions/{actionId}") - suspend fun deleteTargetActionAsync( - @Header("Authorization") auth: String, - @Path("targetId") targetId: String, - @Path("actionId") actionId: Int - ) - - @PUT("$BASE_V1_REQUEST_MAPPING/system/configs/authentication.gatewaytoken.enabled") - suspend fun setGatewayTokenAuthorizationEnabled( - @Header("Authorization") auth: String, - @Body body: ServerSystemConfig - ) - - @PUT("$BASE_V1_REQUEST_MAPPING/system/configs/authentication.targettoken.enabled") - suspend fun setTargetTokenAuthorizationEnabled( - @Header("Authorization") auth: String, - @Body body: ServerSystemConfig - ) - - @PUT("$BASE_V1_REQUEST_MAPPING/system/configs/pollingTime") - suspend fun setPollingTime( - @Header("Authorization") auth: String, - @Body body: ServerSystemConfig - ) -} - -object ManagementClient { - - fun newInstance(url: String): ManagementApi { - return object : ManagementApi { - private val delegate: ManagementApi = Retrofit.Builder().baseUrl(url) - .client(OkHttpClient.Builder().build()) - .addConverterFactory(GsonConverterFactory.create()) - .callbackExecutor(Executors.newSingleThreadExecutor()) - .build() - .create(ManagementApi::class.java) - - override suspend fun getTargetActionStatusAsync(auth: String, targetId: String, actionId: Int): ActionStatus { - return delegate.getTargetActionStatusAsync(auth, targetId, actionId) - } - - override suspend fun getActionAsync(auth: String, targetId: String, actionId: Int): Action { - return delegate.getActionAsync(auth, targetId, actionId) - } - - override suspend fun deleteTargetActionAsync(auth: String, targetId: String, actionId: Int): Unit { - TODO("not implemented") // To change body of created functions use File | Settings | File Templates. - } - - override suspend fun setGatewayTokenAuthorizationEnabled(auth: String, - body: ServerSystemConfig) { - delegate.setGatewayTokenAuthorizationEnabled(auth, body) - } - - override suspend fun setTargetTokenAuthorizationEnabled(auth: String, - body: ServerSystemConfig) { - delegate.setTargetTokenAuthorizationEnabled(auth, body) - } - - override suspend fun setPollingTime(auth: String, body: ServerSystemConfig) { - delegate.setPollingTime(auth, body) - } - } - } -} - object TestUtils { data class TargetDeployments( @@ -142,10 +36,10 @@ object TestUtils { val deploymentInfo: List ) { data class DeploymentInfo( - val actionId: Int, - val actionStatusOnStart: ActionStatus, - val actionStatusOnFinish: ActionStatus, - val filesDownloadedPairedWithServerFile: Set> + val actionId: Int, + val actionStatusOnStart: ActionStatus, + val actionStatusOnFinish: ActionStatus, + val filesDownloadedPairedWithServerFile: Set> ) } @@ -183,10 +77,20 @@ object TestUtils { } } - val serverFilesMappedToLocantionAndMd5 = mapOf("test1" to Pair("docker/test/artifactrepo/$tenantName/4b/5a/b54e43082887d1e7cdb10b7a21fe4a1e56b44b5a", "2490a3d39b0004e4afeb517ef0ddbe2d"), - "test2" to Pair("docker/test/artifactrepo/$tenantName/b6/1e/a096a9d3cb96fa4cf6c63bd736a84cb7a7e4b61e", "b0b3b0dbf5330e3179c6ae3e0ac524c9"), - "test3" to Pair("docker/test/artifactrepo/$tenantName/bf/94/cde0c01b26634f869bb876326e4fbe969792bf94", "2244fbd6bee5dcbe312e387c062ce6e6"), - "test4" to Pair("docker/test/artifactrepo/$tenantName/dd/0a/07fa4d03ac54d0b2a52f23d8e878c96db7aadd0a", "94424c5ce3f8c57a5b26d02f37dc06fc")) + val serverFilesMappedToLocantionAndMd5 = mapOf( + "test1" to Pair( + "docker/test/artifactrepo/$tenantName/4b/5a/b54e43082887d1e7cdb10b7a21fe4a1e56b44b5a", + "2490a3d39b0004e4afeb517ef0ddbe2d"), + "test2" to Pair( + "docker/test/artifactrepo/$tenantName/b6/1e/a096a9d3cb96fa4cf6c63bd736a84cb7a7e4b61e", + "b0b3b0dbf5330e3179c6ae3e0ac524c9"), + "test3" to Pair( + "docker/test/artifactrepo/$tenantName/bf/94/cde0c01b26634f869bb876326e4fbe969792bf94", + "2244fbd6bee5dcbe312e387c062ce6e6"), + "test4" to Pair( + "docker/test/artifactrepo/$tenantName/dd/0a/07fa4d03ac54d0b2a52f23d8e878c96db7aadd0a", + "94424c5ce3f8c57a5b26d02f37dc06fc"), + ) val md5OfFileNamed: (String) -> String = { key -> serverFilesMappedToLocantionAndMd5.getValue(key).second } val locationOfFileNamed: (String) -> String = { key -> serverFilesMappedToLocantionAndMd5.getValue(key).first } @@ -336,15 +240,22 @@ object TestUtils { ) ) + val waitingForDownloadAuthorizationMessage = ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.running, + listOf("Waiting authorization to download") + ) + + val waitingForUpdateAuthorizationMessage = ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.running, + listOf("Waiting authorization to update") + ) + val messagesOnSoftDownloadAuthorization = arrayOf( ActionStatus.ContentEntry( ActionStatus.ContentEntry.Type.running, listOf("Authorization granted for downloading files") ), - ActionStatus.ContentEntry( - ActionStatus.ContentEntry.Type.running, - listOf("Waiting authorization to download") - ) + waitingForDownloadAuthorizationMessage ) val messagesOnSoftUpdateAuthorization = arrayOf( @@ -352,10 +263,7 @@ object TestUtils { ActionStatus.ContentEntry.Type.running, listOf("Authorization granted for update") ), - ActionStatus.ContentEntry( - ActionStatus.ContentEntry.Type.running, - listOf("Waiting authorization to update") - ) + waitingForUpdateAuthorizationMessage ) val firstActionEntry = ActionStatus.ContentEntry( @@ -363,6 +271,11 @@ object TestUtils { listOf(null) ) + val firstActionWithAssignmentEntry = ActionStatus.ContentEntry( + ActionStatus.ContentEntry.Type.running, + listOf("Assignment initiated by user 'test'") + ) + val startMessagesOnUpdateFond = arrayOf( ActionStatus.ContentEntry( ActionStatus.ContentEntry.Type.retrieved, @@ -375,17 +288,29 @@ object TestUtils { pathResolver.fromArtifact(action.toString()).invoke( test1Artifact ) to locationOfFileNamed("test1"), - pathResolver.fromArtifact(action.toString()).invoke( - test2Artifact - ) to locationOfFileNamed("test2"), - pathResolver.fromArtifact(action.toString()).invoke( - test3Artifact - ) to locationOfFileNamed("test3"), - pathResolver.fromArtifact(action.toString()).invoke( - test4Artifact - ) to locationOfFileNamed("test4") + pathResolver.fromArtifact(action.toString()).invoke( + test2Artifact + ) to locationOfFileNamed("test2"), + pathResolver.fromArtifact(action.toString()).invoke( + test3Artifact + ) to locationOfFileNamed("test3"), + pathResolver.fromArtifact(action.toString()).invoke( + test4Artifact + ) to locationOfFileNamed("test4"), ) val defaultActionStatusOnStart = ActionStatus(setOf(firstActionEntry)) } + + +fun OkHttpClient.Builder.addOkhttpLogger(): OkHttpClient.Builder = apply { + val logger = HttpLoggingInterceptor.Logger { message -> + if (LOG_HTTP) { + "OkHttp: $message".log() + } + } + addInterceptor(HttpLoggingInterceptor(logger).apply { + level = HttpLoggingInterceptor.Level.BODY + }) +}