Skip to content

Commit 27fa59b

Browse files
committed
docs(plan): add backwards compatibility test plan for canonical config migration
Covers proxy-side integration tests for JSON -> CanonicalEncryptionConfig -> EncryptConfig pipeline including Identifier bridging, ColumnType mapping, error propagation, and a realistic schema fixture matching the integration test database.
1 parent dbeee44 commit 27fa59b

1 file changed

Lines changed: 275 additions & 0 deletions

File tree

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# Proxy Backwards Compatibility Tests for Canonical Config Migration
2+
3+
> **For Claude:** REQUIRED SUB-SKILL: Use cipherpowers:executing-plans to implement this plan task-by-task.
4+
5+
**Goal:** Verify the proxy's integration pipeline from JSON → `CanonicalEncryptionConfig``EncryptConfig` works correctly, including Identifier conversion, error handling, and ColumnType mapping.
6+
7+
**Tech Stack:** Rust, serde_json, cipherstash-config, cipherstash-client
8+
9+
---
10+
11+
### Task 1: Test Identifier bridging in EncryptConfig
12+
13+
**File:** `packages/cipherstash-proxy/src/proxy/encrypt_config/config.rs` (test module)
14+
15+
The `load_encrypt_config` function converts `cipherstash_config::Identifier``cipherstash_client::eql::Identifier`. Test that this preserves table/column names correctly.
16+
17+
Add these tests to the existing test module:
18+
19+
```rust
20+
#[test]
21+
fn config_map_preserves_table_and_column_names() {
22+
let json = json!({
23+
"v": 1,
24+
"tables": {
25+
"my_schema.users": {
26+
"email_address": {
27+
"cast_as": "text",
28+
"indexes": { "unique": {} }
29+
}
30+
}
31+
}
32+
});
33+
34+
let config = parse(json);
35+
36+
let ident = Identifier::new("my_schema.users", "email_address");
37+
let column = config.get(&ident).expect("column exists");
38+
assert_eq!(column.name, "email_address");
39+
assert_eq!(column.cast_type, ColumnType::Text);
40+
}
41+
42+
#[test]
43+
fn config_map_handles_multiple_tables() {
44+
let json = json!({
45+
"v": 1,
46+
"tables": {
47+
"users": {
48+
"email": { "cast_as": "text" }
49+
},
50+
"orders": {
51+
"total": { "cast_as": "int" }
52+
}
53+
}
54+
});
55+
56+
let config = parse(json);
57+
58+
assert_eq!(config.len(), 2);
59+
assert!(config.contains_key(&Identifier::new("users", "email")));
60+
assert!(config.contains_key(&Identifier::new("orders", "total")));
61+
}
62+
```
63+
64+
**Verify:** `cargo test -p cipherstash-proxy --lib -- encrypt_config`
65+
66+
---
67+
68+
### Task 2: Test ColumnType mapping through column_type_to_postgres_type
69+
70+
**File:** `packages/cipherstash-proxy/src/postgresql/context/column.rs`
71+
72+
The rename from `Utf8Str``Text` and `JsonB``Json` must produce the same PostgreSQL types. Add tests to verify the mapping.
73+
74+
Add a test module to `column.rs`:
75+
76+
```rust
77+
#[cfg(test)]
78+
mod tests {
79+
use super::*;
80+
use eql_mapper::EqlTermVariant;
81+
82+
#[test]
83+
fn text_column_maps_to_postgres_text() {
84+
assert_eq!(
85+
column_type_to_postgres_type(&ColumnType::Text, EqlTermVariant::Full),
86+
postgres_types::Type::TEXT
87+
);
88+
}
89+
90+
#[test]
91+
fn json_column_maps_to_postgres_jsonb() {
92+
assert_eq!(
93+
column_type_to_postgres_type(&ColumnType::Json, EqlTermVariant::Full),
94+
postgres_types::Type::JSONB
95+
);
96+
}
97+
98+
#[test]
99+
fn json_accessor_maps_to_postgres_text() {
100+
assert_eq!(
101+
column_type_to_postgres_type(&ColumnType::Json, EqlTermVariant::JsonAccessor),
102+
postgres_types::Type::TEXT
103+
);
104+
}
105+
106+
#[test]
107+
fn all_column_types_have_postgres_mapping() {
108+
let types = vec![
109+
ColumnType::Boolean,
110+
ColumnType::BigInt,
111+
ColumnType::BigUInt,
112+
ColumnType::Date,
113+
ColumnType::Decimal,
114+
ColumnType::Float,
115+
ColumnType::Int,
116+
ColumnType::SmallInt,
117+
ColumnType::Timestamp,
118+
ColumnType::Text,
119+
ColumnType::Json,
120+
];
121+
122+
for ct in types {
123+
// Should not panic
124+
let _ = column_type_to_postgres_type(&ct, EqlTermVariant::Full);
125+
}
126+
}
127+
}
128+
```
129+
130+
**Verify:** `cargo test -p cipherstash-proxy --lib -- context::column`
131+
132+
---
133+
134+
### Task 3: Test error propagation for invalid configs
135+
136+
**File:** `packages/cipherstash-proxy/src/proxy/encrypt_config/config.rs` (test module)
137+
138+
The canonical `into_config_map()` can now return errors (e.g., ste_vec on non-JSON column). Verify the error surfaces correctly through the proxy's error types.
139+
140+
```rust
141+
#[test]
142+
fn invalid_config_returns_error() {
143+
let json = json!({
144+
"v": 1,
145+
"tables": {
146+
"users": {
147+
"email": {
148+
"cast_as": "text",
149+
"indexes": {
150+
"ste_vec": { "prefix": "test" }
151+
}
152+
}
153+
}
154+
}
155+
});
156+
157+
let config: CanonicalEncryptionConfig = serde_json::from_value(json).unwrap();
158+
let result = config.into_config_map();
159+
assert!(result.is_err(), "ste_vec on text column should fail validation");
160+
}
161+
162+
#[test]
163+
fn malformed_json_returns_parse_error() {
164+
let json = json!({
165+
"v": 1,
166+
"tables": "not a map"
167+
});
168+
169+
let result = serde_json::from_value::<CanonicalEncryptionConfig>(json);
170+
assert!(result.is_err());
171+
}
172+
```
173+
174+
**Verify:** `cargo test -p cipherstash-proxy --lib -- encrypt_config`
175+
176+
---
177+
178+
### Task 4: Test real integration schema config shape
179+
180+
**File:** `packages/cipherstash-proxy/src/proxy/encrypt_config/config.rs` (test module)
181+
182+
Use the same fixture from the cipherstash-config plan — the JSON shape matching the proxy's integration test schema. Verify the full pipeline including Identifier conversion.
183+
184+
```rust
185+
#[test]
186+
fn real_eql_config_produces_correct_encrypt_config() {
187+
let json = json!({
188+
"v": 1,
189+
"tables": {
190+
"encrypted": {
191+
"encrypted_text": {
192+
"cast_as": "text",
193+
"indexes": { "unique": {}, "match": {}, "ore": {} }
194+
},
195+
"encrypted_bool": {
196+
"cast_as": "boolean",
197+
"indexes": { "unique": {}, "ore": {} }
198+
},
199+
"encrypted_int2": {
200+
"cast_as": "small_int",
201+
"indexes": { "unique": {}, "ore": {} }
202+
},
203+
"encrypted_int4": {
204+
"cast_as": "int",
205+
"indexes": { "unique": {}, "ore": {} }
206+
},
207+
"encrypted_int8": {
208+
"cast_as": "big_int",
209+
"indexes": { "unique": {}, "ore": {} }
210+
},
211+
"encrypted_float8": {
212+
"cast_as": "double",
213+
"indexes": { "unique": {}, "ore": {} }
214+
},
215+
"encrypted_date": {
216+
"cast_as": "date",
217+
"indexes": { "unique": {}, "ore": {} }
218+
},
219+
"encrypted_jsonb": {
220+
"cast_as": "jsonb",
221+
"indexes": {
222+
"ste_vec": { "prefix": "encrypted/encrypted_jsonb" }
223+
}
224+
},
225+
"encrypted_jsonb_filtered": {
226+
"cast_as": "jsonb",
227+
"indexes": {
228+
"ste_vec": {
229+
"prefix": "encrypted/encrypted_jsonb_filtered",
230+
"term_filters": [{ "kind": "downcase" }]
231+
}
232+
}
233+
}
234+
}
235+
}
236+
});
237+
238+
let config = parse(json);
239+
240+
// All 9 columns present with correct Identifiers
241+
assert_eq!(config.len(), 9);
242+
243+
// Verify legacy type aliases map correctly
244+
let float_col = config.get(&Identifier::new("encrypted", "encrypted_float8")).unwrap();
245+
assert_eq!(float_col.cast_type, ColumnType::Float);
246+
247+
let jsonb_col = config.get(&Identifier::new("encrypted", "encrypted_jsonb")).unwrap();
248+
assert_eq!(jsonb_col.cast_type, ColumnType::Json);
249+
250+
// Verify index counts
251+
let text_col = config.get(&Identifier::new("encrypted", "encrypted_text")).unwrap();
252+
assert_eq!(text_col.indexes.len(), 3);
253+
254+
let bool_col = config.get(&Identifier::new("encrypted", "encrypted_bool")).unwrap();
255+
assert_eq!(bool_col.indexes.len(), 2);
256+
257+
let jsonb_filtered = config.get(&Identifier::new("encrypted", "encrypted_jsonb_filtered")).unwrap();
258+
assert_eq!(jsonb_filtered.indexes.len(), 1);
259+
}
260+
```
261+
262+
**Verify:** `cargo test -p cipherstash-proxy --lib -- encrypt_config`
263+
264+
---
265+
266+
### Task 5: Full verification
267+
268+
Run the complete test suite:
269+
270+
```bash
271+
cargo clippy --no-deps --tests --all-features --all-targets -p cipherstash-proxy -- -D warnings
272+
cargo test -p cipherstash-proxy --lib
273+
```
274+
275+
All tests must pass, zero clippy warnings.

0 commit comments

Comments
 (0)