Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion tests/sqlx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ This test crate provides:

## Migration Status

Framework infrastructure complete. Test migration in progress.
**Progress: 24/24 SQL assertions ported**

- ✅ JSONB functions: 24/24 (arrays, paths, structure validation, encrypted selectors)

## Architecture

Expand Down Expand Up @@ -167,6 +169,20 @@ async fn test_name(pool: PgPool) {
- **Better errors**: Rust panic messages show exact assertion failure
- **Test isolation**: Each test runs in fresh database (SQLx handles this automatically)

## Test Organization

### Current Test Modules

**`tests/jsonb_tests.rs`** - JSONB functions and operators
- Converted from `src/jsonb/functions_test.sql`
- Tests: `jsonb_array_elements`, `jsonb_array_elements_text`, `jsonb_array_length`, `jsonb_path_query`, `jsonb_path_exists`, encrypted selector validation

### Test Count

- **Total**: 20 tests (19 functional + 1 helper)
- **JSONB**: 19 tests
- **Helpers**: 1 test

## Dependencies

From `Cargo.toml`:
Expand Down
310 changes: 310 additions & 0 deletions tests/sqlx/tests/jsonb_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
//! JSONB function tests
//!
//! Converted from src/jsonb/functions_test.sql
//! Tests EQL JSONB path query functions with encrypted data

use eql_tests::{QueryAssertion, Selectors};
use sqlx::{PgPool, Row};

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_returns_array_elements(pool: PgPool) {
// Test: jsonb_array_elements returns array elements from jsonb_path_query result
// Original SQL line 19-21 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_elements(eql_v2.jsonb_path_query(e, '{}')) as e FROM encrypted",
Selectors::ARRAY_ELEMENTS
);

QueryAssertion::new(&pool, &sql).returns_rows().await;

// Also verify count
QueryAssertion::new(&pool, &sql).count(5).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_throws_exception_for_non_array(pool: PgPool) {
// Test: jsonb_array_elements throws exception if input is not an array
// Original SQL line 28-30 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_elements(eql_v2.jsonb_path_query(e, '{}')) as e FROM encrypted LIMIT 1",
Selectors::ARRAY_ROOT
);

QueryAssertion::new(&pool, &sql).throws_exception().await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_text_returns_array_elements(pool: PgPool) {
// Test: jsonb_array_elements_text returns array elements as text
// Original SQL line 83-90 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_elements_text(eql_v2.jsonb_path_query(e, '{}')) as e FROM encrypted",
Selectors::ARRAY_ELEMENTS
);

QueryAssertion::new(&pool, &sql)
.returns_rows()
.await
.count(5)
.await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_text_throws_exception_for_non_array(pool: PgPool) {
// Test: jsonb_array_elements_text throws exception if input is not an array
// Original SQL line 92-94 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_elements_text(eql_v2.jsonb_path_query(e, '{}')) as e FROM encrypted LIMIT 1",
Selectors::ARRAY_ROOT
);

QueryAssertion::new(&pool, &sql).throws_exception().await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_length_returns_array_length(pool: PgPool) {
// Test: jsonb_array_length returns correct array length
// Original SQL line 114-117 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_length(eql_v2.jsonb_path_query(e, '{}')) as e FROM encrypted LIMIT 1",
Selectors::ARRAY_ELEMENTS
);

QueryAssertion::new(&pool, &sql).returns_int_value(5).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_length_throws_exception_for_non_array(pool: PgPool) {
// Test: jsonb_array_length throws exception if input is not an array
// Original SQL line 119-121 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_length(eql_v2.jsonb_path_query(e, '{}')) as e FROM encrypted LIMIT 1",
Selectors::ARRAY_ROOT
);

QueryAssertion::new(&pool, &sql).throws_exception().await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
async fn jsonb_path_query_finds_selector(pool: PgPool) {
// Test: jsonb_path_query finds records by selector
// Original SQL line 182-189 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_query(e, '{}') FROM encrypted LIMIT 1",
Selectors::N
);

QueryAssertion::new(&pool, &sql).returns_rows().await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
async fn jsonb_path_query_returns_correct_count(pool: PgPool) {
// Test: jsonb_path_query returns correct count
// Original SQL line 186-189 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_query(e, '{}') FROM encrypted",
Selectors::N
);

QueryAssertion::new(&pool, &sql).count(3).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
async fn jsonb_path_exists_returns_true_for_existing_path(pool: PgPool) {
// Test: jsonb_path_exists returns true for existing path
// Original SQL line 231-234 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_exists(e, '{}') FROM encrypted LIMIT 1",
Selectors::N
);

QueryAssertion::new(&pool, &sql)
.returns_bool_value(true)
.await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
async fn jsonb_path_exists_returns_false_for_nonexistent_path(pool: PgPool) {
// Test: jsonb_path_exists returns false for nonexistent path
// Original SQL line 236-239 in src/jsonb/functions_test.sql

let sql = "SELECT eql_v2.jsonb_path_exists(e, 'blahvtha') FROM encrypted LIMIT 1";

QueryAssertion::new(&pool, sql)
.returns_bool_value(false)
.await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
async fn jsonb_path_exists_returns_correct_count(pool: PgPool) {
// Test: jsonb_path_exists returns correct count
// Original SQL line 241-244 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_exists(e, '{}') FROM encrypted",
Selectors::N
);

QueryAssertion::new(&pool, &sql).count(3).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
async fn jsonb_path_query_returns_valid_structure(pool: PgPool) {
// Test: jsonb_path_query returns JSONB with correct structure ('i' and 'v' keys)
// Original SQL line 195-207 in src/jsonb/functions_test.sql
// Important: Validates decrypt-ability of returned data

let sql = format!(
"SELECT eql_v2.jsonb_path_query(e, '{}')::jsonb FROM encrypted LIMIT 1",
Selectors::N
);

let row = sqlx::query(&sql).fetch_one(&pool).await.unwrap();
let result: serde_json::Value = row.try_get(0).unwrap();

// Verify structure has 'i' (iv) and 'v' (value) keys required for decryption
assert!(
result.get("i").is_some(),
"Result must contain 'i' key for initialization vector"
);
assert!(
result.get("v").is_some(),
"Result must contain 'v' key for encrypted value"
);
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_returns_valid_structure(pool: PgPool) {
// Test: jsonb_array_elements returns elements with correct structure
// Original SQL line 211-223 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_array_elements(eql_v2.jsonb_path_query(e, '{}'))::jsonb FROM encrypted LIMIT 1",
Selectors::ARRAY_ELEMENTS
);

let row = sqlx::query(&sql).fetch_one(&pool).await.unwrap();
let result: serde_json::Value = row.try_get(0).unwrap();

// Verify array elements maintain encryption structure
assert!(
result.get("i").is_some(),
"Array element must contain 'i' key for initialization vector"
);
assert!(
result.get("v").is_some(),
"Array element must contain 'v' key for encrypted value"
);
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_path_query_first_with_array_selector(pool: PgPool) {
// Test: jsonb_path_query_first returns first element from array path
// Original SQL line 135-160 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_query_first(e, '{}') as e FROM encrypted",
Selectors::ARRAY_ROOT
);

// Should return 4 total rows (3 from encrypted_json + 1 from array_data)
QueryAssertion::new(&pool, sql).count(4).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_path_query_first_filters_non_null(pool: PgPool) {
// Test: jsonb_path_query_first can filter by non-null values
// Original SQL line 331-333 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_query_first(e, '{}') as e FROM encrypted WHERE eql_v2.jsonb_path_query_first(e, '{}') IS NOT NULL",
Selectors::ARRAY_ROOT,
Selectors::ARRAY_ROOT
);

// Should return only 1 row (the one with array data)
QueryAssertion::new(&pool, sql).count(1).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_path_query_with_array_selector_returns_single_result(pool: PgPool) {
// Test: jsonb_path_query wraps arrays as single result
// Original SQL line 254-274 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_query(e, '{}') FROM encrypted",
Selectors::ARRAY_ELEMENTS
);

// Array should be wrapped and returned as single element
QueryAssertion::new(&pool, sql).count(1).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_path_exists_with_array_selector(pool: PgPool) {
// Test: jsonb_path_exists works with array selectors
// Original SQL line 282-303 in src/jsonb/functions_test.sql

let sql = format!(
"SELECT eql_v2.jsonb_path_exists(e, '{}') FROM encrypted",
Selectors::ARRAY_ELEMENTS
);

// Should return 4 rows (3 encrypted_json + 1 array_data)
QueryAssertion::new(&pool, sql).count(4).await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_with_encrypted_selector(pool: PgPool) {
// Test: jsonb_array_elements_text accepts eql_v2_encrypted selector
// Original SQL line 39-66 in src/jsonb/functions_test.sql
// Tests alternative API pattern using encrypted selector

// Create encrypted selector for array elements path
let selector_sql = format!(
"SELECT '{}'::jsonb::eql_v2_encrypted::text",
Selectors::as_encrypted(Selectors::ARRAY_ELEMENTS)
);
let row = sqlx::query(&selector_sql).fetch_one(&pool).await.unwrap();
let encrypted_selector: String = row.try_get(0).unwrap();

let sql = format!(
"SELECT eql_v2.jsonb_array_elements_text(eql_v2.jsonb_path_query(e, '{}'::eql_v2_encrypted)) as e FROM encrypted",
encrypted_selector
);

QueryAssertion::new(&pool, &sql)
.returns_rows()
.await
.count(5)
.await;
}

#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json", "array_data")))]
async fn jsonb_array_elements_with_encrypted_selector_throws_for_non_array(pool: PgPool) {
// Test: encrypted selector also validates array type
// Original SQL line 61-63 in src/jsonb/functions_test.sql

let selector_sql = format!(
"SELECT '{}'::jsonb::eql_v2_encrypted::text",
Selectors::as_encrypted(Selectors::ARRAY_ROOT)
);
let row = sqlx::query(&selector_sql).fetch_one(&pool).await.unwrap();
let encrypted_selector: String = row.try_get(0).unwrap();

let sql = format!(
"SELECT eql_v2.jsonb_array_elements_text(eql_v2.jsonb_path_query(e, '{}'::eql_v2_encrypted)) as e FROM encrypted LIMIT 1",
encrypted_selector
);

QueryAssertion::new(&pool, &sql).throws_exception().await;
}
30 changes: 30 additions & 0 deletions tests/sqlx/tests/test_helpers_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use eql_tests::reset_function_stats;
use sqlx::PgPool;

#[sqlx::test]
async fn test_reset_function_stats(pool: PgPool) {
// Verify function tracking is enabled
let tracking_enabled = sqlx::query_scalar::<_, String>(
"SHOW track_functions"
)
.fetch_one(&pool)
.await
.expect("Failed to check track_functions setting");

assert_eq!(tracking_enabled, "all", "track_functions should be set to 'all'");

// Test: Call reset_function_stats and verify it completes without error
reset_function_stats(&pool)
.await
.expect("reset_function_stats should complete without error");

// The function wraps pg_stat_reset() which is a PostgreSQL built-in.
// We've verified:
// 1. The function compiles and can be called
// 2. It doesn't return an error
// 3. Function tracking is enabled in PostgreSQL
//
// The actual behavior of pg_stat_reset() is tested by PostgreSQL itself.
// Testing asynchronous stats collection is complex and timing-dependent,
// so we focus on verifying the wrapper works correctly.
}