Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: MIT
*/

package com.owncloud.android.lib.resources.assistant.chat

import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessageRequest
import com.owncloud.android.lib.resources.assistant.v2.GetTaskTypesRemoteOperationV2
import com.owncloud.android.lib.resources.status.NextcloudVersion
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test

class AssistantChatTests : AbstractIT() {
private lateinit var sessionId: String

@Before
fun before() {
testOnlyOnServer(NextcloudVersion.nextcloud_30)

val result =
CreateConversationRemoteOperation(null, System.currentTimeMillis())
.execute(nextcloudClient)
assertTrue(result.isSuccess)
sessionId =
result.resultData.session.id
.toString()
}

@Test
fun testCreateAndGetMessages() {
val messageRequest =
ChatMessageRequest(
sessionId = sessionId,
role = "human",
content = "Hello assistant!",
timestamp = System.currentTimeMillis()
)

val createResult = CreateMessageRemoteOperation(messageRequest).execute(nextcloudClient)
assertTrue(createResult.isSuccess)

val createdMessage = createResult.resultData!!
assertEquals("Hello assistant!", createdMessage.content)
assertEquals("human", createdMessage.role)
assertEquals(sessionId.toLongOrNull(), createdMessage.sessionId)

// Get messages for session
val getResult = GetMessagesRemoteOperation(sessionId).execute(nextcloudClient)
assertTrue(getResult.isSuccess)

val messages = getResult.resultData
assertTrue(messages.isNotEmpty())
assertTrue(messages.any { it.id == createdMessage.id })
}

@Test
fun testDeleteMessage() {
val messageRequest =
ChatMessageRequest(
sessionId = sessionId,
role = "human",
content = "Message to delete",
timestamp = System.currentTimeMillis()
)
val createResult = CreateMessageRemoteOperation(messageRequest).execute(nextcloudClient)
assertTrue(createResult.isSuccess)

val messageId = createResult.resultData!!.id.toString()

// Delete the message
val deleteResult = DeleteMessageRemoteOperation(messageId, sessionId).execute(nextcloudClient)
assertTrue(deleteResult.isSuccess)

// Ensure the message is gone
val getResult = GetMessagesRemoteOperation(sessionId).execute(nextcloudClient)
assertTrue(getResult.isSuccess)
assertTrue(getResult.resultData!!.none { it.id.toString() == messageId })
}

@Test
fun testGetAndDeleteConversations() {
// Create a message to have a session
val messageRequest =
ChatMessageRequest(
sessionId = sessionId,
role = "human",
content = "Starting conversation",
timestamp = System.currentTimeMillis()
)
CreateMessageRemoteOperation(messageRequest).execute(nextcloudClient)

// Get list of conversations
val getConversationsResult = GetConversationListRemoteOperation().execute(nextcloudClient)
assertTrue(getConversationsResult.isSuccess)

val conversations = getConversationsResult.resultData
assertTrue(conversations.any { it.id.toString() == sessionId })

// Delete conversation
val deleteResult = DeleteConversationRemoteOperation(sessionId).execute(nextcloudClient)
assertTrue(deleteResult.isSuccess)

// Ensure conversation is gone
val getAfterDelete = GetConversationListRemoteOperation().execute(nextcloudClient)
assertTrue(getAfterDelete.isSuccess)
assertTrue(getAfterDelete.resultData!!.none { it.id.toString() == sessionId })
}

@Test
fun testGetTaskTypesAndVerifyChatAndSorting() {
testOnlyOnServer(NextcloudVersion.nextcloud_34)

val result = GetTaskTypesRemoteOperationV2().execute(nextcloudClient)

assertTrue("Request must succeed", result.isSuccess)
val types = result.resultData
assertNotNull("Task types must not be null", types)
assertTrue("Task types list must not be empty", types!!.isNotEmpty())

val firstElementIsChat = types.first().isChat()
assertTrue(
"The first task type must be a chat type (sorted by isChat descending)",
firstElementIsChat
)

val chatTypes = types.filter { it.isChat() }
assertTrue("There must be at least one chat-type task", chatTypes.isNotEmpty())

val nonChat = types.filterNot { it.isChat() }
assertTrue(
"There must be at least one non-chat task with single text input/output",
nonChat.isNotEmpty()
)

val indexOfFirstNonChat = types.indexOfFirst { !it.isChat() }
if (indexOfFirstNonChat > 0) {
val anyChatAfterNonChat = types.drop(indexOfFirstNonChat).any { it.isChat() }
assertTrue(
"Chat types must appear before non-chat types in the list",
!anyChatAfterNonChat
)
}

types.forEach { tt ->
assertNotNull("Each task type must have an ID assigned", tt.id)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: MIT
*/

package com.owncloud.android.lib.resources.assistant.chat
import com.nextcloud.common.NextcloudClient
import com.nextcloud.operations.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage
import org.apache.commons.httpclient.HttpStatus

class CheckGenerationRemoteOperation(
private val taskId: String,
private val sessionId: String
) : RemoteOperation<ChatMessage>() {
@Suppress("TooGenericExceptionCaught")
override fun run(client: NextcloudClient): RemoteOperationResult<ChatMessage> {
val url =
client.baseUri.toString() +
"$BASE_URL/check_generation?taskId=$taskId&sessionId=$sessionId"

val getMethod = GetMethod(url, true)
val status = getMethod.execute(client)

return try {
if (status == HttpStatus.SC_OK) {
val responseBody = getMethod.getResponseBodyAsString()
val jsonResponse = gson.fromJson(responseBody, ChatMessage::class.java)

val result = RemoteOperationResult<ChatMessage>(true, getMethod)
result.resultData = jsonResponse
result
} else {
RemoteOperationResult(false, getMethod)
}
} catch (e: Exception) {
Log_OC.e(TAG, "check generation failed: ", e)
RemoteOperationResult(false, getMethod)
} finally {
getMethod.releaseConnection()
}
}

companion object {
private const val TAG = "CheckGenerationRemoteOperation"
private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: MIT
*/

package com.owncloud.android.lib.resources.assistant.chat

import com.nextcloud.common.NextcloudClient
import com.nextcloud.operations.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.assistant.chat.model.Session
import org.apache.commons.httpclient.HttpStatus

class CheckSessionRemoteOperation(
private val sessionId: String
) : RemoteOperation<Session>() {
@Suppress("TooGenericExceptionCaught")
override fun run(client: NextcloudClient): RemoteOperationResult<Session> {
val getMethod =
GetMethod(
client.baseUri.toString() + "$BASE_URL/check_session?sessionId=$sessionId",
true
)
val status = getMethod.execute(client)

return try {
if (status == HttpStatus.SC_OK) {
val responseBody = getMethod.getResponseBodyAsString()
val jsonResponse = gson.fromJson(responseBody, Session::class.java)

val result = RemoteOperationResult<Session>(true, getMethod)
result.resultData = jsonResponse
result
} else {
RemoteOperationResult(false, getMethod)
}
} catch (e: Exception) {
Log_OC.e(TAG, "check session failed: ", e)
RemoteOperationResult(false, getMethod)
} finally {
getMethod.releaseConnection()
}
}

companion object {
private const val TAG = "CheckSessionRemoteOperation"
private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: MIT
*/

package com.owncloud.android.lib.resources.assistant.chat

import com.google.gson.reflect.TypeToken
import com.nextcloud.common.NextcloudClient
import com.nextcloud.operations.PutMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.assistant.chat.model.CreateConversation
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import org.apache.commons.httpclient.HttpStatus

class CreateConversationRemoteOperation(
private val title: String?,
private val timestamp: Long
) : RemoteOperation<CreateConversation>() {
@Suppress("TooGenericExceptionCaught")
override fun run(client: NextcloudClient): RemoteOperationResult<CreateConversation> {
val bodyMap =
hashMapOf(
"title" to title,
"timestamp" to timestamp
)

val json = gson.toJson(bodyMap)
val requestBody = json.toRequestBody("application/json".toMediaTypeOrNull())

val putMethod = PutMethod(client.baseUri.toString() + "$BASE_URL/new_session", true, requestBody)
val status = putMethod.execute(client)

return try {
if (status == HttpStatus.SC_OK) {
val responseBody = putMethod.getResponseBodyAsString()
val type = object : TypeToken<CreateConversation>() {}.type
val response: CreateConversation = gson.fromJson(responseBody, type)
val result: RemoteOperationResult<CreateConversation> = RemoteOperationResult(true, putMethod)
result.resultData = response
result
} else {
RemoteOperationResult(false, putMethod)
}
} catch (e: Exception) {
Log_OC.e(TAG, "create conversation: ", e)
RemoteOperationResult(false, putMethod)
} finally {
putMethod.releaseConnection()
}
}

companion object {
private const val TAG = "CreateConversationRemoteOperation"
private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: MIT
*/

package com.owncloud.android.lib.resources.assistant.chat

import com.google.gson.reflect.TypeToken
import com.nextcloud.common.NextcloudClient
import com.nextcloud.operations.PutMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessage
import com.owncloud.android.lib.resources.assistant.chat.model.ChatMessageRequest
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import org.apache.commons.httpclient.HttpStatus

class CreateMessageRemoteOperation(
private val messageRequest: ChatMessageRequest
) : RemoteOperation<ChatMessage>() {
@Suppress("TooGenericExceptionCaught")
override fun run(client: NextcloudClient): RemoteOperationResult<ChatMessage> {
val json = gson.toJson(messageRequest.bodyMap)
val requestBody = json.toRequestBody("application/json".toMediaTypeOrNull())

val putMethod = PutMethod(client.baseUri.toString() + "$BASE_URL/new_message", true, requestBody)
val status = putMethod.execute(client)

return try {
if (status == HttpStatus.SC_OK) {
val responseBody = putMethod.getResponseBodyAsString()
val type = object : TypeToken<ChatMessage>() {}.type
val response: ChatMessage = gson.fromJson(responseBody, type)
val result: RemoteOperationResult<ChatMessage> = RemoteOperationResult(true, putMethod)
result.resultData = response
result
} else {
RemoteOperationResult(false, putMethod)
}
} catch (e: Exception) {
Log_OC.e(TAG, "create message: ", e)
RemoteOperationResult(false, putMethod)
} finally {
putMethod.releaseConnection()
}
}

companion object {
private const val TAG = "CreateMessageRemoteOperation"
private const val BASE_URL = "/ocs/v2.php/apps/assistant/chat"
}
}
Loading
Loading