// file: crates/ksp-store-postgres-lib/unit_tests/migration.rs // version: 3 fn applied(version: i64, name: &str, checksum: &str) -> super::AppliedMigration { return super::AppliedMigration { checksum: checksum.to_owned(), name: name.to_owned(), version }; } fn embedded(version: i64, name: &'static str, sql: &'static str) -> super::EmbeddedMigration { return super::EmbeddedMigration { hook: super::MigrationHook::None, name, sql, version }; } #[test] fn pre_003_embedded_registry_keeps_v000_immutable_and_adds_exact_v001() { assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 2); let v000 = &super::EMBEDDED_MIGRATIONS[0]; assert_eq!(v000.version, 0); assert_eq!(v000.name, "bootstrap"); assert_eq!(v000.hook, super::MigrationHook::None); assert!(v000.sql.contains("CREATE TABLE ksp_store_schema_migrations")); for forbidden in ["RawTransaction", "RawAccountState", "raw_transaction", "raw_account", "CORE", "DECODE", "SPECIALIZED"] { assert!(!v000.sql.contains(forbidden), "business schema leaked into immutable V000 SQL: {forbidden}"); } assert_eq!(super::migration_checksum(v000.sql), "d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450"); let v001 = &super::EMBEDDED_MIGRATIONS[1]; assert_eq!(v001.version, 1); assert_eq!(v001.name, "raw_transaction"); assert_eq!(v001.hook, super::MigrationHook::StoreIdentity); assert_eq!(super::migration_checksum(v001.sql), "6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761"); assert!(super::validate_embedded_registry(super::EMBEDDED_MIGRATIONS).is_ok()); assert_eq!(crate::current_migration_version(), 1); return; } #[test] fn pre_003_v001_inventory_indexes_and_api_bounds_are_exact() { let sql = super::EMBEDDED_MIGRATIONS[1].sql; for required in [ "CREATE TABLE ksp_store_identity", "CREATE TABLE ksp_raw_transactions", "CREATE TABLE ksp_raw_transaction_observations", "CREATE TABLE ksp_raw_transaction_archive_payloads", "CREATE INDEX ix_ksp_raw_transactions_slot_signature", "WHERE retention_state <> 'purged'", "octet_length(signature) = 64", "slot BETWEEN 0 AND 18446744073709551615", "block_time_unix_millis BETWEEN 0 AND 253402300799999", "octet_length(content_hash) = 32", "octet_length(payload) BETWEEN 1 AND 16777216", "format_version BETWEEN 1 AND 4294967295", "received_at_unix_millis BETWEEN 0 AND 253402300799999", "source_payload_size_bytes BETWEEN 0 AND 67108864", "origin IN ('backfill', 'import', 'live', 'repair', 'replay')", "retention_state IN ('full', 'archived', 'purged')", ] { assert!(sql.contains(required), "V001 physical contract is missing: {required}"); } assert_eq!(sql.matches("PRIMARY KEY").count(), 4); assert_eq!(sql.matches("REFERENCES ksp_raw_transactions(signature) ON DELETE RESTRICT").count(), 2); assert!(!sql.contains("compacted")); assert!(!sql.contains("BIGSERIAL")); assert!(!sql.contains("slot BIGINT")); assert!(sql.contains("network TEXT NOT NULL")); return; } #[test] fn pre_003_ordered_registry_accepts_v000_prefix_and_full_v001_history() { let v000 = super::EMBEDDED_MIGRATIONS[0]; let v001 = super::EMBEDDED_MIGRATIONS[1]; let v000_checksum = super::migration_checksum(v000.sql); let prefix = [applied(0, v000.name, v000_checksum.as_str())]; assert_eq!(super::validate_history(&prefix, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(1)); let v001_checksum = super::migration_checksum(v001.sql); let full = [applied(0, v000.name, v000_checksum.as_str()), applied(1, v001.name, v001_checksum.as_str())]; assert_eq!(super::validate_history(&full, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(2)); return; } #[test] fn pre_002_registry_rejects_empty_nonzero_gap_and_empty_metadata_entries() { let empty: [super::EmbeddedMigration; 0] = []; let starts_at_one = [embedded(1, "future", "SELECT 1;")]; let gap = [embedded(0, "bootstrap", "SELECT 0;"), embedded(2, "future", "SELECT 2;")]; let empty_name = [embedded(0, "", "SELECT 0;")]; let empty_sql = [embedded(0, "bootstrap", "")]; for registry in [&empty[..], &starts_at_one[..], &gap[..], &empty_name[..], &empty_sql[..]] { let result = super::validate_embedded_registry(registry); assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::MigrationMismatch)); } return; } #[test] fn pre_003_divergent_missing_or_gapped_history_is_terminal_mismatch() { let v000 = super::EMBEDDED_MIGRATIONS[0]; let v001 = super::EMBEDDED_MIGRATIONS[1]; let v000_checksum = super::migration_checksum(v000.sql); let v001_checksum = super::migration_checksum(v001.sql); let wrong_name = [applied(0, "changed", v000_checksum.as_str())]; let wrong_checksum = [applied(0, v000.name, "00")]; let missing: [super::AppliedMigration; 0] = []; let missing_v000 = [applied(1, v001.name, v001_checksum.as_str())]; for history in [&wrong_name[..], &wrong_checksum[..], &missing[..], &missing_v000[..]] { let result = super::validate_history(history, super::EMBEDDED_MIGRATIONS); assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::MigrationMismatch)); } return; } #[test] fn pre_003_newer_history_is_rejected_without_down_migration() { let v000 = super::EMBEDDED_MIGRATIONS[0]; let v001 = super::EMBEDDED_MIGRATIONS[1]; let v000_checksum = super::migration_checksum(v000.sql); let v001_checksum = super::migration_checksum(v001.sql); let history = [ applied(0, v000.name, v000_checksum.as_str()), applied(1, v001.name, v001_checksum.as_str()), applied(2, "future", "future-checksum"), ]; let result = super::validate_history(&history, super::EMBEDDED_MIGRATIONS); assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::SchemaNewer)); return; }