diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/lotw/LoTWConfigTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/lotw/LoTWConfigTest.kt new file mode 100644 index 00000000..647e93d8 --- /dev/null +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/lotw/LoTWConfigTest.kt @@ -0,0 +1,78 @@ +/* + * Look4Sat. Amateur radio satellite tracker and pass predictor. + * Copyright (C) 2019-2026 Arty Bishop and contributors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ +package com.rtbishop.look4sat.core.data.lotw + +import com.rtbishop.look4sat.core.domain.repository.LoTWZonePair +import java.io.File +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +/** Parses the real bundled ARRL config.tq6 to verify region-field and zonemap extraction. */ +class LoTWConfigTest { + + private val config = LoTWConfig(File("src/main/assets/lotw/config.tq6").inputStream()) + + @Test + fun `china exposes province field with all 31 options and zonemaps`() { + val meta = config.stationMeta(318) + assertEquals("CN_PROVINCE", meta.regionField?.id) + assertEquals("Province", meta.regionField?.label) + assertEquals(31, meta.regionField?.options?.size) + val gd = meta.regionField?.options?.first { it.code == "GD" } + assertEquals("Guangdong", gd?.name) + assertEquals(listOf(LoTWZonePair(44, 24)), gd?.zones) + val gx = meta.regionField?.options?.first { it.code == "GX" } + assertEquals(listOf(LoTWZonePair(43, 24), LoTWZonePair(44, 24)), gx?.zones) + // China spans 8 zone pairs — the UI must NOT prefill from the national map. + assertTrue(meta.countryZones.size > 1) + } + + @Test + fun `usa exposes state field with 49 continental options`() { + val meta = config.stationMeta(291) + assertEquals("US_STATE", meta.regionField?.id) + assertEquals("State", meta.regionField?.label) + // AK and HI are separate DXCC entities (6 and 110) with their own 1-option blocks. + assertEquals(49, meta.regionField?.options?.size) + assertTrue(meta.regionField!!.options.none { it.code == "AK" || it.code == "HI" }) + assertTrue(meta.countryZones.size > 1) + } + + @Test + fun `alaska and hawaii each expose their own single-state block`() { + val ak = config.stationMeta(6) + assertEquals(listOf("AK"), ak.regionField?.options?.map { it.code }) + val hi = config.stationMeta(110) + assertEquals(listOf("HI"), hi.regionField?.options?.map { it.code }) + } + + @Test + fun `germany has no region field and one national zone pair`() { + val meta = config.stationMeta(230) + assertNull(meta.regionField) + assertEquals(listOf(LoTWZonePair(28, 14)), meta.countryZones) + } + + @Test + fun `india has no region field and one national zone pair`() { + val meta = config.stationMeta(324) + assertNull(meta.regionField) + assertEquals(listOf(LoTWZonePair(41, 22)), meta.countryZones) + } + + @Test + fun `unknown entity yields empty meta`() { + val meta = config.stationMeta(999999) + assertNull(meta.regionField) + assertTrue(meta.countryZones.isEmpty()) + } +} diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/lotw/Pkcs12ReaderTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/lotw/Pkcs12ReaderTest.kt new file mode 100644 index 00000000..ebaa9711 --- /dev/null +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/lotw/Pkcs12ReaderTest.kt @@ -0,0 +1,73 @@ +/* + * Look4Sat. Amateur radio satellite tracker and pass predictor. + * Copyright (C) 2019-2026 Arty Bishop and contributors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.rtbishop.look4sat.core.data.lotw + +import java.security.Signature +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Verifies the PBES2/AES-256-CBC PKCS12 reader against a fixture generated with + * `openssl pkcs12 -export -certpbe NONE` — same layout as modern TQSL exports + * (plain certificate bags + PBES2-shrouded private key), which Android's legacy + * Bouncy Castle parser cannot read. + */ +class Pkcs12ReaderTest { + + private fun fixture(): ByteArray = + javaClass.classLoader!!.getResourceAsStream("test_pbes2.p12")!!.use { it.readBytes() } + + @Test + fun readsPbes2KeyAndCertificate() { + val (key, cert) = Pkcs12Reader.read(fixture(), "testpass123".toCharArray()) + assertEquals("RSA", key.algorithm) + assertEquals("RSA", cert.publicKey.algorithm) + assertNotNull(cert.subjectX500Principal) + } + + @Test + fun keyPairsWithCertificate() { + val (key, cert) = Pkcs12Reader.read(fixture(), "testpass123".toCharArray()) + val challenge = "Look4Sat LoTW certificate key check".toByteArray(Charsets.US_ASCII) + val signed = Signature.getInstance("SHA1withRSA").run { + initSign(key); update(challenge); sign() + } + val valid = Signature.getInstance("SHA1withRSA").run { + initVerify(cert); update(challenge); verify(signed) + } + assertTrue("private key must match certificate", valid) + } + + @Test + fun wrongPasswordThrows() { + assertThrows(Exception::class.java) { + Pkcs12Reader.read(fixture(), "wrongpass".toCharArray()) + } + } + + @Test + fun garbageBytesThrows() { + assertThrows(Exception::class.java) { + Pkcs12Reader.read("not a p12 file at all".toByteArray(), "x".toCharArray()) + } + } +} diff --git a/core/data/src/test/resources/test_pbes2.p12 b/core/data/src/test/resources/test_pbes2.p12 new file mode 100644 index 00000000..c9c160ab Binary files /dev/null and b/core/data/src/test/resources/test_pbes2.p12 differ