Files
sanctum/tests/integration_test.rs
T

161 lines
5.8 KiB
Rust

use std::io::SeekFrom;
use std::path::PathBuf;
use bytes::Bytes;
use dav_server::{
davpath::DavPath,
fs::{DavFileSystem, OpenOptions, ReadDirMeta},
};
use futures_util::StreamExt;
use sanctum::{
crypto::{
derive_kek, generate_dek, generate_salt, unwrap_dek, wrap_dek, KdfParams, CHUNK_SIZE,
FORMAT_VERSION,
},
storage::Database,
vfs::SanctumFs,
};
#[tokio::test]
async fn test_sanctum_full_container_lifecycle() {
let temp_dir = std::env::temp_dir();
let container_path: PathBuf = temp_dir.join(format!("test_sanctum_{}.sanctum", std::process::id()));
// Aufräumen, falls alte Testdatei existiert
if container_path.exists() {
let _ = std::fs::remove_file(&container_path);
}
let password = "CorrectMasterPassword2026!";
let wrong_password = "WrongMasterPassword!";
// 1. Initialisierung
let salt = generate_salt();
let kdf_params = KdfParams {
memory_cost: 1024, // Schnell für Tests
time_cost: 1,
parallelism: 1,
};
let kek = derive_kek(password, &salt, &kdf_params).expect("KEK derivation");
let dek = generate_dek();
let (wrapped_dek, header_nonce, header_tag) =
wrap_dek(&kek, &dek).expect("DEK wrapping");
let db = Database::open(&container_path).expect("Open database");
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag)
.expect("Init schema");
db.checkpoint().expect("Checkpoint");
// 2. Header & Magic Bytes Prüfung
let meta = db.read_meta().expect("Read meta");
assert_eq!(meta.version, FORMAT_VERSION);
assert_eq!(meta.kdf_salt, salt);
assert_eq!(meta.wrapped_dek, wrapped_dek);
// Falsches Passwort schlägt fehl
let wrong_kek = derive_kek(wrong_password, &meta.kdf_salt, &meta.kdf_params).unwrap();
assert!(unwrap_dek(&wrong_kek, &meta.wrapped_dek, &meta.header_nonce, &meta.header_tag).is_err());
// Richtiges Passwort entschlüsselt DEK
let unwrapped_dek =
unwrap_dek(&kek, &meta.wrapped_dek, &meta.header_nonce, &meta.header_tag).unwrap();
assert_eq!(*dek, *unwrapped_dek);
// 3. VFS Filesystem-Operationen (WebDAV Trait)
let fs = SanctumFs::new(db.clone(), unwrapped_dek);
// Ordner erstellen
let docs_path = DavPath::new("/documents").unwrap();
fs.create_dir(&docs_path).await.expect("Create /documents");
let docs_meta = fs.metadata(&docs_path).await.expect("Metadata /documents");
assert!(docs_meta.is_dir());
// Multi-MB Datei über mehrere Chunks hinweg schreiben (2.5 MB = 3 Chunks à 1 MB)
let file_path = DavPath::new("/documents/large_payload.bin").unwrap();
let mut opts_write = OpenOptions::default();
opts_write.write = true;
opts_write.create_new = true;
let mut file = fs.open(&file_path, opts_write).await.expect("Open for write");
let payload_size = (2.5 * CHUNK_SIZE as f64) as usize;
let mut sample_data = Vec::with_capacity(payload_size);
for i in 0..payload_size {
sample_data.push((i % 251) as u8);
}
file.write_bytes(Bytes::copy_from_slice(&sample_data))
.await
.expect("Write 2.5 MB");
file.flush().await.expect("Flush file");
drop(file);
// Metadaten verifizieren
let file_meta = fs.metadata(&file_path).await.expect("Metadata file");
assert_eq!(file_meta.len(), payload_size as u64);
assert!(!file_meta.is_dir());
// Datei lesen & Seek über Chunk-Grenzen testen
let mut opts_read = OpenOptions::default();
opts_read.read = true;
let mut read_file = fs.open(&file_path, opts_read).await.expect("Open for read");
// Seek mitten in den 2. Chunk (1 MB + 500 Bytes)
let seek_offset = (CHUNK_SIZE + 500) as u64;
let new_pos = read_file
.seek(SeekFrom::Start(seek_offset))
.await
.expect("Seek");
assert_eq!(new_pos, seek_offset);
// 2000 Bytes lesen (überschreitet evtl. Chunk-Grenze oder bleibt im Chunk)
let read_len = 2000;
let read_chunk_bytes = read_file.read_bytes(read_len).await.expect("Read bytes");
assert_eq!(read_chunk_bytes.len(), read_len);
assert_eq!(
&read_chunk_bytes[..],
&sample_data[seek_offset as usize..seek_offset as usize + read_len]
);
drop(read_file);
// Datei umbenennen
let renamed_path = DavPath::new("/documents/renamed_payload.bin").unwrap();
fs.rename(&file_path, &renamed_path).await.expect("Rename file");
assert!(fs.metadata(&file_path).await.is_err());
let renamed_meta = fs.metadata(&renamed_path).await.expect("Metadata renamed");
assert_eq!(renamed_meta.len(), payload_size as u64);
// Datei kopieren
let copy_path = DavPath::new("/documents/copy_payload.bin").unwrap();
fs.copy(&renamed_path, &copy_path).await.expect("Copy file");
let copy_meta = fs.metadata(&copy_path).await.expect("Metadata copy");
assert_eq!(copy_meta.len(), payload_size as u64);
// Verzeichnis auflisten
let stream = fs.read_dir(&docs_path, ReadDirMeta::None).await.expect("Read dir");
let entries: Vec<_> = stream.collect().await;
assert_eq!(entries.len(), 2); // renamed_payload.bin & copy_payload.bin
// Dateien und Verzeichnis löschen
fs.remove_file(&renamed_path).await.expect("Remove renamed");
fs.remove_file(&copy_path).await.expect("Remove copy");
fs.remove_dir(&docs_path).await.expect("Remove dir");
// Prüfen, dass Root leer ist
let root_path = DavPath::new("/").unwrap();
let root_stream = fs.read_dir(&root_path, ReadDirMeta::None).await.expect("Read root");
let root_entries: Vec<_> = root_stream.collect().await;
assert_eq!(root_entries.len(), 0);
// Finaler DB Checkpoint
db.checkpoint().expect("Final checkpoint");
drop(fs);
drop(db);
// Verifizieren, dass die Containerdatei existiert und aufgeräumt werden kann
assert!(container_path.exists());
let _ = std::fs::remove_file(&container_path);
}