@@ -10,8 +10,10 @@ import eu.opencloud.android.lib.common.accounts.AccountUtils
1010import eu.opencloud.android.lib.common.authentication.OpenCloudCredentialsFactory
1111import eu.opencloud.android.lib.common.operations.RemoteOperationResult
1212import eu.opencloud.android.lib.resources.files.tus.CreateTusUploadRemoteOperation.Base64Encoder
13+ import okhttp3.mockwebserver.Dispatcher
1314import okhttp3.mockwebserver.MockResponse
1415import okhttp3.mockwebserver.MockWebServer
16+ import okhttp3.mockwebserver.RecordedRequest
1517import org.junit.After
1618import org.junit.Assert.*
1719import org.junit.Before
@@ -179,6 +181,72 @@ class TusIntegrationTest {
179181 assertEquals(" 1.0.0" , delReq.getHeader(" Tus-Resumable" ))
180182 }
181183
184+ @Test
185+ fun creation_with_upload_returns_offset () {
186+ val client = newClient()
187+ val collectionPath = " /remote.php/dav/uploads/$userId "
188+ val locationPath = " $collectionPath /UPLD-WITH-DATA"
189+ val localFile = File .createTempFile(" tus" , " .bin" ).apply {
190+ writeBytes(ByteArray (100 ) { it.toByte() })
191+ }
192+ val firstChunkSize = 50L
193+
194+ // POST Create with Upload -> 201 + Location + Upload-Offset
195+ server.dispatcher = object : Dispatcher () {
196+ override fun dispatch (request : RecordedRequest ): MockResponse {
197+ if (request.path == collectionPath) {
198+ // Verify body content
199+ val bodySize = request.bodySize
200+ assertEquals(firstChunkSize, bodySize)
201+
202+ // Verify body bytes (first 50 bytes of the file)
203+ val expectedBytes = ByteArray (firstChunkSize.toInt()) { it.toByte() }
204+ val actualBytes = request.body.readByteArray()
205+ assertArrayEquals(expectedBytes, actualBytes)
206+
207+ return MockResponse ()
208+ .setResponseCode(201 )
209+ .addHeader(" Tus-Resumable" , " 1.0.0" )
210+ .addHeader(" Location" , locationPath)
211+ .addHeader(" Upload-Offset" , bodySize.toString())
212+ }
213+ return MockResponse ().setResponseCode(404 )
214+ }
215+ }
216+
217+ val create = CreateTusUploadRemoteOperation (
218+ file = localFile,
219+ remotePath = " /test-with-data.bin" ,
220+ mimetype = " application/octet-stream" ,
221+ metadata = mapOf (" filename" to " test-with-data.bin" ),
222+ useCreationWithUpload = true ,
223+ firstChunkSize = firstChunkSize,
224+ tusUrl = null ,
225+ collectionUrlOverride = server.url(collectionPath).toString(),
226+ base64Encoder = object : Base64Encoder {
227+ override fun encode (bytes : ByteArray ): String =
228+ Base64 .getEncoder().encodeToString(bytes)
229+ }
230+ )
231+
232+ val createResult = create.execute(client)
233+ assertTrue(" Create operation failed" , createResult.isSuccess)
234+
235+ val creationResult = createResult.data
236+ assertNotNull(creationResult)
237+ assertEquals(firstChunkSize, creationResult!! .uploadOffset)
238+ assertTrue(creationResult.uploadUrl.endsWith(locationPath))
239+
240+ // Verify POST request
241+ val postReq = server.takeRequest()
242+ assertEquals(" POST" , postReq.method)
243+ assertEquals(" Bearer $token " , postReq.getHeader(" Authorization" ))
244+ assertEquals(" 1.0.0" , postReq.getHeader(" Tus-Resumable" ))
245+ // creation-with-upload sends Content-Type and Content-Length for the chunk
246+ assertEquals(" application/offset+octet-stream" , postReq.getHeader(" Content-Type" ))
247+ assertEquals(firstChunkSize.toString(), postReq.getHeader(" Content-Length" ))
248+ }
249+
182250 @Test
183251 fun patch_wrong_offset_returns_conflict () {
184252 val client = newClient()
0 commit comments