offline-web/offline-web-storage/tests/integration_tests.rs

432 lines
14 KiB
Rust

use std::{collections::HashMap, sync::Arc};
use offline_web_model::Reference;
use offline_web_storage::ReferenceStore;
async fn create_test_store() -> ReferenceStore {
let store = ReferenceStore::new("sqlite::memory:").await.unwrap();
store
}
#[tokio::test]
async fn test_store_and_retrieve_reference() {
let store = create_test_store().await;
let reference = Reference::new(Some("abc123".to_string()), "test.txt".to_string());
// Store the reference
store.store_reference(&reference).await.unwrap();
// Retrieve it
let retrieved = store.get_reference(&reference.id).await.unwrap();
assert!(retrieved.is_some());
let retrieved = retrieved.unwrap();
assert_eq!(retrieved.id, reference.id);
assert_eq!(retrieved.content_address, reference.content_address);
assert_eq!(retrieved.name, reference.name);
assert_eq!(retrieved.dependents.len(), 0);
}
#[tokio::test]
async fn test_store_reference_with_dependents() {
let store = create_test_store().await;
let dep1 = Arc::new(Reference::new(
Some("dep1".to_string()),
"dep1.txt".to_string(),
));
let dep2 = Arc::new(Reference::new(
Some("dep2".to_string()),
"dep2.txt".to_string(),
));
// Store dependencies first
store.store_reference(&dep1).await.unwrap();
store.store_reference(&dep2).await.unwrap();
let mut parent = Reference::new(Some("parent".to_string()), "parent.txt".to_string());
parent = parent.add_dep(dep1.clone());
parent = parent.add_dep(dep2.clone());
// Store parent with dependencies
store.store_reference(&parent).await.unwrap();
// Retrieve and verify
let retrieved = store.get_reference(&parent.id).await.unwrap().unwrap();
assert_eq!(retrieved.dependents.len(), 2);
let dep_names: Vec<_> = retrieved.dependents.iter().map(|d| &d.name).collect();
assert!(dep_names.contains(&&"dep1.txt".to_string()));
assert!(dep_names.contains(&&"dep2.txt".to_string()));
}
#[tokio::test]
async fn test_get_references_by_name() {
let store = create_test_store().await;
let ref1 = Reference::new(Some("abc1".to_string()), "test.txt".to_string());
let ref2 = Reference::new(Some("abc2".to_string()), "test.txt".to_string());
let ref3 = Reference::new(Some("abc3".to_string()), "other.txt".to_string());
store.store_reference(&ref1).await.unwrap();
store.store_reference(&ref2).await.unwrap();
store.store_reference(&ref3).await.unwrap();
let results = store.get_references_by_name("test.txt").await.unwrap();
assert_eq!(results.len(), 2);
let results = store.get_references_by_name("other.txt").await.unwrap();
assert_eq!(results.len(), 1);
let results = store
.get_references_by_name("nonexistent.txt")
.await
.unwrap();
assert_eq!(results.len(), 0);
}
#[tokio::test]
async fn test_get_references_by_content_address() {
let store = create_test_store().await;
let ref1 = Reference::new(Some("same_content".to_string()), "file1.txt".to_string());
let ref2 = Reference::new(Some("same_content".to_string()), "file2.txt".to_string());
let ref3 = Reference::new(
Some("different_content".to_string()),
"file3.txt".to_string(),
);
store.store_reference(&ref1).await.unwrap();
store.store_reference(&ref2).await.unwrap();
store.store_reference(&ref3).await.unwrap();
let results = store
.get_references_by_content_address("same_content")
.await
.unwrap();
assert_eq!(results.len(), 2);
let results = store
.get_references_by_content_address("different_content")
.await
.unwrap();
assert_eq!(results.len(), 1);
}
#[tokio::test]
async fn test_delete_reference() {
let store = create_test_store().await;
let reference = Reference::new(Some("abc123".to_string()), "test.txt".to_string());
store.store_reference(&reference).await.unwrap();
// Verify it exists
let retrieved = store.get_reference(&reference.id).await.unwrap();
assert!(retrieved.is_some());
// Delete it
let deleted = store.delete_reference(&reference.id).await.unwrap();
assert!(deleted);
// Verify it's gone
let retrieved = store.get_reference(&reference.id).await.unwrap();
assert!(retrieved.is_none());
// Try to delete again
let deleted = store.delete_reference(&reference.id).await.unwrap();
assert!(!deleted);
}
#[tokio::test]
async fn test_list_all_references() {
let store = create_test_store().await;
let ref1 = Reference::new(Some("abc1".to_string()), "a.txt".to_string());
let ref2 = Reference::new(Some("abc2".to_string()), "b.txt".to_string());
let ref3 = Reference::new(Some("abc3".to_string()), "c.txt".to_string());
store.store_reference(&ref1).await.unwrap();
store.store_reference(&ref2).await.unwrap();
store.store_reference(&ref3).await.unwrap();
let all_refs = store.list_all_references().await.unwrap();
assert_eq!(all_refs.len(), 3);
// Should be sorted by name
assert_eq!(all_refs[0].name, "a.txt");
assert_eq!(all_refs[1].name, "b.txt");
assert_eq!(all_refs[2].name, "c.txt");
}
#[tokio::test]
async fn test_update_reference_graph() {
let store = create_test_store().await;
let ref1 = Arc::new(Reference::new(
Some("abc1".to_string()),
"file1.txt".to_string(),
));
let ref2 = Arc::new(Reference::new(
Some("abc2".to_string()),
"file2.txt".to_string(),
));
let mut updated_refs = HashMap::new();
updated_refs.insert(ref1.id.clone(), ref1.clone());
updated_refs.insert(ref2.id.clone(), ref2.clone());
store.update_reference_graph(&updated_refs).await.unwrap();
// Verify both references were stored
let retrieved1 = store.get_reference(&ref1.id).await.unwrap();
let retrieved2 = store.get_reference(&ref2.id).await.unwrap();
assert!(retrieved1.is_some());
assert!(retrieved2.is_some());
assert_eq!(retrieved1.unwrap().name, "file1.txt");
assert_eq!(retrieved2.unwrap().name, "file2.txt");
}
#[tokio::test]
async fn test_store_and_retrieve_content() {
let store = create_test_store().await;
let content = b"Hello, world!";
let content_type = Some("text/plain".to_string());
// Store content
let content_address = store
.store_content(content, content_type.clone())
.await
.unwrap();
// Retrieve content
let retrieved_content = store.get_content(&content_address).await.unwrap();
assert!(retrieved_content.is_some());
assert_eq!(retrieved_content.unwrap(), content);
// Check content info
let content_info = store.get_content_info(&content_address).await.unwrap();
assert!(content_info.is_some());
let info = content_info.unwrap();
assert_eq!(info.content_address, content_address);
assert_eq!(info.content_type, content_type);
// Check content exists
assert!(store.content_exists(&content_address).await.unwrap());
}
#[tokio::test]
async fn test_content_deduplication() {
let store = create_test_store().await;
let content = b"Duplicate content";
// Store same content twice
let addr1 = store.store_content(content, None).await.unwrap();
let addr2 = store.store_content(content, None).await.unwrap();
// Should get same address
assert_eq!(addr1, addr2);
// Should only have one copy in storage
let stats = store.get_storage_stats().await.unwrap();
assert_eq!(stats.content_object_count, 1);
}
#[tokio::test]
async fn test_store_reference_with_content() {
let store = create_test_store().await;
let content = b"File content here";
let name = "test_file.txt".to_string();
let content_type = Some("text/plain".to_string());
// Store reference with content
let reference = store
.store_reference_with_content(name.clone(), content, content_type)
.await
.unwrap();
assert_eq!(reference.name, name);
assert!(reference.content_address.is_some());
// Retrieve reference with content
let (retrieved_ref, retrieved_content) = store
.get_reference_with_content(&reference.id)
.await
.unwrap()
.unwrap();
assert_eq!(retrieved_ref.id, reference.id);
assert_eq!(retrieved_ref.name, name);
assert!(retrieved_content.is_some());
assert_eq!(retrieved_content.unwrap(), content);
}
#[tokio::test]
async fn test_calculate_content_address() {
let content1 = b"Same content";
let content2 = b"Same content";
let content3 = b"Different content";
let addr1 = ReferenceStore::calculate_content_address(content1);
let addr2 = ReferenceStore::calculate_content_address(content2);
let addr3 = ReferenceStore::calculate_content_address(content3);
assert_eq!(addr1, addr2);
assert_ne!(addr1, addr3);
// Should be a valid hex string
assert!(addr1.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(addr1.len(), 128); // Blake2b512 produces 64 bytes = 128 hex chars
}
#[tokio::test]
async fn test_delete_content() {
let store = create_test_store().await;
let content = b"Content to delete";
let content_address = store.store_content(content, None).await.unwrap();
// Verify content exists
assert!(store.content_exists(&content_address).await.unwrap());
// Delete content
let deleted = store.delete_content(&content_address).await.unwrap();
assert!(deleted);
// Verify content is gone
assert!(!store.content_exists(&content_address).await.unwrap());
let retrieved = store.get_content(&content_address).await.unwrap();
assert!(retrieved.is_none());
// Try to delete again
let deleted_again = store.delete_content(&content_address).await.unwrap();
assert!(!deleted_again);
}
#[tokio::test]
async fn test_cleanup_unreferenced_content() {
let store = create_test_store().await;
// Store some content with references
let content1 = b"Referenced content";
let ref1 = store
.store_reference_with_content("file1.txt".to_string(), content1, None)
.await
.unwrap();
// Store content without references
let content2 = b"Unreferenced content 1";
let content3 = b"Unreferenced content 2";
let _addr2 = store.store_content(content2, None).await.unwrap();
let _addr3 = store.store_content(content3, None).await.unwrap();
// Initial stats
let stats = store.get_storage_stats().await.unwrap();
assert_eq!(stats.content_object_count, 3);
assert_eq!(stats.reference_count, 1);
// List unreferenced content
let unreferenced = store.list_unreferenced_content().await.unwrap();
assert_eq!(unreferenced.len(), 2);
// Cleanup unreferenced content
let cleaned_up = store.cleanup_unreferenced_content().await.unwrap();
assert_eq!(cleaned_up, 2);
// Check final stats
let final_stats = store.get_storage_stats().await.unwrap();
assert_eq!(final_stats.content_object_count, 1);
assert_eq!(final_stats.reference_count, 1);
// Referenced content should still exist
let (retrieved_ref, retrieved_content) = store
.get_reference_with_content(&ref1.id)
.await
.unwrap()
.unwrap();
assert_eq!(retrieved_ref.id, ref1.id);
assert_eq!(retrieved_content.unwrap(), content1);
}
#[tokio::test]
async fn test_storage_stats() {
let store = create_test_store().await;
// Initial stats should be empty
let stats = store.get_storage_stats().await.unwrap();
assert_eq!(stats.content_object_count, 0);
assert_eq!(stats.total_content_size, 0);
assert_eq!(stats.reference_count, 0);
// Add some content and references
let content1 = b"First file";
let content2 = b"Second file content";
let _ref1 = store
.store_reference_with_content("file1.txt".to_string(), content1, None)
.await
.unwrap();
let _ref2 = store
.store_reference_with_content("file2.txt".to_string(), content2, None)
.await
.unwrap();
// Check updated stats
let final_stats = store.get_storage_stats().await.unwrap();
assert_eq!(final_stats.content_object_count, 2);
assert_eq!(final_stats.reference_count, 2);
}
#[tokio::test]
async fn test_reference_with_content_and_dependencies() {
let store = create_test_store().await;
// Create dependencies with content
let dep1_content = b"Dependency 1 content";
let dep2_content = b"Dependency 2 content";
let dep1 = store
.store_reference_with_content("dep1.txt".to_string(), dep1_content, None)
.await
.unwrap();
let dep2 = store
.store_reference_with_content("dep2.txt".to_string(), dep2_content, None)
.await
.unwrap();
// Create parent with content and dependencies
let parent_content = b"Parent content";
let parent = store
.store_reference_with_content("parent.txt".to_string(), parent_content, None)
.await
.unwrap();
// Add dependencies to parent
let parent_with_deps = parent.add_dep(Arc::new(dep1)).add_dep(Arc::new(dep2));
store.store_reference(&parent_with_deps).await.unwrap();
// Retrieve parent with content
let (retrieved_parent, retrieved_content) = store
.get_reference_with_content(&parent_with_deps.id)
.await
.unwrap()
.unwrap();
assert_eq!(retrieved_parent.dependents.len(), 2);
assert_eq!(retrieved_content.unwrap(), parent_content);
// Check that dependencies also have their content
for dep in &retrieved_parent.dependents {
let (_, dep_content) = store
.get_reference_with_content(&dep.id)
.await
.unwrap()
.unwrap();
assert!(dep_content.is_some());
}
}