Files
komp_ac/server/tests/table_definition/post_table_definition_test4.rs
2025-06-20 19:59:42 +02:00

223 lines
7.7 KiB
Rust

// tests/table_definition/post_table_definition_test4.rs
// NOTE: All 'use' statements are inherited from the parent file that includes this one.
// ========= Category 5: Implementation-Specific Edge Cases =========
#[rstest]
#[tokio::test]
async fn test_fail_on_fk_base_name_collision(#[future] pool: PgPool) {
// Scenario: Link to two tables (`team1_users`, `team2_users`) that both have a
// base name of "users". This should cause a duplicate "users_id" column in the
// generated SQL.
let pool = pool.await;
// Arrange: Create the two prerequisite tables
let req1 = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "team1_users".into(),
..Default::default()
};
post_table_definition(&pool, req1).await.unwrap();
let req2 = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "team2_users".into(),
..Default::default()
};
post_table_definition(&pool, req2).await.unwrap();
// Arrange: A request that links to both, causing the collision
let colliding_req = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "tasks".into(),
links: vec![
TableLink {
linked_table_name: "team1_users".into(),
required: true,
},
TableLink {
linked_table_name: "team2_users".into(),
required: false,
},
],
..Default::default()
};
// Act
let result = post_table_definition(&pool, colliding_req).await;
// Assert
let err = result.unwrap_err();
assert_eq!(
err.code(),
Code::Internal,
"Expected Internal error from duplicate column in CREATE TABLE"
);
}
#[rstest]
#[tokio::test]
async fn test_sql_reserved_keywords_as_identifiers_are_allowed(#[future] pool: PgPool) {
// NOTE: This test confirms that the system currently allows SQL reserved keywords
// as column names because they are correctly quoted. This is technically correct,
// but some systems add validation to block this as a policy to prevent user confusion.
let pool = pool.await;
let keywords = vec!["user", "select", "group", "order"];
for (i, keyword) in keywords.into_iter().enumerate() {
let table_name = format!("keyword_test_{}", i);
let request = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: table_name.clone(),
columns: vec![ColumnDefinition {
name: keyword.into(),
field_type: "text".into(),
}],
..Default::default()
};
// Act & Assert
let response = post_table_definition(&pool, request)
.await
.unwrap_or_else(|e| {
panic!(
"Failed to create table with reserved keyword '{}': {:?}",
keyword, e
)
});
assert!(response.success);
assert!(response.sql.contains(&format!("\"{}\" TEXT", keyword)));
assert_table_structure_is_correct(&pool, &table_name, &[(keyword, "text")]).await;
}
}
// ========= Category 6: Environmental and Extreme Edge Cases =========
#[rstest]
#[tokio::test]
async fn test_sanitization_of_unicode_and_special_chars(#[future] pool: PgPool) {
// Scenario: Use identifiers with characters that should be stripped by sanitization,
// including multi-byte unicode (emoji) and a null byte.
let pool = pool.await;
let request = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "produits_😂".into(), // Should become "produits_"
columns: vec![ColumnDefinition {
name: "col\0with_null".into(), // Should become "colwith_null"
field_type: "text".into(),
}],
..Default::default()
};
// Act
let response = post_table_definition(&pool, request).await.unwrap();
// Assert
assert!(response.success);
// Assert that the generated SQL contains the SANITIZED names
assert!(response.sql.contains("CREATE TABLE gen.\"produits_\""));
assert!(response.sql.contains("\"colwith_null\" TEXT"));
// Verify the actual structure in the database
assert_table_structure_is_correct(&pool, "produits_", &[("colwith_null", "text")]).await;
}
#[rstest]
#[tokio::test]
async fn test_fail_gracefully_if_schema_is_missing(#[future] pool: PgPool) {
// Scenario: The handler relies on the 'gen' schema existing. This test ensures
// it fails gracefully if that assumption is broken.
let pool = pool.await;
// Arrange: Drop the schema that the handler needs
sqlx::query("DROP SCHEMA gen CASCADE;")
.execute(&pool)
.await
.expect("Failed to drop 'gen' schema for test setup");
let request = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "this_will_fail".into(),
..Default::default()
};
// Act
let result = post_table_definition(&pool, request).await;
// Assert
let err = result.unwrap_err();
assert_eq!(err.code(), Code::Internal);
// Check for the Postgres error message for a missing schema.
assert!(err.message().to_lowercase().contains("schema \"gen\" does not exist"));
}
#[rstest]
#[tokio::test]
async fn test_column_name_with_id_suffix_is_rejected(#[future] pool: PgPool) {
// Test that column names ending with '_id' are properly rejected during input validation
let pool = pool.await;
// Test 1: Column ending with '_id' should be rejected
let request = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "orders".into(), // Valid table name
columns: vec![ColumnDefinition {
name: "legacy_order_id".into(), // This should be rejected
field_type: "integer".into(),
}],
..Default::default()
};
// Act & Assert - should fail validation
let result = post_table_definition(&pool, request).await;
assert!(result.is_err(), "Column names ending with '_id' should be rejected");
if let Err(status) = result {
assert_eq!(status.code(), tonic::Code::InvalidArgument);
assert!(status.message().contains("Invalid column name"));
}
// Test 2: Column named exactly 'id' should be rejected
let request2 = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "orders".into(),
columns: vec![ColumnDefinition {
name: "id".into(), // This should be rejected
field_type: "integer".into(),
}],
..Default::default()
};
let result2 = post_table_definition(&pool, request2).await;
assert!(result2.is_err(), "Column named 'id' should be rejected");
}
#[rstest]
#[tokio::test]
async fn test_table_name_with_id_suffix_is_rejected(#[future] pool: PgPool) {
// Test that table names ending with '_id' are properly rejected during input validation
let pool = pool.await;
let request = PostTableDefinitionRequest {
profile_name: "default".into(),
table_name: "orders_id".into(), // This should be rejected
columns: vec![ColumnDefinition {
name: "customer_name".into(), // Valid column name
field_type: "text".into(),
}],
..Default::default()
};
// Act & Assert - should fail validation
let result = post_table_definition(&pool, request).await;
assert!(result.is_err(), "Table names ending with '_id' should be rejected");
if let Err(status) = result {
assert_eq!(status.code(), tonic::Code::InvalidArgument);
assert!(status.message().contains("Table name cannot be 'id', 'deleted', 'created_at' or end with '_id'"));
}
}