-- ================================================================================================ -- Schema Validation Test Suite -- PostgreSQL 16.10 + TimescaleDB 2.22.1 -- Validates all required tables, constraints, indexes, and types exist -- ================================================================================================ BEGIN; -- ================================================================================================ -- TEST 1: Core Tables Existence -- Verify all critical tables are created -- ================================================================================================ DO $$ DECLARE v_required_tables TEXT[] := ARRAY[ 'trading_events', 'risk_events', 'audit_events', 'orders', 'executions', 'positions', 'accounts', 'users', 'roles', 'permissions', 'user_roles', 'role_permissions', 'api_keys', 'jwt_revocations', 'rate_limits', 'compliance_reports', 'market_data_raw', 'market_data_aggregated', 'config_parameters', 'config_history', 'symbols', 'symbol_metadata' ]; v_table TEXT; v_missing_tables TEXT[] := ARRAY[]::TEXT[]; v_found_count INTEGER := 0; BEGIN FOREACH v_table IN ARRAY v_required_tables LOOP IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = v_table ) THEN v_found_count := v_found_count + 1; ELSE v_missing_tables := array_append(v_missing_tables, v_table); END IF; END LOOP; IF array_length(v_missing_tables, 1) IS NULL THEN RAISE NOTICE 'TEST 1 PASS: All % required tables exist', array_length(v_required_tables, 1); ELSE RAISE WARNING 'TEST 1 WARNING: Missing tables (% of %): %', array_length(v_missing_tables, 1), array_length(v_required_tables, 1), array_to_string(v_missing_tables, ', '); END IF; END $$; -- ================================================================================================ -- TEST 2: Required Enums Validation -- Verify all enum types are defined -- ================================================================================================ DO $$ DECLARE v_required_enums TEXT[] := ARRAY[ 'trading_event_type', 'order_side', 'order_type', 'time_in_force', 'order_status', 'execution_type', 'risk_event_type', 'risk_severity', 'risk_metric_type', 'audit_event_type', 'audit_severity', 'system_component', 'account_type', 'position_side', 'execution_status' ]; v_enum TEXT; v_missing_enums TEXT[] := ARRAY[]::TEXT[]; BEGIN FOREACH v_enum IN ARRAY v_required_enums LOOP IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = v_enum AND typtype = 'e' ) THEN v_missing_enums := array_append(v_missing_enums, v_enum); END IF; END LOOP; IF array_length(v_missing_enums, 1) IS NULL THEN RAISE NOTICE 'TEST 2 PASS: All % required enum types exist', array_length(v_required_enums, 1); ELSE RAISE WARNING 'TEST 2 WARNING: Missing enum types: %', array_to_string(v_missing_enums, ', '); END IF; END $$; -- ================================================================================================ -- TEST 3: Primary Key Constraints -- Verify all tables have primary keys -- ================================================================================================ DO $$ DECLARE v_table_record RECORD; v_tables_without_pk TEXT[] := ARRAY[]::TEXT[]; v_checked_count INTEGER := 0; BEGIN FOR v_table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' AND table_name NOT LIKE '%_old' AND table_name NOT LIKE '%_backup' LOOP v_checked_count := v_checked_count + 1; IF NOT EXISTS ( SELECT 1 FROM information_schema.table_constraints WHERE table_schema = 'public' AND table_name = v_table_record.table_name AND constraint_type = 'PRIMARY KEY' ) THEN v_tables_without_pk := array_append(v_tables_without_pk, v_table_record.table_name); END IF; END LOOP; IF array_length(v_tables_without_pk, 1) IS NULL THEN RAISE NOTICE 'TEST 3 PASS: All % tables have primary key constraints', v_checked_count; ELSE RAISE WARNING 'TEST 3 WARNING: Tables without primary keys: %', array_to_string(v_tables_without_pk, ', '); END IF; END $$; -- ================================================================================================ -- TEST 4: Foreign Key Constraints -- Verify critical foreign key relationships -- ================================================================================================ DO $$ DECLARE v_required_fks TEXT[][] := ARRAY[ ARRAY['orders', 'account_id', 'accounts'], ARRAY['executions', 'order_id', 'orders'], ARRAY['positions', 'account_id', 'accounts'], ARRAY['user_roles', 'user_id', 'users'], ARRAY['user_roles', 'role_id', 'roles'], ARRAY['role_permissions', 'role_id', 'roles'], ARRAY['role_permissions', 'permission_id', 'permissions'], ARRAY['api_keys', 'user_id', 'users'] ]; v_fk TEXT[]; v_missing_fks TEXT[] := ARRAY[]::TEXT[]; v_fk_desc TEXT; BEGIN FOREACH v_fk SLICE 1 IN ARRAY v_required_fks LOOP v_fk_desc := v_fk[1] || '.' || v_fk[2] || ' -> ' || v_fk[3]; IF NOT EXISTS ( SELECT 1 FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = v_fk[1] AND kcu.column_name = v_fk[2] AND ccu.table_name = v_fk[3] ) THEN -- Check if table and column exist before marking as missing FK IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = v_fk[1]) AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = v_fk[1] AND column_name = v_fk[2]) AND EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = v_fk[3]) THEN v_missing_fks := array_append(v_missing_fks, v_fk_desc); END IF; END IF; END LOOP; IF array_length(v_missing_fks, 1) IS NULL THEN RAISE NOTICE 'TEST 4 PASS: All critical foreign key relationships verified'; ELSE RAISE WARNING 'TEST 4 WARNING: Missing foreign keys: %', array_to_string(v_missing_fks, ', '); END IF; END $$; -- ================================================================================================ -- TEST 5: Index Coverage - Performance Indexes -- Verify critical indexes exist for query performance -- ================================================================================================ DO $$ DECLARE v_required_indexes TEXT[][] := ARRAY[ ARRAY['trading_events', 'symbol'], ARRAY['trading_events', 'event_type'], ARRAY['trading_events', 'correlation_id'], ARRAY['risk_events', 'symbol'], ARRAY['risk_events', 'account_id'], ARRAY['risk_events', 'severity'], ARRAY['audit_events', 'user_id'], ARRAY['audit_events', 'component'], ARRAY['orders', 'symbol'], ARRAY['orders', 'account_id'], ARRAY['orders', 'status'], ARRAY['executions', 'order_id'], ARRAY['positions', 'account_id'], ARRAY['positions', 'symbol'] ]; v_idx TEXT[]; v_missing_indexes TEXT[] := ARRAY[]::TEXT[]; v_idx_desc TEXT; BEGIN FOREACH v_idx SLICE 1 IN ARRAY v_required_indexes LOOP v_idx_desc := v_idx[1] || '.' || v_idx[2]; IF NOT EXISTS ( SELECT 1 FROM pg_indexes WHERE tablename = v_idx[1] AND indexdef LIKE '%' || v_idx[2] || '%' ) THEN -- Check if table exists before marking as missing index IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = v_idx[1]) THEN v_missing_indexes := array_append(v_missing_indexes, v_idx_desc); END IF; END IF; END LOOP; IF array_length(v_missing_indexes, 1) IS NULL THEN RAISE NOTICE 'TEST 5 PASS: All critical performance indexes exist'; ELSE RAISE WARNING 'TEST 5 WARNING: Missing indexes: %', array_to_string(v_missing_indexes, ', '); END IF; END $$; -- ================================================================================================ -- TEST 6: NOT NULL Constraints - Critical Fields -- Verify critical fields have NOT NULL constraints -- ================================================================================================ DO $$ DECLARE v_required_not_nulls TEXT[][] := ARRAY[ ARRAY['trading_events', 'event_timestamp'], ARRAY['trading_events', 'event_type'], ARRAY['trading_events', 'symbol'], ARRAY['risk_events', 'event_timestamp'], ARRAY['risk_events', 'severity'], ARRAY['audit_events', 'event_timestamp'], ARRAY['audit_events', 'event_type'], ARRAY['orders', 'symbol'], ARRAY['orders', 'side'], ARRAY['orders', 'order_type'], ARRAY['accounts', 'id'], ARRAY['users', 'username'], ARRAY['users', 'email'] ]; v_nn TEXT[]; v_missing_not_nulls TEXT[] := ARRAY[]::TEXT[]; v_nn_desc TEXT; BEGIN FOREACH v_nn SLICE 1 IN ARRAY v_required_not_nulls LOOP v_nn_desc := v_nn[1] || '.' || v_nn[2]; IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = v_nn[1] AND column_name = v_nn[2] AND is_nullable = 'NO' ) THEN -- Check if column exists IF EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = v_nn[1] AND column_name = v_nn[2] ) THEN v_missing_not_nulls := array_append(v_missing_not_nulls, v_nn_desc); END IF; END IF; END LOOP; IF array_length(v_missing_not_nulls, 1) IS NULL THEN RAISE NOTICE 'TEST 6 PASS: All critical NOT NULL constraints exist'; ELSE RAISE WARNING 'TEST 6 WARNING: Missing NOT NULL constraints: %', array_to_string(v_missing_not_nulls, ', '); END IF; END $$; -- ================================================================================================ -- TEST 7: CHECK Constraints - Data Integrity -- Verify CHECK constraints for data validation -- ================================================================================================ DO $$ DECLARE v_constraint_count INTEGER; BEGIN -- Count CHECK constraints in critical tables SELECT COUNT(*) INTO v_constraint_count FROM information_schema.table_constraints WHERE constraint_type = 'CHECK' AND table_schema = 'public' AND table_name IN ('trading_events', 'risk_events', 'orders', 'executions', 'positions'); IF v_constraint_count > 0 THEN RAISE NOTICE 'TEST 7 PASS: Found % CHECK constraints for data integrity', v_constraint_count; ELSE RAISE WARNING 'TEST 7 WARNING: No CHECK constraints found in critical tables'; END IF; END $$; -- ================================================================================================ -- TEST 8: Unique Constraints - Duplicate Prevention -- Verify unique constraints exist -- ================================================================================================ DO $$ DECLARE v_unique_count INTEGER; BEGIN SELECT COUNT(*) INTO v_unique_count FROM information_schema.table_constraints WHERE constraint_type = 'UNIQUE' AND table_schema = 'public'; IF v_unique_count > 0 THEN RAISE NOTICE 'TEST 8 PASS: Found % UNIQUE constraints', v_unique_count; ELSE RAISE WARNING 'TEST 8 WARNING: No UNIQUE constraints found'; END IF; END $$; -- ================================================================================================ -- TEST 9: Default Values - Auto-population -- Verify critical defaults are set -- ================================================================================================ DO $$ DECLARE v_defaults_with_defaults TEXT[] := ARRAY[]::TEXT[]; v_col_record RECORD; BEGIN FOR v_col_record IN SELECT table_name, column_name, column_default FROM information_schema.columns WHERE table_schema = 'public' AND ( (column_name LIKE '%_id' AND column_default LIKE '%uuid_generate%') OR (column_name LIKE '%_timestamp' AND column_default IS NOT NULL) OR (column_name = 'created_at' AND column_default IS NOT NULL) OR (column_name = 'updated_at' AND column_default IS NOT NULL) ) LOOP v_defaults_with_defaults := array_append(v_defaults_with_defaults, v_col_record.table_name || '.' || v_col_record.column_name); END LOOP; IF array_length(v_defaults_with_defaults, 1) > 0 THEN RAISE NOTICE 'TEST 9 PASS: Found % columns with default values', array_length(v_defaults_with_defaults, 1); ELSE RAISE WARNING 'TEST 9 WARNING: No default values found on expected columns'; END IF; END $$; -- ================================================================================================ -- TEST 10: Database Extensions -- Verify required PostgreSQL extensions are installed -- ================================================================================================ DO $$ DECLARE v_required_extensions TEXT[] := ARRAY[ 'uuid-ossp', 'pgcrypto', 'timescaledb' ]; v_ext TEXT; v_missing_extensions TEXT[] := ARRAY[]::TEXT[]; v_installed_extensions TEXT[] := ARRAY[]::TEXT[]; BEGIN FOREACH v_ext IN ARRAY v_required_extensions LOOP IF EXISTS ( SELECT 1 FROM pg_extension WHERE extname = v_ext ) THEN v_installed_extensions := array_append(v_installed_extensions, v_ext); ELSE v_missing_extensions := array_append(v_missing_extensions, v_ext); END IF; END LOOP; IF array_length(v_missing_extensions, 1) IS NULL THEN RAISE NOTICE 'TEST 10 PASS: All required extensions installed: %', array_to_string(v_installed_extensions, ', '); ELSE RAISE WARNING 'TEST 10 WARNING: Missing extensions: %', array_to_string(v_missing_extensions, ', '); END IF; END $$; -- ================================================================================================ -- Cleanup: Rollback transaction -- ================================================================================================ ROLLBACK; -- Test Summary SELECT 'Schema Validation Test Suite Completed - Review NOTICE/WARNING messages above' AS test_summary;