Files
SyncFlow/app/src/androidTest/kotlin/com/syncflow/SftpProviderTest.kt
T
amir 10007eb4fb
Build & Release APK / build (push) Successful in 12m50s
Add interruption/atomicity, SFTP, and scheduling tests
- Interruption: failed mid-write leaves original intact (no truncation, no temp
  leftover); a sync that drops after N files resumes cleanly on the next sync
  with all content byte-intact (real network-drop simulation).
- SFTP: live round-trip test against an SFTP server (connect/upload-atomic/
  list/download/overwrite/special-name/delete); skips if endpoint unreachable.
- Scheduling: WorkManager request builders map Wi-Fi-only -> UNMETERED,
  charging-only -> requiresCharging, interval, input data, and tags correctly.
2026-06-05 15:16:10 +00:00

70 lines
3.2 KiB
Kotlin

package com.syncflow
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.syncflow.data.providers.sftp.SftpProvider
import com.syncflow.data.security.CredentialStore
import com.syncflow.domain.model.CloudAccount
import com.syncflow.domain.model.ProviderType
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Test
import org.junit.runner.RunWith
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
/**
* Live SFTP test (the other major provider code path: sshj). Runs against a throwaway SFTP
* server. Skips unless -e sftpHost/sftpPort/sftpUser/sftpPass are provided.
*/
@RunWith(AndroidJUnit4::class)
class SftpProviderTest {
private val ctx = InstrumentationRegistry.getInstrumentation().targetContext
private val args = InstrumentationRegistry.getArguments()
private fun provider() = SftpProvider(
CloudAccount(
id = 1, displayName = "sftp", email = null, providerType = ProviderType.SFTP,
credentialJson = """{"username":"${args.getString("sftpUser")}","password":"${args.getString("sftpPass")}"}""",
serverUrl = args.getString("sftpHost"), port = args.getString("sftpPort")?.toInt(),
),
CredentialStore(ctx),
)
@Test fun sftpFullRoundTrip() = runBlocking {
assumeTrue("sftp* args required", args.getString("sftpHost") != null)
val p = provider()
val dir = "upload/it_${System.currentTimeMillis()}"
// Skip (don't fail) if the endpoint isn't reachable from the test runner's network —
// e.g. a phone on an isolated VLAN that only reaches services via the reverse proxy.
assumeTrue("SFTP endpoint not reachable from this device's network", p.testConnection().isSuccess)
assertTrue("mkdir", p.createDirectory(dir).isSuccess)
// upload (atomic temp + rename), list, download
val body = "sftp round-trip ✓".toByteArray()
assertTrue("upload", p.uploadFile(ByteArrayInputStream(body), "$dir/f.txt", body.size.toLong()).isSuccess)
assertTrue("f.txt" in p.listFiles(dir).getOrThrow().map { it.name })
val out = ByteArrayOutputStream(); p.downloadFile("$dir/f.txt", out).getOrThrow()
assertEquals("sftp round-trip ✓", out.toString("UTF-8"))
// atomic overwrite (temp + rename over existing)
val v2 = "updated-content".toByteArray()
assertTrue(p.uploadFile(ByteArrayInputStream(v2), "$dir/f.txt", v2.size.toLong()).isSuccess)
val out2 = ByteArrayOutputStream(); p.downloadFile("$dir/f.txt", out2).getOrThrow()
assertEquals("updated-content", out2.toString("UTF-8"))
// special / non-ASCII name (SFTP handles UTF-8 natively, no URL encoding)
val special = "café & rapport (1).txt"
assertTrue(p.uploadFile(ByteArrayInputStream("x".toByteArray()), "$dir/$special", 1).isSuccess)
assertTrue(special in p.listFiles(dir).getOrThrow().map { it.name })
// delete
assertTrue(p.deleteFile("$dir/f.txt").isSuccess)
assertTrue("f.txt" !in p.listFiles(dir).getOrThrow().map { it.name })
}
}