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 }) } }