package com.peachpasswords.sync import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test /** * Frozen local-identity schema (campaign task 2.1): round trip, legacy * completion, and every malformed-shape rejection. These run on the JVM because * the codec is storage-agnostic; `DeviceIdentity` only adapts it to * EncryptedSharedPreferences. */ class LocalIdentityRecordTest { @Test fun round_trips_every_frozen_field() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, createdAt = 1_700_000_000_000L) val decoded = readIdentityRecord(fields) assertTrue(decoded is IdentityRecord.Current) val current = decoded as IdentityRecord.Current assertEquals("device-a", current.credentials.deviceId) assertTrue(current.credentials.x25519PrivateKey.contentEquals(credentials.x25519PrivateKey)) assertTrue(current.credentials.x25519PublicKey.contentEquals(credentials.x25519PublicKey)) assertTrue(current.credentials.ed25519Seed.contentEquals(credentials.ed25519Seed)) assertTrue(current.credentials.ed25519PublicKey.contentEquals(credentials.ed25519PublicKey)) assertEquals(1_700_000_000_000L, current.createdAt) } @Test fun no_stored_fields_is_absent() { assertEquals(IdentityRecord.Absent, readIdentityRecord(emptyMap())) } @Test fun legacy_random_record_completes_without_changing_the_identity() { val credentials = randomLocalIdentityCredentials("legacy-id") val fields = mapOf( FIELD_VERSION to LOCAL_IDENTITY_RECORD_VERSION_LEGACY_RANDOM, FIELD_DEVICE_ID to credentials.deviceId, FIELD_X25519_PRIVATE to pubkeyToHex(credentials.x25519PrivateKey), FIELD_ED25519_SEED to pubkeyToHex(credentials.ed25519Seed), ) val decoded = readIdentityRecord(fields) assertTrue(decoded is IdentityRecord.LegacyRandom) val legacy = decoded as IdentityRecord.LegacyRandom assertEquals("legacy-id", legacy.credentials.deviceId) assertTrue(legacy.credentials.x25519PrivateKey.contentEquals(credentials.x25519PrivateKey)) assertTrue(legacy.credentials.ed25519Seed.contentEquals(credentials.ed25519Seed)) } @Test fun unknown_version_is_invalid() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, 1L) + (FIELD_VERSION to 99) assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun stored_public_key_that_does_not_match_the_private_key_is_invalid() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, 1L) + (FIELD_X25519_PUBLIC to "aa".repeat(32)) assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun missing_creation_time_is_invalid() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, 1L) - FIELD_CREATED_AT assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun missing_stored_public_value_is_invalid() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, 1L) - FIELD_ED25519_PUBLIC assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun private_material_without_a_version_is_invalid() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, 1L) - FIELD_VERSION assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun partial_private_material_is_invalid() { val fields = mapOf( FIELD_VERSION to LOCAL_IDENTITY_RECORD_VERSION, FIELD_DEVICE_ID to "device-a", FIELD_ED25519_SEED to "22".repeat(32), ) assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun malformed_private_hex_is_invalid() { val fields = mapOf( FIELD_VERSION to LOCAL_IDENTITY_RECORD_VERSION, FIELD_DEVICE_ID to "device-a", FIELD_X25519_PRIVATE to "not-hex", FIELD_ED25519_SEED to "22".repeat(32), ) assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } @Test fun blank_device_id_is_invalid() { val credentials = randomLocalIdentityCredentials("device-a") val fields = identityRecordFields(credentials, 1L) + (FIELD_DEVICE_ID to "") assertEquals(IdentityRecord.Invalid, readIdentityRecord(fields)) } }