package com.osglab.account.config import io.ktor.server.config.MapApplicationConfig import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.util.Base64 class AppConfigTest : FunSpec({ test("test configuration can be injected without Apple client credentials") { val config = AppConfig.from(validConfig("test")) config.environment shouldBe Environment.TEST config.apple.clientCredentialsAvailable shouldBe false config.encryption.key.size shouldBe 32 config.credits.signupTrial shouldBe 1_000 config.credits.referralInviter shouldBe 1_000 config.credits.referralInvitee shouldBe 1_000 } test("production rejects placeholder secrets") { val config = validProductionConfig().apply { put("app.session.secret", "replace-with-secret") } shouldThrow { AppConfig.from(config) } } test("production accepts complete independent configuration") { val config = AppConfig.from(validProductionConfig()) config.environment shouldBe Environment.PRODUCTION config.database.username shouldBe "test" config.database.migrationUsername shouldBe "test_migrator" } test("production accepts enabled admin bootstrap with Argon2 PHC hash") { val config = validProductionConfig().apply { put("app.admin.enabled", "true") put("app.admin.bootstrapEnabled", "true") put("app.admin.bootstrapOperatorId", "2c031def-4517-4fde-b592-5db3a3eefdf6") put("app.admin.bootstrapUsername", "owner") put( "app.admin.bootstrapPasswordHash", "\$argon2id\$v=19\$m=65536,t=3,p=1\$c2FsdHNhbHRzYWx0c2FsdA\$aGFzaGhhc2hoYXNoaGFzaGhhc2hoYXNoaGFzaA", ) put("app.admin.bootstrapTotpSecretBase32", "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP") } val admin = AppConfig.from(config).admin admin.enabled shouldBe true admin.bootstrapEnabled shouldBe true } test("production accepts established admin without bootstrap credentials") { val config = validProductionConfig().apply { put("app.admin.enabled", "true") put("app.admin.bootstrapEnabled", "false") } val admin = AppConfig.from(config).admin admin.enabled shouldBe true admin.bootstrapEnabled shouldBe false admin.bootstrapTotpSecretBase32 shouldBe null } test("admin bootstrap cannot be enabled while admin routes are disabled") { val config = validProductionConfig().apply { put("app.admin.enabled", "false") put("app.admin.bootstrapEnabled", "true") } shouldThrow { AppConfig.from(config) }.message.orEmpty() shouldContain "bootstrapEnabled requires" } test("StoreKit requires an app identifier and dedicated credit product when enabled") { val missingAppId = validProductionConfig().apply { put("app.storeKit.enabled", "true") put("app.storeKit.products", "500tks:500,1500tks:1500,3000tks:3000") } shouldThrow { AppConfig.from(missingAppId) }.message.orEmpty() shouldContain "appAppleId is required" val enabled = validProductionConfig().apply { put("app.storeKit.enabled", "true") put("app.storeKit.appAppleId", "6781553267") put("app.storeKit.products", "500tks:500,1500tks:1500,3000tks:3000") } val storeKit = AppConfig.from(enabled).storeKit storeKit.enabled shouldBe true storeKit.appAppleId shouldBe 6_781_553_267 storeKit.products.map { it.productId to it.credits } shouldBe listOf( "500tks" to 500, "1500tks" to 1_500, "3000tks" to 3_000, ) } test("StoreKit rejects duplicate product mappings") { val config = validProductionConfig().apply { put("app.storeKit.enabled", "true") put("app.storeKit.appAppleId", "6781553267") put("app.storeKit.products", "500tks:500,500tks:3000") } shouldThrow { AppConfig.from(config) }.message.orEmpty() shouldContain "duplicate product IDs" } test("production fails fast when Apple signing credentials are missing") { val config = validProductionConfig().apply { put("app.apple.keyId", "") } shouldThrow { AppConfig.from(config) }.message.orEmpty() shouldContain "app.apple.keyId" } test("production rejects monitor-only integrity configuration") { val config = validProductionConfig().apply { put("app.providers.volcengine.apiKey", "volcengine-key") put("app.providers.deepseek.apiKey", "deepseek-key") put("app.integrity.enforceDeviceCheck", "false") put("app.integrity.enforceAppAttest", "false") } shouldThrow { AppConfig.from(config) }.message.orEmpty() shouldContain "must enforce both DeviceCheck and App Attest" } test("production rejects provider endpoints outside the exact host allowlist") { val config = validProductionConfig().apply { put("app.providers.deepseek.endpoint", "https://127.0.0.1/v1") } shouldThrow { AppConfig.from(config) }.message.orEmpty() shouldContain "DeepSeek endpoint" } test("production requires separate migration credentials") { val missingMigrator = validProductionConfig().apply { put("app.database.migrationUsername", "") } shouldThrow { AppConfig.from(missingMigrator) }.message.orEmpty() shouldContain "app.database.migrationUsername" val reusedPassword = validProductionConfig().apply { put("app.database.migrationPassword", "database-password") } shouldThrow { AppConfig.from(reusedPassword) }.message.orEmpty() shouldContain "passwords must be distinct" } test("production requires independent cryptographic secrets") { val config = validProductionConfig().apply { put( "app.antiAbuse.identityHmacKeyBase64", Base64.getEncoder().encodeToString(ByteArray(32) { 7 }), ) } shouldThrow { AppConfig.from(config) }.message.orEmpty() shouldContain "must be distinct" } test("production requires exact public and App Store URLs") { val publicUrl = validProductionConfig().apply { put("app.publicBaseUrl", "https://account.osglab.com.evil.example") } shouldThrow { AppConfig.from(publicUrl) }.message.orEmpty() shouldContain "PUBLIC_BASE_URL" val appStoreUrl = validProductionConfig().apply { put("app.appStoreUrl", "https://apps.apple.com/app/id0000000000") } shouldThrow { AppConfig.from(appStoreUrl) }.message.orEmpty() shouldContain "APP_STORE_URL" } }) private fun validConfig(environment: String) = MapApplicationConfig( "app.environment" to environment, "app.database.jdbcUrl" to "jdbc:mysql://localhost:3306/test", "app.database.username" to "test", "app.database.password" to "database-password", "app.database.migrationUsername" to "test_migrator", "app.database.migrationPassword" to "migration-password", "app.database.maximumPoolSize" to "4", "app.session.issuer" to "https://issuer.example", "app.session.audience" to "ios-app", "app.session.secret" to "01234567890123456789012345678901", "app.session.accessMinutes" to "15", "app.session.refreshDays" to "30", "app.encryption.keyBase64" to Base64.getEncoder().encodeToString(ByteArray(32) { 7 }), "app.antiAbuse.identityHmacKeyBase64" to Base64.getEncoder().encodeToString(ByteArray(32) { 8 }), "app.apple.clientId" to "com.example.app", "app.apple.jwksUrl" to "https://appleid.apple.com/auth/keys", "app.apple.tokenUrl" to "https://appleid.apple.com/auth/token", "app.apple.revokeUrl" to "https://appleid.apple.com/auth/revoke", "app.integrity.enforceDeviceCheck" to "false", "app.integrity.enforceAppAttest" to "false", ) private fun validProductionConfig() = validConfig("production").apply { put("app.publicBaseUrl", "https://account.osglab.com") put("app.inviteBaseUrl", "https://osglab.com/i") put("app.appStoreUrl", "https://apps.apple.com/app/id1234567890") put("app.apple.teamId", APP_ATTEST_TEAM_ID) put("app.apple.keyId", "APPLE_KEY") put("app.apple.clientId", APP_ATTEST_BUNDLE_ID) put("app.apple.privateKeyPem", "private-key-material") put("app.integrity.appleEnvironment", "production") put("app.integrity.enforceDeviceCheck", "true") put("app.integrity.enforceAppAttest", "true") put("app.providers.volcengine.apiKey", "volcengine-key") put("app.providers.deepseek.apiKey", "deepseek-key") }