spacedrive/core/tests/phase_snapshot_test.rs
Jamie Pine 69e825fe06 Revert sync to November 14th - Add new sync tests
1. Watermark Messages: - Nov 16: Simple - single my_state_watermark,
single state_watermark in response - Main: Complex - per-resource
my_resource_watermarks HashMap, resource counts, content hashes 2.
DataAvailableNotification: - Nov 16: Doesn't exist - Main: New message
type that triggers watermark exchanges 3. FK Mapper: - Nov 16: Sets
missing FKs to NULL, applies all records - Main: Marks records with
missing FKs as failed, filters them out 4. Watermark Storage (internal,
not in messages): - Nov 16: Single last_watermark per resource - Main:
Dual watermarks - cursor_watermark and
2025-11-24 14:27:32 -08:00

274 lines
7.9 KiB
Rust

//! Comprehensive phase snapshot test
//!
//! This test indexes Desktop with Content mode and captures:
//! - All events received during each phase
//! - Directory listing query results after each phase
//! - Saves snapshots to disk for manual inspection
use sd_core::{
infra::{
db::entities,
event::Event,
},
location::{create_location, IndexMode, LocationCreateArgs},
Core,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter};
use std::{sync::Arc, time::Duration};
use tempfile::TempDir;
#[tokio::test]
async fn capture_phase_snapshots() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
eprintln!("\nPHASE SNAPSHOT TEST - Indexing Desktop\n");
eprintln!("{}", "=".repeat(80));
// Setup
let temp_dir = TempDir::new()?;
let core = Core::new(temp_dir.path().to_path_buf()).await?;
let library = core
.libraries
.create_library("Phase Snapshot Test", None, core.context.clone())
.await?;
eprintln!("Created test library");
// Get Desktop path
let desktop_path = dirs::desktop_dir()
.expect("Could not find Desktop directory");
eprintln!("Will index: {:?}", desktop_path);
// Register device
let db = library.db();
let device = core.device.to_device()?;
let device_record = match entities::device::Entity::find()
.filter(entities::device::Column::Uuid.eq(device.id))
.one(db.conn())
.await?
{
Some(existing) => existing,
None => {
let device_model: entities::device::ActiveModel = device.into();
device_model.insert(db.conn()).await?
}
};
// Create output directory for snapshots in project root
let snapshot_dir = std::path::PathBuf::from("/Users/jamespine/Projects/spacedrive/test_snapshots");
std::fs::create_dir_all(&snapshot_dir)?;
eprintln!("Snapshots will be saved to: {:?}\n", snapshot_dir);
// Collect all events
let events_collected = Arc::new(tokio::sync::Mutex::new(Vec::new()));
let events_clone = events_collected.clone();
let mut subscriber = core.events.subscribe();
tokio::spawn(async move {
while let Ok(event) = subscriber.recv().await {
events_clone.lock().await.push(event);
}
});
tokio::time::sleep(Duration::from_millis(100)).await;
eprintln!("Starting Content mode indexing of Desktop...\n");
let location_args = LocationCreateArgs {
path: desktop_path.clone(),
name: Some("Desktop".to_string()),
index_mode: IndexMode::Content,
};
let _location_db_id = create_location(
library.clone(),
&core.events,
location_args,
device_record.id,
)
.await?;
// Monitor job completion
eprintln!("Waiting for indexing job to complete...\n");
let mut indexing_started = false;
let mut processing_complete = false;
let mut content_phase_complete = false;
let mut job_completed = false;
let mut phase_events: Vec<(String, Vec<Event>)> = Vec::new();
let mut current_phase_events: Vec<Event> = Vec::new();
let mut current_phase_name = String::from("Initial");
let mut last_processed_event_count = 0;
// Poll for up to 2 minutes
for iteration in 0..120 {
tokio::time::sleep(Duration::from_secs(1)).await;
let events = events_collected.lock().await;
let all_events_count = events.len();
// Process only new events since last check
let new_events: Vec<Event> = if all_events_count > last_processed_event_count {
events[last_processed_event_count..].to_vec()
} else {
vec![]
};
last_processed_event_count = all_events_count;
drop(events); // Release lock
for event in new_events {
match &event {
Event::IndexingStarted { .. } if !indexing_started => {
eprintln!("Phase: Discovery Started");
indexing_started = true;
// Save previous phase
if !current_phase_events.is_empty() {
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
}
current_phase_name = "Discovery".to_string();
current_phase_events.clear();
}
Event::JobProgress { job_type, message, .. } if job_type == "indexer" => {
if let Some(msg) = message {
if msg.contains("Processing entries") && !processing_complete {
eprintln!("Phase: Processing");
processing_complete = true;
// Save previous phase
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
current_phase_name = "Processing".to_string();
current_phase_events.clear();
} else if msg.contains("Generating content identities") && !content_phase_complete {
eprintln!("Phase: Content Identification");
content_phase_complete = true;
// Save previous phase
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
current_phase_name = "Content".to_string();
current_phase_events.clear();
}
}
}
Event::IndexingCompleted { .. } => {
if !job_completed {
eprintln!("Indexing Complete\n");
job_completed = true;
// Save final phase
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
}
}
Event::JobCompleted { job_type, .. } => {
if job_type == "indexer" && !job_completed {
eprintln!("Job Complete\n");
job_completed = true;
// Save final phase
if !current_phase_events.is_empty() {
phase_events.push((current_phase_name.clone(), current_phase_events.clone()));
}
}
}
_ => {}
}
current_phase_events.push(event);
}
if job_completed {
break;
}
}
eprintln!("Captured {} phases\n", phase_events.len());
// Save phase events to disk with detailed structure
for (phase_name, phase_event_list) in &phase_events {
let mut phase_data = serde_json::json!({
"phase_name": phase_name,
"total_events": phase_event_list.len(),
"file_batches": [],
"all_files": [],
});
let mut all_files_in_phase = vec![];
for event in phase_event_list {
if let Event::ResourceChangedBatch { resource_type, resources } = event {
if resource_type == "file" {
if let Some(files_array) = resources.as_array() {
// Save batch metadata
phase_data["file_batches"].as_array_mut().unwrap().push(serde_json::json!({
"batch_size": files_array.len(),
"sample_file": files_array.first(),
}));
// Collect all files
for file in files_array {
all_files_in_phase.push(file.clone());
}
}
}
}
}
phase_data["all_files"] = serde_json::json!(all_files_in_phase);
phase_data["total_files"] = serde_json::json!(all_files_in_phase.len());
let filename = snapshot_dir.join(format!("phase_{}.json", phase_name));
let json = serde_json::to_string_pretty(&phase_data)?;
std::fs::write(&filename, json)?;
eprintln!("Saved phase {} with {} total files across {} batches",
phase_name,
all_files_in_phase.len(),
phase_data["file_batches"].as_array().unwrap().len()
);
}
// Query directory listing and save snapshot
eprintln!("\nQuerying final directory listing state...\n");
use sd_core::infra::db::entities::entry;
// Just load all entries and construct Files to see what query would return
let entries_with_content = entry::Entity::find()
.filter(entry::Column::Kind.eq(0)) // Files only
.all(db.conn())
.await?;
eprintln!("Found {} file entries in database", entries_with_content.len());
// Save ALL entries to check ID matching
let all_entries: Vec<_> = entries_with_content.iter().map(|e| {
serde_json::json!({
"entry_id": e.id,
"entry_uuid": e.uuid,
"name": e.name,
"content_id_fk": e.content_id,
"extension": e.extension,
})
}).collect();
let entries_file = snapshot_dir.join("db_entries_all.json");
std::fs::write(&entries_file, serde_json::to_string_pretty(&all_entries)?)?;
eprintln!("Saved {} database entries", all_entries.len());
eprintln!("\n{}", "=".repeat(80));
eprintln!("TEST COMPLETE");
eprintln!("{}", "=".repeat(80));
eprintln!("\nSnapshots saved to: {:?}", snapshot_dir);
eprintln!("\nFiles created:");
for entry in std::fs::read_dir(&snapshot_dir)? {
let entry = entry?;
eprintln!(" - {}", entry.file_name().to_string_lossy());
}
eprintln!();
Ok(())
}