Files
FlClash/android/tests/service/VpnOptionsTest.kt
T
chen08209 ae29f38f6b feat(android): rework the VPN service, tile, lifecycle arbitration, and native build
ServiceState serializes start, stop, and restart intents so the latest
request wins, and switching between VPN and proxy mode tears the previous
service down the way stop() does before binding the next one, so a
system-started VpnService no longer outlives the switch. The package
resolver drops its cache on package add, replace, and remove broadcasts
and notifies Flutter, and the persistent notification can omit its stop
action through an application setting synced over the shared state.

The :core module takes the NDK clang directory and API level from the
setup build hook input instead of ANDROID_NDK and a hardcoded API 21, and
derives its ABI list from the target-platform property so a single-ABI
build no longer links ABIs the hook did not produce.

Changelog: Rework the Android VPN service and lifecycle handling
2026-09-10 12:42:59 +08:00

145 lines
4.0 KiB
Kotlin

package com.follow.clash.service.models
import com.follow.clash.common.AccessControlMode
import java.net.Inet4Address
import java.net.Inet6Address
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test
private fun optionsWithRoutes(routeAddress: List<String>) = VpnOptions(
enable = true,
port = 7890,
ipv6 = true,
dnsHijacking = false,
accessControlProps = AccessControlProps(
enable = false,
mode = AccessControlMode.ACCEPT_SELECTED,
acceptList = emptyList(),
rejectList = emptyList(),
),
allowBypass = false,
systemProxy = true,
bypassDomain = emptyList(),
stack = "gvisor",
routeAddress = routeAddress,
)
class ToCIDRTest {
@Test
fun `parses an IPv4 network`() {
val cidr = "192.168.1.0/24".toCIDR()
assertTrue(cidr.address is Inet4Address)
assertEquals("192.168.1.0", cidr.address.hostAddress)
assertEquals(24, cidr.prefixLength)
}
@Test
fun `parses an IPv6 network`() {
val cidr = "fd00::/8".toCIDR()
assertTrue(cidr.address is Inet6Address)
assertEquals(8, cidr.prefixLength)
}
@Test
fun `accepts the boundary prefix lengths`() {
assertEquals(0, "0.0.0.0/0".toCIDR().prefixLength)
assertEquals(32, "10.0.0.1/32".toCIDR().prefixLength)
assertEquals(0, "::/0".toCIDR().prefixLength)
assertEquals(128, "fd00::1/128".toCIDR().prefixLength)
}
@Test
fun `rejects an address without a prefix`() {
val error = assertThrows(IllegalArgumentException::class.java) {
"192.168.1.0".toCIDR()
}
assertTrue(error.message!!.contains("Invalid CIDR format"))
}
@Test
fun `rejects an address with too many segments`() {
assertThrows(IllegalArgumentException::class.java) {
"192.168.1.0/24/8".toCIDR()
}
}
@Test
fun `rejects a non-numeric prefix`() {
val error = assertThrows(IllegalArgumentException::class.java) {
"192.168.1.0/abc".toCIDR()
}
assertTrue(error.message!!.contains("Invalid prefix length"))
}
@Test
fun `rejects an IPv4 prefix above 32`() {
assertThrows(IllegalArgumentException::class.java) {
"192.168.1.0/33".toCIDR()
}
}
@Test
fun `rejects an IPv6 prefix above 128`() {
assertThrows(IllegalArgumentException::class.java) {
"fd00::/129".toCIDR()
}
}
@Test
fun `rejects a negative prefix`() {
assertThrows(IllegalArgumentException::class.java) {
"192.168.1.0/-1".toCIDR()
}
}
}
class RouteAddressTest {
@Test
fun `splits a mixed route list by address family`() {
val options = optionsWithRoutes(
listOf("192.168.1.0/24", "fd00::/8", "10.0.0.0/8", "2000::/3"),
)
val ipv4 = options.getIpv4RouteAddress()
val ipv6 = options.getIpv6RouteAddress()
assertEquals(2, ipv4.size)
assertEquals(2, ipv6.size)
assertTrue(ipv4.all { it.address is Inet4Address })
assertTrue(ipv6.all { it.address is Inet6Address })
}
@Test
fun `returns empty lists for an empty route list`() {
val options = optionsWithRoutes(emptyList())
assertTrue(options.getIpv4RouteAddress().isEmpty())
assertTrue(options.getIpv6RouteAddress().isEmpty())
}
@Test
fun `preserves the configured order within a family`() {
val options = optionsWithRoutes(
listOf("10.0.0.0/8", "fd00::/8", "192.168.0.0/16"),
)
assertEquals(
listOf(8, 16),
options.getIpv4RouteAddress().map { it.prefixLength },
)
}
@Test
fun `propagates a malformed entry instead of silently dropping it`() {
val options = optionsWithRoutes(listOf("192.168.1.0/24", "not-a-cidr"))
assertThrows(IllegalArgumentException::class.java) {
options.getIpv4RouteAddress()
}
}
}