// tests/table_definition/post_table_definition_test5.rs // NOTE: All 'use' statements are inherited from the parent file that includes this one. // ========= Category 7: Schema Validation and Edge Cases ========= #[rstest] #[tokio::test] async fn test_schema_name_validation_reserved_names(#[future] pool: PgPool) { let pool = pool.await; let reserved_names = vec![ "pg_catalog", "information_schema", "pg_toast", "public", // May be reserved depending on implementation ]; for reserved_name in reserved_names { let request = PostTableDefinitionRequest { profile_name: reserved_name.into(), table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Reserved schema name '{}' should be rejected", reserved_name); if let Err(status) = result { assert_eq!(status.code(), Code::InvalidArgument); } } } #[rstest] #[tokio::test] async fn test_schema_name_validation_sql_injection(#[future] pool: PgPool) { let pool = pool.await; let malicious_names = vec![ "test; DROP SCHEMA public", "test'; DROP TABLE users; --", "test\"; CREATE TABLE evil; --", ]; for malicious_name in malicious_names { let request = PostTableDefinitionRequest { profile_name: malicious_name.into(), table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Malicious schema name '{}' should be rejected", malicious_name); if let Err(status) = result { assert_eq!(status.code(), Code::InvalidArgument); assert!(status.message().contains("contains invalid characters")); } } } #[rstest] #[tokio::test] async fn test_schema_name_length_limits(#[future] pool: PgPool) { let pool = pool.await; // Test schema name length limits (63 chars in PostgreSQL) let long_name = "a".repeat(64); let request = PostTableDefinitionRequest { profile_name: long_name, table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Schema names longer than 63 characters should be rejected"); if let Err(status) = result { assert_eq!(status.code(), Code::InvalidArgument); } } #[rstest] #[tokio::test] async fn test_unicode_in_schema_names_rejected(#[future] pool: PgPool) { let pool = pool.await; let unicode_names = vec![ "test_😀", "schéma", "тест", "测试", ]; for unicode_name in unicode_names { let request = PostTableDefinitionRequest { profile_name: unicode_name.into(), table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err(), "Unicode schema name '{}' should be rejected", unicode_name); if let Err(status) = result { assert_eq!(status.code(), Code::InvalidArgument); assert!(status.message().contains("contains invalid characters")); } } } // ========= Category 8: Foreign Key Edge Cases ========= #[rstest] #[tokio::test] async fn test_fk_column_name_uniqueness_collision(#[future] pool: PgPool) { let pool = pool.await; // Create tables with similar suffixes let req1 = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "customers_146053".into(), columns: vec![], indexes: vec![], links: vec![], }; post_table_definition(&pool, req1).await.unwrap(); let req2 = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "suppliers_146053".into(), columns: vec![], indexes: vec![], links: vec![], }; post_table_definition(&pool, req2).await.unwrap(); // Create a table linking to both - should succeed with full table names let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "orders_test".into(), // Use unique name to avoid conflicts columns: vec![], indexes: vec![], links: vec![ TableLink { linked_table_name: "customers_146053".into(), required: true, }, TableLink { linked_table_name: "suppliers_146053".into(), required: true, }, ], }; let result = post_table_definition(&pool, request).await; // Should succeed - no collision with full table names assert!(result.is_ok()); let response = result.unwrap(); // Verify both FK columns are created with full table names assert!(response.sql.contains("\"customers_146053_id\"")); assert!(response.sql.contains("\"suppliers_146053_id\"")); // Verify both are NOT NULL (required = true) assert!(response.sql.contains("\"customers_146053_id\" BIGINT NOT NULL")); assert!(response.sql.contains("\"suppliers_146053_id\" BIGINT NOT NULL")); } #[rstest] #[tokio::test] async fn test_cross_schema_references_prevented(#[future] pool: PgPool) { let pool = pool.await; // Create table in schema A let req_a = PostTableDefinitionRequest { profile_name: "scheam_a".into(), table_name: "users".into(), columns: vec![], indexes: vec![], links: vec![], }; post_table_definition(&pool, req_a).await.unwrap(); // Try to link from schema B to schema A's table let req_b = PostTableDefinitionRequest { profile_name: "schema_b".into(), table_name: "orders".into(), columns: vec![], indexes: vec![], links: vec![TableLink { linked_table_name: "users".into(), // This should not find A.users required: true, }], }; let result = post_table_definition(&pool, req_b).await; assert!(result.is_err()); assert!(result.unwrap_err().message().contains("not found")); } // ========= Category 9: Concurrent Operations ========= #[rstest] #[tokio::test] async fn test_concurrent_schema_creation(#[future] pool: PgPool) { let pool = pool.await; use futures::future::join_all; let futures = (0..10).map(|i| { let pool = pool.clone(); async move { let request = PostTableDefinitionRequest { profile_name: format!("concurrent_schema_{}", i), table_name: "test_table".into(), columns: vec![ColumnDefinition { name: "test_column".into(), field_type: "text".into(), }], indexes: vec![], links: vec![], }; post_table_definition(&pool, request).await } }); let results = join_all(futures).await; assert!(results.iter().all(|r| r.is_ok())); } #[rstest] #[tokio::test] async fn test_table_creation_with_many_foreign_keys(#[future] pool: PgPool) { let pool = pool.await; // Create several tables to link to for i in 0..5 { let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: format!("target_table_{}", i), columns: vec![], indexes: vec![], links: vec![], }; post_table_definition(&pool, request).await.unwrap(); } // Create a table that links to all of them let links = (0..5).map(|i| TableLink { linked_table_name: format!("target_table_{}", i), required: false, }).collect(); let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "many_links_table".into(), columns: vec![], indexes: vec![], links, }; let result = post_table_definition(&pool, request).await; assert!(result.is_ok()); } // ========= Category 10: Empty and Boundary Cases ========= #[rstest] #[tokio::test] async fn test_empty_schema_and_table_names_rejected(#[future] pool: PgPool) { let pool = pool.await; // Test empty schema name let request = PostTableDefinitionRequest { profile_name: "".into(), table_name: "valid_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err()); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); // Test empty table name let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err()); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); } #[rstest] #[tokio::test] async fn test_schema_name_case_sensitivity(#[future] pool: PgPool) { let pool = pool.await; // First, verify that uppercase letters are rejected let invalid_request = PostTableDefinitionRequest { profile_name: "TestSchema".into(), // Contains uppercase - should be rejected table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, invalid_request).await; assert!(result.is_err()); let err = result.unwrap_err(); assert_eq!(err.code(), Code::InvalidArgument); assert!(err.message().contains("contains invalid characters")); // Now test with valid lowercase names - create first schema let request1 = PostTableDefinitionRequest { profile_name: "test_schema_a".into(), table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; post_table_definition(&pool, request1).await.unwrap(); // Different lowercase schema should work fine let request2 = PostTableDefinitionRequest { profile_name: "test_schema_b".into(), table_name: "test_table".into(), // Same table name, different schema columns: vec![], indexes: vec![], links: vec![], }; let result2 = post_table_definition(&pool, request2).await; assert!(result2.is_ok(), "Different schemas should allow same table names"); // Same schema name should cause table collision let request3 = PostTableDefinitionRequest { profile_name: "test_schema_a".into(), // Same schema as request1 table_name: "test_table".into(), // Same table name as request1 columns: vec![], indexes: vec![], links: vec![], }; let result3 = post_table_definition(&pool, request3).await; assert!(result3.is_err(), "Same schema + table should cause collision"); let err3 = result3.unwrap_err(); assert_eq!(err3.code(), Code::AlreadyExists); } #[rstest] #[tokio::test] async fn test_whitespace_in_identifiers_rejected(#[future] pool: PgPool) { let pool = pool.await; // Test schema name with whitespace let request = PostTableDefinitionRequest { profile_name: "test schema".into(), table_name: "test_table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err()); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); // Test table name with whitespace let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "test table".into(), columns: vec![], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err()); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); // Test column name with whitespace let request = PostTableDefinitionRequest { profile_name: "default".into(), table_name: "test_table".into(), columns: vec![ColumnDefinition { name: "test column".into(), field_type: "text".into(), }], indexes: vec![], links: vec![], }; let result = post_table_definition(&pool, request).await; assert!(result.is_err()); assert_eq!(result.unwrap_err().code(), Code::InvalidArgument); }