Skip to content

Commit 2fce71d

Browse files
authored
Merge pull request #247 from cipherstash/refactor-param-tests
Refactor insert tests
2 parents 97923d6 + ccf9d2b commit 2fce71d

File tree

12 files changed

+410
-271
lines changed

12 files changed

+410
-271
lines changed

packages/cipherstash-proxy-integration/src/common.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ pub fn random_id() -> i64 {
2323
rng.random_range(1..=i64::MAX)
2424
}
2525

26+
// Limited by valid data range
27+
pub fn random_limited() -> i32 {
28+
use rand::Rng;
29+
let mut rng = rand::rng();
30+
rng.random_range(1..=31)
31+
}
32+
2633
pub fn random_string() -> String {
2734
rand::rng()
2835
.sample_iter(&Alphanumeric)
@@ -126,6 +133,15 @@ pub async fn query<T: for<'a> tokio_postgres::types::FromSql<'a> + Send + Sync>(
126133
rows.iter().map(|row| row.get(0)).collect::<Vec<T>>()
127134
}
128135

136+
pub async fn query_by<T>(sql: &str, param: &(dyn ToSql + Sync)) -> Vec<T>
137+
where
138+
T: for<'a> tokio_postgres::types::FromSql<'a> + Send + Sync,
139+
{
140+
let client = connect_with_tls(PROXY).await;
141+
let rows = client.query(sql, &[param]).await.unwrap();
142+
rows.iter().map(|row| row.get(0)).collect::<Vec<T>>()
143+
}
144+
129145
pub async fn simple_query<T: std::str::FromStr>(sql: &str) -> Vec<T>
130146
where
131147
<T as std::str::FromStr>::Err: std::fmt::Debug,
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::common::{clear, insert, query_by, random_id, random_limited, trace};
4+
use chrono::NaiveDate;
5+
use serde_json::Value;
6+
7+
macro_rules! test_insert_with_literal {
8+
($name: ident, $type: ident, $pg_type: ident) => {
9+
#[tokio::test]
10+
pub async fn $name() {
11+
trace();
12+
13+
clear().await;
14+
15+
let id = random_id();
16+
17+
let encrypted_col = format!("encrypted_{}", stringify!($pg_type));
18+
let encrypted_val = crate::value_for_type!($type, random_limited());
19+
20+
let sql = format!("INSERT INTO encrypted (id, {encrypted_col}) VALUES ($1, '{encrypted_val}')");
21+
insert(&sql, &[&id]).await;
22+
23+
let expected = vec![encrypted_val];
24+
25+
let sql = format!("SELECT {encrypted_col} FROM encrypted WHERE id = $1");
26+
27+
let actual = query_by::<$type>(&sql, &id).await;
28+
29+
assert_eq!(expected, actual);
30+
}
31+
};
32+
}
33+
34+
test_insert_with_literal!(insert_with_literal_int2, i16, int2);
35+
test_insert_with_literal!(insert_with_literal_int4, i32, int4);
36+
test_insert_with_literal!(insert_with_literal_int8, i64, int8);
37+
test_insert_with_literal!(insert_with_literal_float8, f64, float8);
38+
test_insert_with_literal!(insert_with_literal_bool, bool, bool);
39+
test_insert_with_literal!(insert_with_literal_text, String, text);
40+
test_insert_with_literal!(insert_with_literal_date, NaiveDate, date);
41+
test_insert_with_literal!(insert_with_literal_jsonb, Value, jsonb);
42+
43+
// -----------------------------------------------------------------
44+
45+
/// Sanity check insert of unencrypted literal value
46+
#[tokio::test]
47+
pub async fn insert_with_literal_plaintext() {
48+
trace();
49+
50+
clear().await;
51+
52+
let id = random_id();
53+
54+
let encrypted_val = crate::value_for_type!(String, random_limited());
55+
56+
let sql = format!("INSERT INTO encrypted (id, plaintext) VALUES ($1, '{encrypted_val}')");
57+
insert(&sql, &[&id]).await;
58+
59+
let expected = vec![encrypted_val];
60+
61+
let sql = "SELECT plaintext FROM encrypted WHERE id = $1";
62+
63+
let actual = query_by::<String>(sql, &id).await;
64+
65+
assert_eq!(expected, actual);
66+
}
67+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::common::{clear, insert, query_by, random_id, trace};
4+
use chrono::NaiveDate;
5+
use serde_json::Value;
6+
7+
macro_rules! test_insert_with_null_literal {
8+
($name: ident, $type: ident, $pg_type: ident) => {
9+
#[tokio::test]
10+
pub async fn $name() {
11+
trace();
12+
13+
clear().await;
14+
15+
let id = random_id();
16+
17+
let encrypted_col = format!("encrypted_{}", stringify!($pg_type));
18+
let encrypted_val: Option<$type> = None;
19+
20+
let sql = format!("INSERT INTO encrypted (id, {encrypted_col}) VALUES ($1, NULL)");
21+
insert(&sql, &[&id]).await;
22+
23+
let expected = vec![encrypted_val];
24+
25+
let sql = format!("SELECT {encrypted_col} FROM encrypted WHERE id = $1");
26+
27+
let actual = query_by::<Option<$type>>(&sql, &id).await;
28+
29+
assert_eq!(expected, actual);
30+
}
31+
};
32+
}
33+
34+
test_insert_with_null_literal!(insert_with_null_literal_int2, i16, int2);
35+
test_insert_with_null_literal!(insert_with_null_literal_int4, i32, int4);
36+
test_insert_with_null_literal!(insert_with_null_literal_int8, i64, int8);
37+
test_insert_with_null_literal!(insert_with_null_literal_float8, f64, float8);
38+
test_insert_with_null_literal!(insert_with_null_literal_bool, bool, bool);
39+
test_insert_with_null_literal!(insert_with_null_literal_text, String, text);
40+
test_insert_with_null_literal!(insert_with_null_literal_date, NaiveDate, date);
41+
test_insert_with_null_literal!(insert_with_null_literal_jsonb, Value, jsonb);
42+
43+
// -----------------------------------------------------------------
44+
45+
/// Sanity check insert of unencrypted literal value
46+
#[tokio::test]
47+
pub async fn insert_with_null_literal_plaintext() {
48+
trace();
49+
50+
clear().await;
51+
52+
let id = random_id();
53+
54+
let expected: Vec<Option<String>> = vec![None];
55+
56+
let sql = "INSERT INTO encrypted (id, plaintext) VALUES ($1, NULL)";
57+
insert(sql, &[&id]).await;
58+
59+
let sql = "SELECT plaintext FROM encrypted WHERE id = $1";
60+
61+
let actual = query_by::<Option<String>>(sql, &id).await;
62+
63+
assert_eq!(expected, actual);
64+
}
65+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::common::{clear, insert, query_by, random_id, trace};
4+
use chrono::NaiveDate;
5+
use serde_json::Value;
6+
7+
macro_rules! test_insert_with_null_param {
8+
($name: ident, $type: ident, $pg_type: ident) => {
9+
#[tokio::test]
10+
pub async fn $name() {
11+
trace();
12+
13+
clear().await;
14+
15+
let id = random_id();
16+
17+
let encrypted_col = format!("encrypted_{}", stringify!($pg_type));
18+
let encrypted_val: Option<$type> = None;
19+
20+
let sql = format!("INSERT INTO encrypted (id, {encrypted_col}) VALUES ($1, $2)");
21+
insert(&sql, &[&id, &encrypted_val]).await;
22+
23+
let expected = vec![encrypted_val];
24+
25+
let sql = format!("SELECT {encrypted_col} FROM encrypted WHERE id = $1");
26+
let result = query_by::<Option<$type>>(&sql, &id).await;
27+
28+
assert_eq!(expected, result);
29+
}
30+
};
31+
}
32+
33+
test_insert_with_null_param!(insert_with_null_param_int2, i16, int2);
34+
test_insert_with_null_param!(insert_with_null_param_int4, i32, int4);
35+
test_insert_with_null_param!(insert_with_null_param_int8, i64, int8);
36+
test_insert_with_null_param!(insert_with_null_param_float8, f64, float8);
37+
test_insert_with_null_param!(insert_with_null_param_bool, bool, bool);
38+
test_insert_with_null_param!(insert_with_null_param_text_only, String, text);
39+
test_insert_with_null_param!(insert_with_null_param_date, NaiveDate, date);
40+
test_insert_with_null_param!(insert_with_null_param_jsonb, Value, jsonb);
41+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::common::{clear, insert, query_by, random_id, random_limited, trace};
4+
use chrono::NaiveDate;
5+
use serde_json::Value;
6+
7+
macro_rules! test_insert_with_param {
8+
($name: ident, $type: ident, $pg_type: ident) => {
9+
#[tokio::test]
10+
pub async fn $name() {
11+
trace();
12+
13+
clear().await;
14+
15+
let id = random_id();
16+
17+
let encrypted_col = format!("encrypted_{}", stringify!($pg_type));
18+
let encrypted_val = crate::value_for_type!($type, random_limited());
19+
20+
let sql = format!("INSERT INTO encrypted (id, {encrypted_col}) VALUES ($1, $2)");
21+
insert(&sql, &[&id, &encrypted_val]).await;
22+
23+
let expected = vec![encrypted_val];
24+
25+
let sql = format!("SELECT {encrypted_col} FROM encrypted WHERE id = $1");
26+
27+
let actual = query_by::<$type>(&sql, &id).await;
28+
29+
assert_eq!(expected, actual);
30+
}
31+
};
32+
}
33+
34+
test_insert_with_param!(insert_with_param_int2, i16, int2);
35+
test_insert_with_param!(insert_with_param_int4, i32, int4);
36+
test_insert_with_param!(insert_with_param_int8, i64, int8);
37+
test_insert_with_param!(insert_with_param_float8, f64, float8);
38+
test_insert_with_param!(insert_with_param_bool, bool, bool);
39+
test_insert_with_param!(insert_with_param_text, String, text);
40+
test_insert_with_param!(insert_with_param_date, NaiveDate, date);
41+
test_insert_with_param!(insert_with_param_jsonb, Value, jsonb);
42+
43+
// -----------------------------------------------------------------
44+
45+
/// Sanity check insert of unencrypted plaintext value
46+
#[tokio::test]
47+
pub async fn insert_with_param_plaintext() {
48+
trace();
49+
50+
clear().await;
51+
52+
let id = random_id();
53+
54+
let encrypted_val = crate::value_for_type!(String, random_limited());
55+
56+
let sql = "INSERT INTO encrypted (id, plaintext) VALUES ($1, $2)";
57+
insert(sql, &[&id, &encrypted_val]).await;
58+
59+
let expected = vec![encrypted_val];
60+
61+
let sql = "SELECT plaintext FROM encrypted WHERE id = $1";
62+
63+
let actual = query_by::<String>(sql, &id).await;
64+
65+
assert_eq!(expected, actual);
66+
}
67+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::common::{clear, insert, query, random_id, random_limited, trace};
4+
use chrono::NaiveDate;
5+
use rand::{seq::IndexedRandom, Rng};
6+
use serde_json::Value;
7+
use tokio_postgres::types::ToSql;
8+
use tracing::info;
9+
10+
fn value_for_type(t: &str) -> Box<dyn ToSql + Sync> {
11+
let mut rng = rand::rng();
12+
13+
match t {
14+
"i16" => Box::new(rng.random_range(1..=i16::MAX) as i16),
15+
"i32" => Box::new(rng.random_range(1..=i32::MAX) as i32),
16+
"i64" => Box::new(rng.random_range(1..=i64::MAX) as i64),
17+
"f64" => Box::new(rng.random_range(1.0..=f64::MAX) as f64),
18+
"bool" => Box::new(rand::random_bool(0.5) as bool),
19+
"String" => {
20+
let i = random_limited();
21+
Box::new(((b'A' + (i - 1) as u8) as char).to_string())
22+
}
23+
"NaiveDate" => {
24+
let i = random_limited();
25+
Box::new(NaiveDate::parse_from_str(&format!("2023-01-{}", i), "%Y-%m-%d").unwrap())
26+
}
27+
"Value" => {
28+
let i = rng.random_range(1..=i32::MAX) as i32;
29+
Box::new(serde_json::json!({"n": i, "s": format!("{}", i) }))
30+
}
31+
32+
_ => panic!("Unknown type {t}"),
33+
}
34+
}
35+
36+
///
37+
/// Generates a random number of columns and values
38+
/// Return as a tuple of two vecs:
39+
/// - first vec contains column names
40+
/// - second vec contains values of the corresponding column type
41+
pub fn generate_columns_with_values() -> (Vec<String>, Vec<Box<(dyn ToSql + Sync)>>) {
42+
let columns = vec![
43+
("i16", "int2"),
44+
("i32", "int4"),
45+
("i64", "int8"),
46+
("f64", "float8"),
47+
("bool", "bool"),
48+
("String", "text"),
49+
("NaiveDate", "date"),
50+
("Value", "jsonb"),
51+
];
52+
53+
let mut rng = rand::rng();
54+
let n = rng.random_range(1..columns.len());
55+
56+
let (mut columns, mut values): (Vec<_>, Vec<_>) = columns
57+
.choose_multiple(&mut rng, n)
58+
.map(|(t, c)| {
59+
let c = format!("encrypted_{c}");
60+
(c, value_for_type(t))
61+
})
62+
.unzip();
63+
64+
let id = Box::new(random_id());
65+
columns.insert(0, "id".to_string());
66+
values.insert(0, id);
67+
68+
(columns, values)
69+
}
70+
71+
pub async fn query<T: for<'a> tokio_postgres::types::FromSql<'a> + Send + Sync>(
72+
sql: &str,
73+
) -> Vec<T> {
74+
let client = connect_with_tls(PROXY).await;
75+
let rows = client.query(sql, &[]).await.unwrap();
76+
rows.iter().map(|row| row.get(0)).collect::<Vec<T>>()
77+
}
78+
79+
#[tokio::test]
80+
pub async fn test_everything_all_at_once() {
81+
trace();
82+
83+
clear().await;
84+
85+
let (columns, values) = generate_columns_with_values();
86+
87+
info!("Columns: {:?}", columns.join(","));
88+
info!("Values: {:?}", values);
89+
90+
let columns = columns.join(", ");
91+
let params: Vec<&(dyn ToSql + Sync)> = values.iter().map(|v| v.as_ref()).collect();
92+
93+
let placeholders = (1..=values.len())
94+
.map(|i| format!("${}", i))
95+
.collect::<Vec<_>>()
96+
.join(", ");
97+
98+
let sql = format!("INSERT INTO encrypted ({columns}) VALUES ({placeholders})");
99+
100+
info!(sql);
101+
insert(&sql, &params).await;
102+
103+
let sql = format!("SELECT {columns} FROM encrypted WHERE id = $1");
104+
105+
// let actual = query_by::<$type>(&sql, &id).await;
106+
107+
// assert_eq!(expected, actual);
108+
}
109+
110+
// test_insert_with_params!(insert_with_params_int2, i16, int2);
111+
// test_insert_with_params!(insert_with_params_int4, i32, int4);
112+
// test_insert_with_params!(insert_with_params_int8, i64, int8);
113+
// test_insert_with_params!(insert_with_params_float8, f64, float8);
114+
// test_insert_with_params!(insert_with_params_bool, bool, bool);
115+
// test_insert_with_params!(insert_with_params_text_only, String, text);
116+
// test_insert_with_params!(insert_with_params_date, NaiveDate, date);
117+
// test_insert_with_params!(insert_with_params_jsonb, Value, jsonb);
118+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod insert_with_literal;
2+
mod insert_with_null_literal;
3+
mod insert_with_null_param;
4+
mod insert_with_param;

0 commit comments

Comments
 (0)