Skip to content

Commit

Permalink
add tests for POST with multipart bodies
Browse files Browse the repository at this point in the history
  • Loading branch information
sunny-chung committed May 1, 2024
1 parent 3a8848b commit 68dd326
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fun <T: DropDownable> DropDownView(
)
},
arrowPadding: PaddingValues = PaddingValues(0.dp),
testTagPart: TestTagPart? = null,
testTagParts: Array<Any?>? = null,
selectedItem: T? = null,
onClickItem: (T) -> Boolean,
) {
Expand All @@ -78,8 +78,8 @@ fun <T: DropDownable> DropDownView(
.padding(horizontal = 8.dp, vertical = 4.dp)
.fillMaxWidth()
.run {
if (testTagPart != null) {
testTag(buildTestTag(testTagPart, TestTagPart.DropdownItem, item.displayText)!!)
if (testTagParts != null) {
testTag(buildTestTag(*testTagParts, TestTagPart.DropdownItem, item.displayText)!!)
} else {
this
}
Expand All @@ -103,8 +103,8 @@ fun <T: DropDownable> DropDownView(
this
}
}.run {
if (testTagPart != null) {
testTag(buildTestTag(testTagPart, TestTagPart.DropdownButton)!!)
if (testTagParts != null) {
testTag(buildTestTag(*testTagParts, TestTagPart.DropdownButton)!!)
} else {
this
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package com.sunnychung.application.multiplatform.hellohttp.ux

import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.window.AwtWindow
import com.sunnychung.application.multiplatform.hellohttp.util.log
import com.sunnychung.application.multiplatform.hellohttp.ux.viewmodel.FileDialogState
import com.sunnychung.lib.multiplatform.kdatetime.KDuration
import com.sunnychung.lib.multiplatform.kdatetime.KFixedTimeUnit
import com.sunnychung.lib.multiplatform.kdatetime.KInstant
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.awt.FileDialog
import java.awt.Frame
import java.io.File

/**
* For UX test only
*/
var testChooseFile: File? = null

/**
* Due to the bug stated in {@link FileDialogState}, result of onCloseRequest has 3 cases:
* 1. non-empty list -> user selected a file
Expand All @@ -27,6 +35,16 @@ fun FileDialog(
onCloseRequest: (result: List<File>?) -> Unit
) {
log.d { "FileDialog 1" }

testChooseFile?.let {
rememberCoroutineScope().launch {
delay(50L)
testChooseFile = null
onCloseRequest(listOf(it))
}
return
}

val lastCloseTime = state.lastCloseTime.value
if (lastCloseTime != null && KInstant.now() - lastCloseTime < KDuration.Companion.of(1, KFixedTimeUnit.Second)) {
onCloseRequest(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ fun KeyValueEditorView(
} else null,
modifier = Modifier.weight(0.6f).border(width = 1.dp, color = colors.placeholder)
.run {
buildTestTag(testTagPart1, testTagPart2, TestTagPart.FileButton, index)?.let {
buildTestTag(testTagPart1, testTagPart2, index, TestTagPart.FileButton)?.let {
testTag(it)
} ?: this
},
Expand All @@ -216,6 +216,7 @@ fun KeyValueEditorView(
onItemChange(index, it.copy(valueType = valueType))
true
},
testTagParts = arrayOf(testTagPart1, testTagPart2, index, TestTagPart.ValueTypeDropdown),
modifier = Modifier.padding(horizontal = 4.dp)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ fun RequestEditorView(
onRequestModified(request.copyForApplication(application = it.key.application, method = it.key.method))
true
},
testTagPart = TestTagPart.RequestMethodDropdown,
testTagParts = arrayOf(TestTagPart.RequestMethodDropdown),
modifier = Modifier.fillMaxHeight()
)

Expand Down Expand Up @@ -972,7 +972,7 @@ private fun RequestBodyEditor(
)
true
},
testTagPart = TestTagPart.RequestBodyTypeDropdown,
testTagParts = arrayOf(TestTagPart.RequestBodyTypeDropdown),
)
} else {
AppText(selectedContentType.displayText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.ui.window.rememberWindowState
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.sunnychung.application.multiplatform.hellohttp.AppContext
import com.sunnychung.application.multiplatform.hellohttp.model.ContentType
import com.sunnychung.application.multiplatform.hellohttp.model.FieldValueType
import com.sunnychung.application.multiplatform.hellohttp.model.FileBody
import com.sunnychung.application.multiplatform.hellohttp.model.FormUrlEncodedBody
import com.sunnychung.application.multiplatform.hellohttp.model.GraphqlBody
Expand All @@ -44,6 +45,7 @@ import com.sunnychung.application.multiplatform.hellohttp.ux.AppView
import com.sunnychung.application.multiplatform.hellohttp.ux.TestTag
import com.sunnychung.application.multiplatform.hellohttp.ux.TestTagPart
import com.sunnychung.application.multiplatform.hellohttp.ux.buildTestTag
import com.sunnychung.application.multiplatform.hellohttp.ux.testChooseFile
import com.sunnychung.lib.multiplatform.kdatetime.KDuration
import com.sunnychung.lib.multiplatform.kdatetime.extension.seconds
import kotlinx.coroutines.delay
Expand Down Expand Up @@ -351,6 +353,112 @@ class RequestResponseTest {
)
)
}

@Test
fun echoPostWithMultipartStrings() = runTest {
createAndSendRestEchoRequestAndAssertResponse(
UserRequestTemplate(
id = uuidString(),
method = "POST",
url = echoUrl,
examples = listOf(
UserRequestExample(
id = uuidString(),
name = "Base",
contentType = ContentType.Multipart,
body = MultipartBody(listOf(
UserKeyValuePair("abcc", "中文字123"),
UserKeyValuePair("MyFormParam", "abcc def_gh+i=?j/k"),
UserKeyValuePair("emoj", "a\uD83D\uDE0EBC"),
)),
)
)
)
)
}

@Test
fun echoPostWithMultipartFiles() = runTest {
createAndSendRestEchoRequestAndAssertResponse(
UserRequestTemplate(
id = uuidString(),
method = "POST",
url = echoUrl,
examples = listOf(
UserRequestExample(
id = uuidString(),
name = "Base",
contentType = ContentType.Multipart,
body = MultipartBody(listOf(
UserKeyValuePair("abcc", "中文字123"),
UserKeyValuePair(
id = uuidString(),
key = "file2",
value = "src/test/resources/testFile2.txt",
valueType = FieldValueType.File,
isEnabled = true,
),
UserKeyValuePair(
id = uuidString(),
key = "file1",
value = "src/test/resources/testFile1中文字.txt",
valueType = FieldValueType.File,
isEnabled = true,
),
UserKeyValuePair("MyFormParam", "abcc def_gh+i=?j/k"),
UserKeyValuePair("emoj", "a\uD83D\uDE0EBC"),
)),
)
)
)
)
}

@Test
fun echoPostWithMultipartFilesAndHeaderAndQueryParameters() = runTest {
createAndSendRestEchoRequestAndAssertResponse(
UserRequestTemplate(
id = uuidString(),
method = "POST",
url = echoUrl,
examples = listOf(
UserRequestExample(
id = uuidString(),
name = "Base",
headers = listOf(
UserKeyValuePair("h1", "abcd"),
UserKeyValuePair("x-My-Header", "defg HIjk"),
),
queryParameters = listOf(
UserKeyValuePair("abc", "中文字"),
UserKeyValuePair("MyQueryParam", "abc def_gh+i=?j/k"),
UserKeyValuePair("emoji", "A\uD83D\uDE0Eb"),
),
contentType = ContentType.Multipart,
body = MultipartBody(listOf(
UserKeyValuePair("abcc", "中文字123"),
UserKeyValuePair(
id = uuidString(),
key = "file2",
value = "src/test/resources/testFile2.txt",
valueType = FieldValueType.File,
isEnabled = true,
),
UserKeyValuePair(
id = uuidString(),
key = "file1",
value = "src/test/resources/testFile1中文字.txt",
valueType = FieldValueType.File,
isEnabled = true,
),
UserKeyValuePair("MyFormParam", "abcc def_gh+i=?j/k"),
UserKeyValuePair("emoj", "a\uD83D\uDE0EBC"),
)),
)
)
)
)
}
}

fun runTest(testBlock: suspend ComposeUiTest.() -> Unit) =
Expand Down Expand Up @@ -463,7 +571,54 @@ suspend fun ComposeUiTest.createAndSendHttpRequest(request: UserRequestTemplate,
delayShort()
}
}
ContentType.Multipart -> TODO()

ContentType.Multipart -> {
val body = (baseExample.body as MultipartBody).value
body.forEachIndexed { index, it ->
waitUntilExactlyOneExists(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, TestTagPart.Key, index)!!))
waitUntilExactlyOneExists(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, TestTagPart.Value, index)!!))
onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, TestTagPart.Key, index)!!))
.assertIsDisplayedWithRetry(this)
.performTextInput(it.key)
delayShort()
onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, TestTagPart.Key, index)!!))
.assertIsDisplayedWithRetry(this)
.assertTextEquals(it.key)

when (it.valueType) {
FieldValueType.String -> {
onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, TestTagPart.Value, index)!!))
.assertIsDisplayedWithRetry(this)
.performTextInput(it.value)
delayShort()
}
FieldValueType.File -> {
onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, index, TestTagPart.ValueTypeDropdown, TestTagPart.DropdownButton)!!))
.assertIsDisplayedWithRetry(this)
.performClickWithRetry(this)
delayShort()

onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, index, TestTagPart.ValueTypeDropdown, TestTagPart.DropdownItem, "File")!!))
.assertIsDisplayedWithRetry(this)
.performClickWithRetry(this)
delayShort()

testChooseFile = File(it.value)
val filename = testChooseFile!!.name
onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, index, TestTagPart.FileButton)!!))
.assertIsDisplayedWithRetry(this)
.performClickWithRetry(this)

delay(100)
delayShort()
onNode(hasTestTag(buildTestTag(TestTagPart.RequestBodyMultipartForm, TestTagPart.Current, index, TestTagPart.FileButton)!!))
.assertTextEquals(filename, includeEditableText = false)

}
}
}
}

ContentType.FormUrlEncoded -> {
val body = (baseExample.body as FormUrlEncodedBody).value
body.forEachIndexed { index, it ->
Expand Down Expand Up @@ -574,7 +729,25 @@ suspend fun ComposeUiTest.createAndSendRestEchoRequestAndAssertResponse(request:
} else {
assertEquals(0, resp.formData.size)
}
assertEquals(0, resp.multiparts.size)
if (baseExample.body is MultipartBody) {
val body = (baseExample.body as MultipartBody).value.sortedBy { it.key }
resp.multiparts.sortedBy { it.name }.forEachIndexed { index, part ->
val reqPart = body[index]
assertEquals(reqPart.key, part.name)
when (reqPart.valueType) {
FieldValueType.String -> assertEquals(reqPart.value, part.data)
FieldValueType.File -> {
val file = File(reqPart.value)
assertEquals(file.length().toInt(), part.size)
if (part.data != null) {
assertEquals(file.readText(), part.data)
}
}
}
}
} else {
assertEquals(0, resp.multiparts.size)
}
when (val body = baseExample.body) {
null, is FormUrlEncodedBody, is MultipartBody -> assertEquals(null, resp.body)
is FileBody -> TODO()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
There are some 中文字 here.
😎✌🏽Yeah!
4 changes: 4 additions & 0 deletions ux-and-transport-test/src/test/resources/testFile2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
abcdef

GHIjk
+=s-+#@$*&@(7567!()*&@(#_\n

0 comments on commit 68dd326

Please sign in to comment.