Skip to content

Commit 2cbc38a

Browse files
committed
test: add unit tests for TOFU certificate validation module
- verifies initial certificate storage on first use - ensures stored certificates are retrieved correctly - tests certificate updates and replacement - confirms data persistence across store instances - tests handling of large certificates and empty certificates
1 parent 219615c commit 2cbc38a

File tree

1 file changed

+166
-1
lines changed

1 file changed

+166
-1
lines changed

src/tofu/mod.rs

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{fs::create_dir_all, path::PathBuf, sync::Mutex};
22
use rusqlite::{Connection, OptionalExtension, Result as SqlResult, params};
33

4-
54
#[derive(Debug)]
65
pub struct TofuData {
76
db_path: PathBuf,
@@ -12,6 +11,10 @@ pub struct TofuData {
1211

1312
impl TofuData {
1413
pub fn setData(&self, host: &str, cert: Vec<u8>) -> SqlResult<()> {
14+
if cert.is_empty() {
15+
return Err(rusqlite::Error::InvalidQuery);
16+
}
17+
1518
let connection = self.connection.lock().unwrap();
1619
let sql = "INSERT INTO tofu (host, cert) VALUES (?1, ?2)
1720
ON CONFLICT(host) DO UPDATE SET cert = excluded.cert";
@@ -50,3 +53,165 @@ impl TofuData {
5053
})
5154
}
5255
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use super::*;
60+
use std::fs;
61+
use std::path::Path;
62+
use std::sync::atomic::{AtomicU64, Ordering};
63+
64+
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
65+
66+
fn setup_with_path(db_path: PathBuf) -> SqlResult<TofuData> {
67+
if let Some(parent) = db_path.parent() {
68+
create_dir_all(parent).ok();
69+
}
70+
let connection = Connection::open(&db_path)?;
71+
let sql = "CREATE TABLE IF NOT EXISTS tofu(
72+
host TEXT PRIMARY KEY,
73+
cert BLOB NOT NULL
74+
)";
75+
76+
connection.execute(sql, [])?;
77+
78+
Ok(TofuData {
79+
db_path,
80+
connection: Mutex::new(connection)
81+
})
82+
}
83+
84+
fn create_temp_db() -> (PathBuf, TofuData) {
85+
let temp_dir = std::env::temp_dir();
86+
let counter = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
87+
let db_path = temp_dir.join(format!("tofu_test_{}_{}.sqlite", std::process::id(), counter));
88+
// Clean up any existing test database
89+
let _ = fs::remove_file(&db_path);
90+
let store = setup_with_path(db_path.clone()).unwrap();
91+
(db_path, store)
92+
}
93+
94+
fn cleanup_temp_db(db_path: &Path) {
95+
// Small delay to ensure file handles are released
96+
std::thread::sleep(std::time::Duration::from_millis(50));
97+
let _ = fs::remove_file(db_path);
98+
}
99+
100+
#[test]
101+
fn test_tofu_first_use() {
102+
let (db_path, store) = create_temp_db();
103+
104+
let host = "example.com";
105+
let cert = b"test certificate data".to_vec();
106+
107+
// First use: certificate should not exist
108+
let result = store.getData(host).unwrap();
109+
assert!(result.is_none(), "Certificate should not exist on first use");
110+
111+
store.setData(host, cert.clone()).unwrap();
112+
113+
let stored = store.getData(host).unwrap();
114+
assert_eq!(stored, Some(cert), "Certificate should be stored");
115+
116+
drop(store);
117+
cleanup_temp_db(&db_path);
118+
}
119+
120+
#[test]
121+
fn test_tofu_certificate_match() {
122+
let (db_path, store) = create_temp_db();
123+
124+
let host = "example.com";
125+
let cert = b"test certificate data".to_vec();
126+
127+
// Store certificate
128+
store.setData(host, cert.clone()).unwrap();
129+
130+
// Retrieve and verify it matches
131+
let stored = store.getData(host).unwrap();
132+
assert_eq!(stored, Some(cert), "Stored certificate should match");
133+
134+
drop(store);
135+
cleanup_temp_db(&db_path);
136+
}
137+
138+
#[test]
139+
fn test_tofu_certificate_change() {
140+
let (db_path, store) = create_temp_db();
141+
142+
let host = "example.com";
143+
let cert1 = b"first certificate".to_vec();
144+
let cert2 = b"second certificate".to_vec();
145+
146+
// Store first certificate
147+
store.setData(host, cert1.clone()).unwrap();
148+
let stored1 = store.getData(host).unwrap();
149+
assert_eq!(stored1, Some(cert1.clone()), "First certificate should be stored");
150+
151+
// Update with different certificate
152+
store.setData(host, cert2.clone()).unwrap();
153+
let stored2 = store.getData(host).unwrap();
154+
assert_eq!(stored2, Some(cert2.clone()), "Second certificate should replace first");
155+
assert_ne!(stored2, Some(cert1), "Stored certificate should not match first");
156+
157+
drop(store);
158+
cleanup_temp_db(&db_path);
159+
}
160+
161+
#[test]
162+
fn test_tofu_persistence() {
163+
let temp_dir = std::env::temp_dir();
164+
let db_path = temp_dir.join(format!("tofu_persistence_test_{}.sqlite", std::process::id()));
165+
let _ = fs::remove_file(&db_path);
166+
167+
let host = "example.com";
168+
let cert = b"persistent certificate".to_vec();
169+
170+
// Create first store instance and save certificate
171+
{
172+
let store1 = setup_with_path(db_path.clone()).unwrap();
173+
store1.setData(host, cert.clone()).unwrap();
174+
}
175+
176+
// Create second store instance and verify certificate persists
177+
{
178+
let store2 = setup_with_path(db_path.clone()).unwrap();
179+
let stored = store2.getData(host).unwrap();
180+
assert_eq!(stored, Some(cert), "Certificate should persist across instances");
181+
}
182+
183+
cleanup_temp_db(&db_path);
184+
}
185+
186+
#[test]
187+
fn test_tofu_empty_certificate() {
188+
let (db_path, store) = create_temp_db();
189+
190+
let host = "example.com";
191+
let cert = vec![];
192+
193+
// Attempt to store empty certificate should fail
194+
let result = store.setData(host, cert.clone());
195+
assert!(result.is_err(), "Storing empty certificate should return an error");
196+
197+
drop(store);
198+
cleanup_temp_db(&db_path);
199+
}
200+
201+
#[test]
202+
fn test_tofu_large_certificate() {
203+
let (db_path, store) = create_temp_db();
204+
205+
let host = "example.com";
206+
// Create a large certificate (10KB)
207+
let cert = vec![0x42; 10 * 1024];
208+
209+
// Store large certificate
210+
store.setData(host, cert.clone()).unwrap();
211+
let stored = store.getData(host).unwrap();
212+
assert_eq!(stored, Some(cert), "Large certificate should be stored correctly");
213+
214+
drop(store);
215+
cleanup_temp_db(&db_path);
216+
}
217+
}

0 commit comments

Comments
 (0)