Files
komp_ac/server/tests/table_definition/post_table_definition_test5.rs
2025-06-22 12:48:36 +02:00

383 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 that would cause FK column name collisions
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();
// Try to create a table linking to both - this should reveal the FK naming bug
let request = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "orders".into(),
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;
// This test documents the current bug - both tables create "146053_id" columns
if result.is_err() {
let err = result.unwrap_err();
if err.message().contains("specified more than once") {
// This confirms the FK naming collision bug described in the analysis
assert!(err.message().contains("146053_id"));
} else {
// If it's a different error, let it fail normally
panic!("Unexpected error: {:?}", err);
}
} else {
// If this passes, the bug has been fixed
assert!(result.is_ok());
}
}
#[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: "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: "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;
// Test that schema names are properly case-sensitive
let request1 = PostTableDefinitionRequest {
profile_name: "TestSchema".into(),
table_name: "test_table".into(),
columns: vec![],
indexes: vec![],
links: vec![],
};
post_table_definition(&pool, request1).await.unwrap();
// Different case should be treated as different schema
let request2 = PostTableDefinitionRequest {
profile_name: "testschema".into(),
table_name: "test_table".into(),
columns: vec![],
indexes: vec![],
links: vec![],
};
let result = post_table_definition(&pool, request2).await;
// Under case-insensitive profiles this must collide
assert!(result.is_err(), "Expected duplicateschema error");
let err = result.unwrap_err();
// pick the right code for “already exists” in your handler
assert_eq!(err.code(), Code::AlreadyExists, "{:?}", err);
}
#[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);
}