mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-02-20 07:37:26 -05:00
- Refactored the upsert logic for Space, SpaceItem, and SpaceGroup entities to utilize atomic operations, preventing race conditions during synchronization. - Enhanced database interactions with `on_conflict` handling based on UUID for improved data integrity. - Introduced a comprehensive sync setup test to validate the functionality and ensure no UNIQUE constraint errors occur during device pairing and library sharing.
356 lines
9.2 KiB
Rust
356 lines
9.2 KiB
Rust
//! Sync setup test using subprocess framework
|
|
//!
|
|
//! Tests that sync setup works without UNIQUE constraint errors when both devices
|
|
//! have the same deterministic default spaces.
|
|
|
|
use sd_core::testing::CargoTestRunner;
|
|
use sd_core::Core;
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
use tokio::time::timeout;
|
|
|
|
/// Alice's sync setup scenario
|
|
#[tokio::test]
|
|
#[ignore]
|
|
async fn alice_sync_setup_scenario() {
|
|
if env::var("TEST_ROLE").unwrap_or_default() != "alice" {
|
|
return;
|
|
}
|
|
|
|
env::set_var("SPACEDRIVE_TEST_DIR", "/tmp/spacedrive-sync-setup-test");
|
|
|
|
let data_dir = PathBuf::from("/tmp/spacedrive-sync-setup-test/alice");
|
|
|
|
println!("Alice: Starting sync setup test");
|
|
|
|
// Initialize Core
|
|
let mut core = timeout(Duration::from_secs(10), Core::new(data_dir))
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
core.device.set_name("Alice Device".to_string()).unwrap();
|
|
|
|
// Initialize networking
|
|
timeout(Duration::from_secs(10), core.init_networking())
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
println!("Alice: Core initialized");
|
|
|
|
// Create library
|
|
let library = core
|
|
.libraries
|
|
.create_library("Test Library".to_string(), None, core.context.clone())
|
|
.await
|
|
.unwrap();
|
|
|
|
println!("Alice: Library created with ID: {}", library.id());
|
|
|
|
// Write library ID for Bob
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/library_id.txt",
|
|
library.id().to_string(),
|
|
)
|
|
.unwrap();
|
|
|
|
// Write Alice's device ID for Bob
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/alice_device_id.txt",
|
|
core.device.device_id().unwrap().to_string(),
|
|
)
|
|
.unwrap();
|
|
|
|
// Start pairing
|
|
let (pairing_code, _) = if let Some(networking) = core.networking() {
|
|
timeout(
|
|
Duration::from_secs(15),
|
|
networking.start_pairing_as_initiator(false),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.unwrap()
|
|
} else {
|
|
panic!("Networking not initialized");
|
|
};
|
|
|
|
println!("Alice: Pairing code generated");
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/pairing_code.txt",
|
|
&pairing_code,
|
|
)
|
|
.unwrap();
|
|
|
|
// Wait for pairing
|
|
println!("Alice: Waiting for Bob to pair...");
|
|
let mut attempts = 0;
|
|
while attempts < 45 {
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
let connected = core.services.device.get_connected_devices().await.unwrap();
|
|
if !connected.is_empty() {
|
|
println!("Alice: Pairing successful!");
|
|
|
|
// Share library with Bob - THIS IS THE CRITICAL TEST
|
|
let bob_device_id = connected.first().unwrap().clone();
|
|
println!(
|
|
"Alice: Sharing library with Bob (device: {})...",
|
|
bob_device_id
|
|
);
|
|
|
|
use sd_core::infra::action::CoreAction;
|
|
use sd_core::ops::network::sync_setup::{
|
|
LibrarySyncAction, LibrarySyncSetupAction, LibrarySyncSetupInput,
|
|
};
|
|
|
|
let input = LibrarySyncSetupInput {
|
|
local_device_id: core.device.device_id().unwrap(),
|
|
remote_device_id: bob_device_id,
|
|
local_library_id: library.id(),
|
|
remote_library_id: Some(library.id()),
|
|
action: LibrarySyncAction::ShareLocalLibrary {
|
|
library_name: "Test Library".to_string(),
|
|
},
|
|
leader_device_id: core.device.device_id().unwrap(),
|
|
};
|
|
|
|
let action = LibrarySyncSetupAction::from_input(input).unwrap();
|
|
let result = action.execute(core.context.clone()).await;
|
|
|
|
match result {
|
|
Ok(_) => {
|
|
println!("Alice: ✅ Share library SUCCEEDED!");
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/alice_success.txt",
|
|
"success",
|
|
)
|
|
.unwrap();
|
|
}
|
|
Err(e) => {
|
|
println!("Alice: ❌ Share library FAILED: {:?}", e);
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/alice_error.txt",
|
|
format!("{:?}", e),
|
|
)
|
|
.unwrap();
|
|
panic!("Alice: Share library failed: {:?}", e);
|
|
}
|
|
}
|
|
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/alice_paired.txt",
|
|
"success",
|
|
)
|
|
.unwrap();
|
|
|
|
// Give Bob time to process
|
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
|
break;
|
|
}
|
|
|
|
attempts += 1;
|
|
}
|
|
|
|
if attempts >= 45 {
|
|
panic!("Alice: Pairing timeout");
|
|
}
|
|
|
|
println!("Alice: Test completed");
|
|
}
|
|
|
|
/// Bob's sync setup scenario
|
|
#[tokio::test]
|
|
#[ignore]
|
|
async fn bob_sync_setup_scenario() {
|
|
if env::var("TEST_ROLE").unwrap_or_default() != "bob" {
|
|
return;
|
|
}
|
|
|
|
env::set_var("SPACEDRIVE_TEST_DIR", "/tmp/spacedrive-sync-setup-test");
|
|
|
|
let data_dir = PathBuf::from("/tmp/spacedrive-sync-setup-test/bob");
|
|
|
|
println!("Bob: Starting sync setup test");
|
|
|
|
// Initialize Core
|
|
let mut core = timeout(Duration::from_secs(10), Core::new(data_dir))
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
core.device.set_name("Bob Device".to_string()).unwrap();
|
|
|
|
// Initialize networking
|
|
timeout(Duration::from_secs(10), core.init_networking())
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
println!("Bob: Core initialized");
|
|
|
|
// Wait for Alice's library ID
|
|
println!("Bob: Waiting for Alice's library ID...");
|
|
let library_id = loop {
|
|
if let Ok(id) = std::fs::read_to_string("/tmp/spacedrive-sync-setup-test/library_id.txt") {
|
|
break id.trim().to_string();
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
};
|
|
println!("Bob: Found library ID: {}", library_id);
|
|
|
|
// Wait for pairing code
|
|
println!("Bob: Waiting for pairing code...");
|
|
let pairing_code = loop {
|
|
if let Ok(code) =
|
|
std::fs::read_to_string("/tmp/spacedrive-sync-setup-test/pairing_code.txt")
|
|
{
|
|
break code.trim().to_string();
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
};
|
|
|
|
// Join pairing
|
|
println!("Bob: Joining pairing...");
|
|
if let Some(networking) = core.networking() {
|
|
timeout(
|
|
Duration::from_secs(15),
|
|
networking.start_pairing_as_joiner(&pairing_code, false),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
// Wait for pairing completion
|
|
println!("Bob: Waiting for pairing to complete...");
|
|
let mut attempts = 0;
|
|
while attempts < 30 {
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
let connected = core.services.device.get_connected_devices().await.unwrap();
|
|
if !connected.is_empty() {
|
|
println!("Bob: Pairing successful!");
|
|
|
|
// Wait for Alice to share her library (ShareLocalLibrary creates it on Bob's side)
|
|
println!("Bob: Waiting for Alice's ShareLocalLibrary to create library...");
|
|
|
|
let alice_lib_uuid = uuid::Uuid::parse_str(&library_id).unwrap();
|
|
let mut lib_wait_attempts = 0;
|
|
|
|
while lib_wait_attempts < 30 {
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
// Check if library was created by Alice's ShareLocalLibrary action
|
|
if let Some(lib) = core.libraries.get_library(alice_lib_uuid).await {
|
|
println!("Bob: ✅ Library received from Alice! ID: {}", lib.id());
|
|
std::fs::write("/tmp/spacedrive-sync-setup-test/bob_success.txt", "success")
|
|
.unwrap();
|
|
|
|
// Verify sync initialized
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
break;
|
|
}
|
|
|
|
lib_wait_attempts += 1;
|
|
}
|
|
|
|
if lib_wait_attempts >= 30 {
|
|
println!("Bob: ❌ Library was never created - UNIQUE constraint may have failed");
|
|
std::fs::write(
|
|
"/tmp/spacedrive-sync-setup-test/bob_error.txt",
|
|
"Timeout waiting for library from Alice - ShareLocalLibrary may have failed with UNIQUE constraint",
|
|
)
|
|
.unwrap();
|
|
panic!("Bob: Timeout waiting for library");
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
attempts += 1;
|
|
}
|
|
|
|
if attempts >= 30 {
|
|
panic!("Bob: Pairing timeout");
|
|
}
|
|
|
|
println!("Bob: Test completed");
|
|
}
|
|
|
|
/// Main test orchestrator
|
|
#[tokio::test]
|
|
async fn test_sync_setup_no_constraint_error() {
|
|
println!("Testing sync setup with deterministic spaces...");
|
|
|
|
// Clean up
|
|
let _ = std::fs::remove_dir_all("/tmp/spacedrive-sync-setup-test");
|
|
std::fs::create_dir_all("/tmp/spacedrive-sync-setup-test").unwrap();
|
|
|
|
let mut runner = CargoTestRunner::for_test_file("sync_setup_test")
|
|
.with_timeout(Duration::from_secs(120))
|
|
.add_subprocess("alice", "alice_sync_setup_scenario")
|
|
.add_subprocess("bob", "bob_sync_setup_scenario");
|
|
|
|
// Spawn Alice first
|
|
println!("Starting Alice...");
|
|
runner.spawn_single_process("alice").await.unwrap();
|
|
|
|
// Wait for Alice to initialize
|
|
tokio::time::sleep(Duration::from_secs(8)).await;
|
|
|
|
// Start Bob
|
|
println!("Starting Bob...");
|
|
runner.spawn_single_process("bob").await.unwrap();
|
|
|
|
// Wait for success markers
|
|
let result = runner
|
|
.wait_for_success(|_| {
|
|
let alice_paired =
|
|
std::fs::read_to_string("/tmp/spacedrive-sync-setup-test/alice_paired.txt")
|
|
.map(|c| c.trim() == "success")
|
|
.unwrap_or(false);
|
|
|
|
let bob_success =
|
|
std::fs::read_to_string("/tmp/spacedrive-sync-setup-test/bob_success.txt")
|
|
.map(|c| c.trim() == "success")
|
|
.unwrap_or(false);
|
|
|
|
// Check if Bob had an error
|
|
if std::path::Path::new("/tmp/spacedrive-sync-setup-test/bob_error.txt").exists() {
|
|
let error =
|
|
std::fs::read_to_string("/tmp/spacedrive-sync-setup-test/bob_error.txt")
|
|
.unwrap();
|
|
println!("Bob encountered error: {}", error);
|
|
return false;
|
|
}
|
|
|
|
alice_paired && bob_success
|
|
})
|
|
.await;
|
|
|
|
match result {
|
|
Ok(_) => {
|
|
println!("✅ Sync setup test PASSED - no UNIQUE constraint errors!");
|
|
}
|
|
Err(e) => {
|
|
println!("❌ Sync setup test FAILED: {}", e);
|
|
|
|
// Print error if it exists
|
|
if let Ok(error) =
|
|
std::fs::read_to_string("/tmp/spacedrive-sync-setup-test/bob_error.txt")
|
|
{
|
|
println!("Bob's error: {}", error);
|
|
}
|
|
|
|
for (name, output) in runner.get_all_outputs() {
|
|
println!("\n{} output:\n{}", name, output);
|
|
}
|
|
panic!("Sync setup test failed");
|
|
}
|
|
}
|
|
}
|