From caf5a242fd4ad924cff8209ce1d5139dd505b2a2 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Thu, 25 Dec 2025 06:45:17 -0800 Subject: [PATCH] Implement TypeScript integration tests with Rust bridge - Added a new test suite in `core/tests/typescript_bridge_test.rs` to facilitate end-to-end testing between Rust and TypeScript. - Introduced `IndexingHarnessBuilder` enhancements to support a daemon RPC server for TypeScript tests. - Created TypeScript test files for `useNormalizedQuery` to validate file move and folder rename operations. - Configured Bun test environment with `bunfig.toml` and setup scripts for improved testing efficiency. - Updated `TcpSocketTransport` to handle TCP connections for the TypeScript client, ensuring robust communication with the Rust backend. - Enhanced logging and error handling for better visibility during test execution. --- bun.lockb | Bin 1032362 -> 1033362 bytes core/tests/helpers/indexing_harness.rs | 50 ++- core/tests/typescript_bridge_test.rs | 381 ++++++++++++++++++ packages/ts-client/package.json | 2 + packages/ts-client/src/client.ts | 10 +- packages/ts-client/src/transport.ts | 130 ++++++ .../ts-client/tests/integration/README.md | 279 +++++++++++++ .../ts-client/tests/integration/bunfig.toml | 4 + packages/ts-client/tests/integration/setup.ts | 11 + .../useNormalizedQuery.folder-rename.test.ts | 239 +++++++++++ .../integration/useNormalizedQuery.test.ts | 254 ++++++++++++ 11 files changed, 1358 insertions(+), 2 deletions(-) create mode 100644 core/tests/typescript_bridge_test.rs create mode 100644 packages/ts-client/tests/integration/README.md create mode 100644 packages/ts-client/tests/integration/bunfig.toml create mode 100644 packages/ts-client/tests/integration/setup.ts create mode 100644 packages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts create mode 100644 packages/ts-client/tests/integration/useNormalizedQuery.test.ts diff --git a/bun.lockb b/bun.lockb index 3fa3c7108780131dd3e83821b51177fb4769aee4..bfae9d8e03cb974d584eef36b039d72918bdac4a 100755 GIT binary patch delta 129833 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%0DKB=`?|_)ZU5 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 AegFUf delta 128085 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 Self { + self.daemon_enabled = true; + self + } + /// Build the harness pub async fn build(self) -> anyhow::Result { // Use home directory for proper filesystem watcher support on macOS @@ -92,6 +100,39 @@ impl IndexingHarnessBuilder { .await? .ok_or_else(|| anyhow::anyhow!("Device not found after registration"))?; + // Wrap core in Arc for shared access + let core = Arc::new(core); + + // Start daemon RPC server if enabled (for TypeScript bridge tests) + let daemon_socket_addr = if self.daemon_enabled { + // Find an available port by binding to 0 and getting the actual port + let temp_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?; + let actual_port = temp_listener.local_addr()?.port(); + let socket_addr = format!("127.0.0.1:{}", actual_port); + drop(temp_listener); // Release the port + + tracing::info!("Starting daemon RPC server on {}", socket_addr); + + let core_for_daemon = core.clone(); + let socket_addr_clone = socket_addr.clone(); + + // Spawn daemon server in background + tokio::spawn(async move { + let mut server = + sd_core::infra::daemon::rpc::RpcServer::new(socket_addr_clone, core_for_daemon); + if let Err(e) = server.start().await { + tracing::error!("Daemon RPC server error: {}", e); + } + }); + + // Wait for server to start accepting connections + tokio::time::sleep(Duration::from_secs(1)).await; + + Some(socket_addr) + } else { + None + }; + Ok(IndexingHarness { _test_name: self.test_name, _test_root: test_root, @@ -100,6 +141,7 @@ impl IndexingHarnessBuilder { library, device_id, device_db_id: device_record.id, + daemon_socket_addr, }) } } @@ -109,10 +151,11 @@ pub struct IndexingHarness { _test_name: String, _test_root: PathBuf, pub snapshot_dir: PathBuf, - pub core: Core, + pub core: Arc, pub library: Arc, pub device_id: Uuid, pub device_db_id: i32, + daemon_socket_addr: Option, } impl IndexingHarness { @@ -121,6 +164,11 @@ impl IndexingHarness { &self._test_root } + /// Get the daemon socket address (only available if daemon is enabled) + pub fn daemon_socket_addr(&self) -> Option<&str> { + self.daemon_socket_addr.as_deref() + } + /// Create a test location directory with files pub async fn create_test_location(&self, name: &str) -> anyhow::Result { let location_dir = self.temp_path().join(name); diff --git a/core/tests/typescript_bridge_test.rs b/core/tests/typescript_bridge_test.rs new file mode 100644 index 000000000..6f47ddb46 --- /dev/null +++ b/core/tests/typescript_bridge_test.rs @@ -0,0 +1,381 @@ +//! TypeScript Integration Test Bridge +//! +//! This test harness sets up a real Spacedrive daemon with indexed locations, +//! then spawns TypeScript tests that interact with it via the ts-client. +//! This enables true end-to-end testing across the Rust backend and TypeScript frontend. +//! +//! ## Architecture +//! +//! 1. Rust test creates daemon + indexed location using IndexingHarnessBuilder +//! 2. Connection info (socket path, library ID) written to JSON file +//! 3. Rust spawns `bun test` with specific TypeScript test file +//! 4. TypeScript test reads connection info, connects to daemon via ts-client +//! 5. TypeScript test performs file operations and cache assertions +//! 6. Rust validates test exit code and cleans up +//! +//! ## Running +//! +//! ```bash +//! cargo test typescript_bridge -- --nocapture +//! ``` + +mod helpers; + +use helpers::*; +use sd_core::location::IndexMode; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use tokio::time::Duration; + +/// Connection info passed from Rust test harness to TypeScript tests +#[derive(Debug, Serialize, Deserialize)] +struct TestBridgeConfig { + /// TCP socket address for daemon connection (e.g., "127.0.0.1:6969") + socket_addr: String, + /// Library UUID + library_id: String, + /// Location database ID + location_db_id: i32, + /// Physical path to the test location root + location_path: PathBuf, + /// Test data directory (for file operations) + test_data_path: PathBuf, +} + +#[tokio::test] +async fn test_typescript_use_normalized_query_with_file_moves() -> anyhow::Result<()> { + // Setup: Create daemon with indexed location + let harness = IndexingHarnessBuilder::new("typescript_bridge_file_moves") + .enable_daemon() // Start RPC server for TypeScript client + .build() + .await?; + + let test_location = harness.create_test_location("test_moves").await?; + + // Create initial folder structure + test_location.create_dir("folder_a").await?; + test_location.create_dir("folder_b").await?; + test_location + .write_file("folder_a/file1.txt", "Content 1") + .await?; + test_location + .write_file("folder_a/file2.rs", "fn main() {}") + .await?; + test_location + .write_file("folder_b/file3.md", "# Docs") + .await?; + + // Index the location + let location = test_location + .index("TypeScript Test Location", IndexMode::Shallow) + .await?; + + // Wait for indexing to complete + tokio::time::sleep(Duration::from_secs(1)).await; + + // Get daemon socket address + let socket_addr = harness + .daemon_socket_addr() + .expect("Daemon should be enabled") + .to_string(); + + // Prepare bridge config + let bridge_config = TestBridgeConfig { + socket_addr: socket_addr.clone(), + library_id: harness.library.id().to_string(), + location_db_id: location.db_id, + location_path: test_location.path().to_path_buf(), + test_data_path: harness.temp_path().to_path_buf(), + }; + + // Write config to temp file for TypeScript to read + let config_path = harness.temp_path().join("typescript_bridge_config.json"); + let config_json = serde_json::to_string_pretty(&bridge_config)?; + tokio::fs::write(&config_path, config_json).await?; + + tracing::info!("Bridge config written to: {}", config_path.display()); + tracing::info!("Socket address: {}", socket_addr); + tracing::info!("Library ID: {}", bridge_config.library_id); + + // Spawn TypeScript test process + let ts_test_file = "packages/ts-client/tests/integration/useNormalizedQuery.test.ts"; + let workspace_root = std::env::current_dir()?.parent().unwrap().to_path_buf(); + let ts_test_path = workspace_root.join(ts_test_file); + let bun_config = workspace_root.join("packages/ts-client/tests/integration/bunfig.toml"); + + eprintln!("\n=== TypeScript Bridge Test ==="); + eprintln!("Workspace root: {}", workspace_root.display()); + eprintln!("Test file: {}", ts_test_path.display()); + eprintln!("Bun config: {}", bun_config.display()); + eprintln!("Config path: {}", config_path.display()); + eprintln!("Socket address: {}", socket_addr); + eprintln!("Library ID: {}", bridge_config.library_id); + eprintln!("==============================\n"); + + // Check if test file exists + if !ts_test_path.exists() { + anyhow::bail!("TypeScript test file not found: {}", ts_test_path.display()); + } + + let output = tokio::process::Command::new("bun") + .arg("test") + .arg("--config") + .arg(&bun_config) + .arg(&ts_test_path) + .env("BRIDGE_CONFIG_PATH", config_path.to_str().unwrap()) + .env("RUST_LOG", "debug") + .current_dir(&workspace_root) + .output() + .await?; + + // Always print TypeScript output to stderr for visibility + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !stdout.is_empty() { + eprintln!("\n=== TypeScript stdout ===\n{}\n", stdout); + } + if !stderr.is_empty() { + eprintln!("\n=== TypeScript stderr ===\n{}\n", stderr); + } + + // Verify TypeScript test passed + if !output.status.success() { + anyhow::bail!( + "TypeScript test failed with exit code: {:?}", + output.status.code() + ); + } + + tracing::info!("TypeScript test passed! ✓"); + + // Cleanup + harness.shutdown().await?; + Ok(()) +} + +#[tokio::test] +async fn test_typescript_use_normalized_query_with_folder_renames() -> anyhow::Result<()> { + // Setup: Create daemon with indexed location + let harness = IndexingHarnessBuilder::new("typescript_bridge_folder_renames") + .enable_daemon() // Start RPC server for TypeScript client + .build() + .await?; + + let test_location = harness.create_test_location("test_renames").await?; + + // Create initial folder structure + test_location.create_dir("original_folder").await?; + test_location + .write_file("original_folder/file1.txt", "Content 1") + .await?; + test_location + .write_file("original_folder/file2.rs", "fn main() {}") + .await?; + test_location + .write_file("original_folder/nested/file3.md", "# Docs") + .await?; + + // Index the location + let location = test_location + .index("TypeScript Test Location", IndexMode::Shallow) + .await?; + + // Wait for indexing to complete + tokio::time::sleep(Duration::from_secs(1)).await; + + // Get daemon socket address + let socket_addr = harness + .daemon_socket_addr() + .expect("Daemon should be enabled") + .to_string(); + + // Prepare bridge config + let bridge_config = TestBridgeConfig { + socket_addr: socket_addr.clone(), + library_id: harness.library.id().to_string(), + location_db_id: location.db_id, + location_path: test_location.path().to_path_buf(), + test_data_path: harness.temp_path().to_path_buf(), + }; + + // Write config to temp file + let config_path = harness.temp_path().join("typescript_bridge_config.json"); + let config_json = serde_json::to_string_pretty(&bridge_config)?; + tokio::fs::write(&config_path, config_json).await?; + + tracing::info!("Bridge config written to: {}", config_path.display()); + + // Spawn TypeScript test process + let ts_test_file = + "packages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts"; + let workspace_root = std::env::current_dir()?.parent().unwrap().to_path_buf(); + let ts_test_path = workspace_root.join(ts_test_file); + let bun_config = workspace_root.join("packages/ts-client/tests/integration/bunfig.toml"); + + eprintln!("\n=== TypeScript Bridge Test ==="); + eprintln!("Workspace root: {}", workspace_root.display()); + eprintln!("Test file: {}", ts_test_path.display()); + eprintln!("Bun config: {}", bun_config.display()); + eprintln!("Config path: {}", config_path.display()); + eprintln!("Socket address: {}", socket_addr); + eprintln!("Library ID: {}", bridge_config.library_id); + eprintln!("==============================\n"); + + // Check if test file exists + if !ts_test_path.exists() { + anyhow::bail!("TypeScript test file not found: {}", ts_test_path.display()); + } + + let output = tokio::process::Command::new("bun") + .arg("test") + .arg("--config") + .arg(&bun_config) + .arg(&ts_test_path) + .env("BRIDGE_CONFIG_PATH", config_path.to_str().unwrap()) + .env("RUST_LOG", "debug") + .current_dir(&workspace_root) + .output() + .await?; + + // Always print TypeScript output to stderr for visibility + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !stdout.is_empty() { + eprintln!("\n=== TypeScript stdout ===\n{}\n", stdout); + } + if !stderr.is_empty() { + eprintln!("\n=== TypeScript stderr ===\n{}\n", stderr); + } + + // Verify test passed + if !output.status.success() { + anyhow::bail!( + "TypeScript test failed with exit code: {:?}", + output.status.code() + ); + } + + tracing::info!("TypeScript test passed! ✓"); + + harness.shutdown().await?; + Ok(()) +} + +#[tokio::test] +async fn test_typescript_use_normalized_query_with_bulk_moves() -> anyhow::Result<()> { + // Setup: Create daemon with indexed location + let harness = IndexingHarnessBuilder::new("typescript_bridge_bulk_moves") + .enable_daemon() // Start RPC server for TypeScript client + .build() + .await?; + + let test_location = harness.create_test_location("test_bulk").await?; + + // Create subfolder with 20 files + test_location.create_dir("bulk_test").await?; + for i in 1..=20 { + test_location + .write_file( + &format!("bulk_test/file{:02}.txt", i), + &format!("Content of file {}", i), + ) + .await?; + } + + // Also create a couple files in root to verify they're not affected + test_location + .write_file("root_file1.md", "# Root file") + .await?; + test_location + .write_file("root_file2.rs", "fn main() {}") + .await?; + + // Index the location + let location = test_location + .index("TypeScript Bulk Test Location", IndexMode::Shallow) + .await?; + + // Wait for indexing to complete + tokio::time::sleep(Duration::from_secs(2)).await; + + // Get daemon socket address + let socket_addr = harness + .daemon_socket_addr() + .expect("Daemon should be enabled") + .to_string(); + + // Prepare bridge config + let bridge_config = TestBridgeConfig { + socket_addr: socket_addr.clone(), + library_id: harness.library.id().to_string(), + location_db_id: location.db_id, + location_path: test_location.path().to_path_buf(), + test_data_path: harness.temp_path().to_path_buf(), + }; + + // Write config to temp file + let config_path = harness.temp_path().join("typescript_bridge_config.json"); + let config_json = serde_json::to_string_pretty(&bridge_config)?; + tokio::fs::write(&config_path, config_json).await?; + + tracing::info!("Bridge config written to: {}", config_path.display()); + + // Spawn TypeScript test process - run only the bulk moves test + let ts_test_file = "packages/ts-client/tests/integration/useNormalizedQuery.test.ts"; + let workspace_root = std::env::current_dir()?.parent().unwrap().to_path_buf(); + let ts_test_path = workspace_root.join(ts_test_file); + let bun_config = workspace_root.join("packages/ts-client/tests/integration/bunfig.toml"); + + eprintln!("\n=== TypeScript Bridge Test (Bulk Moves) ==="); + eprintln!("Workspace root: {}", workspace_root.display()); + eprintln!("Test file: {}", ts_test_path.display()); + eprintln!("Bun config: {}", bun_config.display()); + eprintln!("Config path: {}", config_path.display()); + eprintln!("Socket address: {}", socket_addr); + eprintln!("Library ID: {}", bridge_config.library_id); + eprintln!("==============================\n"); + + // Check if test file exists + if !ts_test_path.exists() { + anyhow::bail!("TypeScript test file not found: {}", ts_test_path.display()); + } + + let output = tokio::process::Command::new("bun") + .arg("test") + .arg("--config") + .arg(&bun_config) + .arg(&ts_test_path) + .arg("--test-name-pattern") + .arg("should update cache when moving 20 files from subfolder to root") + .env("BRIDGE_CONFIG_PATH", config_path.to_str().unwrap()) + .env("RUST_LOG", "debug") + .current_dir(&workspace_root) + .output() + .await?; + + // Always print TypeScript output to stderr for visibility + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !stdout.is_empty() { + eprintln!("\n=== TypeScript stdout ===\n{}\n", stdout); + } + if !stderr.is_empty() { + eprintln!("\n=== TypeScript stderr ===\n{}\n", stderr); + } + + // Verify test passed + if !output.status.success() { + anyhow::bail!( + "TypeScript test failed with exit code: {:?}", + output.status.code() + ); + } + + tracing::info!("TypeScript bulk move test passed! ✓"); + + harness.shutdown().await?; + Ok(()) +} diff --git a/packages/ts-client/package.json b/packages/ts-client/package.json index b1167ceea..ac05e9b93 100644 --- a/packages/ts-client/package.json +++ b/packages/ts-client/package.json @@ -45,12 +45,14 @@ } }, "devDependencies": { + "@happy-dom/global-registrator": "^15.11.0", "@tanstack/react-query": "^5.62.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@types/jest": "^29.0.0", "@types/node": "^20.0.0", "@types/react": "^19.0.0", + "happy-dom": "^15.11.0", "jest": "^29.0.0", "jest-environment-jsdom": "^29.0.0", "jsdom": "^27.2.0", diff --git a/packages/ts-client/src/client.ts b/packages/ts-client/src/client.ts index 13b98aa09..4b59048c5 100644 --- a/packages/ts-client/src/client.ts +++ b/packages/ts-client/src/client.ts @@ -1,5 +1,5 @@ import type { Transport } from "./transport"; -import { UnixSocketTransport, TauriTransport } from "./transport"; +import { UnixSocketTransport, TcpSocketTransport, TauriTransport } from "./transport"; import type { Event } from "./generated/types"; import { DEFAULT_EVENT_SUBSCRIPTION } from "./event-filter"; import { SubscriptionManager } from "./subscriptionManager"; @@ -67,6 +67,14 @@ export class SpacedriveClient extends SimpleEventEmitter { return new SpacedriveClient(new UnixSocketTransport(socketPath)); } + /** + * Create client for Bun/Node.js using TCP socket + * @param socketAddr - TCP address (e.g., "127.0.0.1:6969") + */ + static fromTcpSocket(socketAddr: string): SpacedriveClient { + return new SpacedriveClient(new TcpSocketTransport(socketAddr)); + } + /** * Create client for Tauri using IPC */ diff --git a/packages/ts-client/src/transport.ts b/packages/ts-client/src/transport.ts index cde3f0882..1afd9841b 100644 --- a/packages/ts-client/src/transport.ts +++ b/packages/ts-client/src/transport.ts @@ -86,6 +86,136 @@ export class TauriTransport implements Transport { } } +/** + * TCP socket transport for Bun/Node environments + * Connects to daemon via TCP (e.g., "127.0.0.1:6969") + */ +export class TcpSocketTransport implements Transport { + constructor(private socketAddr: string) {} + + async sendRequest(request: any): Promise { + // Parse socket address (e.g., "127.0.0.1:6969") + const [hostname, portStr] = this.socketAddr.split(":"); + const port = parseInt(portStr, 10); + + return new Promise((resolve, reject) => { + let buffer = ""; + + // @ts-ignore - Bun global + Bun.connect({ + hostname, + port, + socket: { + data(socket: any, data: any) { + buffer += new TextDecoder().decode(data); + + const newlineIndex = buffer.indexOf("\n"); + if (newlineIndex !== -1) { + const line = buffer.slice(0, newlineIndex).trim(); + socket.end(); + try { + resolve(JSON.parse(line)); + } catch (e) { + reject(e); + } + } + }, + open(socket: any) { + const requestLine = JSON.stringify(request) + "\n"; + socket.write(requestLine); + }, + error(socket: any, error: Error) { + reject(error); + }, + close(socket: any) { + if (buffer && !buffer.includes("\n")) { + reject(new Error("Connection closed without complete response")); + } + }, + }, + }); + }); + } + + async subscribe( + callback: (event: any) => void, + options?: SubscriptionOptions, + ): Promise<() => void> { + // Parse socket address + const [hostname, portStr] = this.socketAddr.split(":"); + const port = parseInt(portStr, 10); + + let socketInstance: any = null; + let buffer = ""; + + // Subscribe to relevant events + const subscribeRequest = { + Subscribe: { + event_types: options?.event_types ?? DEFAULT_EVENT_SUBSCRIPTION, + filter: options?.filter ?? null, + }, + }; + + // @ts-ignore - Bun global + socketInstance = await Bun.connect({ + hostname, + port, + socket: { + data(socket: any, data: any) { + buffer += new TextDecoder().decode(data); + + let newlineIndex: number; + while ((newlineIndex = buffer.indexOf("\n")) !== -1) { + const line = buffer.slice(0, newlineIndex).trim(); + buffer = buffer.slice(newlineIndex + 1); + + if (line) { + try { + const response = JSON.parse(line); + + // Handle DaemonResponse variants + if (response === "Subscribed" || response.Subscribed !== undefined) { + // Subscription acknowledgment, don't forward + } else if (response.Event) { + // Event message, forward to callback + callback(response.Event); + } else if (response.LogMessage) { + // Log message, forward to callback + callback(response.LogMessage); + } else { + console.warn( + "[TcpSocketTransport] Unexpected response:", + response, + ); + } + } catch (e) { + console.error("[TcpSocketTransport] Parse error:", e); + } + } + } + }, + open(socket: any) { + // Send subscription request once connected + socket.write(JSON.stringify(subscribeRequest) + "\n"); + }, + error(socket: any, error: Error) { + console.error("[TcpSocketTransport] Socket error:", error); + }, + close(socket: any) { + console.log("[TcpSocketTransport] Connection closed"); + }, + }, + }); + + // Return unsubscribe function + return () => { + if (socketInstance) { + socketInstance.end(); + } + }; + } +} + /** * Unix socket transport for Bun/Node environments * Note: This won't work in browser, use TauriTransport instead diff --git a/packages/ts-client/tests/integration/README.md b/packages/ts-client/tests/integration/README.md new file mode 100644 index 000000000..09250380d --- /dev/null +++ b/packages/ts-client/tests/integration/README.md @@ -0,0 +1,279 @@ +# TypeScript Integration Tests with Rust Bridge + +This directory contains end-to-end integration tests that bridge Rust and TypeScript, enabling real testing of TypeScript React hooks (`useNormalizedQuery`) against an actual Spacedrive daemon with indexed files. + +## Architecture + +The testing bridge works as follows: + +1. **Rust Test Harness** (`core/tests/typescript_bridge_test.rs`) + - Sets up a real Spacedrive daemon with RPC server + - Indexes a test location with files + - Writes connection config to JSON file + - Spawns `bun test` to run TypeScript tests + - Validates TypeScript test exit code + +2. **Bridge Configuration** (JSON passed via `BRIDGE_CONFIG_PATH`) + - `socket_addr`: TCP address of daemon (e.g., "127.0.0.1:41234") + - `library_id`: UUID of test library + - `location_db_id`: Database ID of indexed location + - `location_path`: Physical filesystem path to test location + - `test_data_path`: Temporary directory for test data + +3. **TypeScript Test** (e.g., `useNormalizedQuery.test.ts`) + - Reads bridge config from environment variable + - Connects to daemon via `TcpSocketTransport` + - Uses React Testing Library to test hooks + - Performs filesystem operations (move, rename) + - Asserts cache updates correctly via WebSocket events + +## Running the Tests + +### Setup (First Time Only) + +Make sure dependencies are installed: + +```bash +# From workspace root +bun install +``` + +This installs: + +- `happy-dom` + `@happy-dom/global-registrator` - Fast, lightweight DOM environment (5-10x faster than jsdom) +- `@testing-library/react` - React hook testing utilities +- `@tanstack/react-query` - Query cache management +- All other required dependencies + +### Full End-to-End Test (Rust → TypeScript) + +```bash +cd core +cargo test --package sd-core --test typescript_bridge_test -- --nocapture +``` + +This will: + +- Build Rust code +- Start daemon with test data +- Run TypeScript tests via Bun +- Display output from both Rust and TypeScript +- Fail if either side fails + +### TypeScript Tests Only (Manual) + +If you need to debug the TypeScript side independently: + +```bash +# Terminal 1: Start a daemon manually +cd core +cargo run --bin sd-daemon + +# Terminal 2: Run TypeScript tests with manual config +export BRIDGE_CONFIG_PATH=/path/to/bridge/config.json +bun test packages/ts-client/tests/integration/useNormalizedQuery.test.ts +``` + +## Test Scenarios + +### File Move Test (`useNormalizedQuery.test.ts`) + +Tests that `useNormalizedQuery` correctly updates cache when files move between folders: + +1. Query `folder_a` directory listing +2. Query `folder_b` directory listing +3. Move `file1.txt` from `folder_a` to `folder_b` (filesystem operation) +4. Wait for watcher to detect change +5. Assert `file1` removed from `folder_a` cache +6. Assert `file1` added to `folder_b` cache with same UUID (move detection) + +### Folder Rename Test (`useNormalizedQuery.folder-rename.test.ts`) + +Tests that `useNormalizedQuery` correctly updates cache when folders are renamed: + +1. Query root directory listing (contains `original_folder`) +2. Rename `original_folder` to `renamed_folder` (filesystem operation) +3. Wait for watcher to detect change +4. Assert `original_folder` removed from cache +5. Assert `renamed_folder` appears in cache with same UUID (identity preserved) + +## Test Environment Setup + +The integration tests use a DOM environment provided by **Happy DOM** (not jsdom) to support React Testing Library. Happy DOM is: + +- **5-10x faster** than jsdom for React hook tests +- **Lighter weight** - optimized specifically for testing +- **Easier to configure** - one-liner setup with automatic global registration + +### How It Works + +- **`setup.ts`** - Imports `@happy-dom/global-registrator` and registers DOM globals + ```typescript + import { GlobalRegistrator } from "@happy-dom/global-registrator"; + GlobalRegistrator.register(); + ``` +- **Test files** - Import `./setup` as the first line to initialize DOM before React imports +- **Cleanup** - Each test calls `cleanup()` from `@testing-library/react` after tests + +This provides `document`, `window`, `HTMLElement`, and all other browser globals needed by React hooks. + +## How It Works + +### Daemon Setup (Rust Side) + +The `IndexingHarnessBuilder` has been extended with `.enable_daemon()`: + +```rust +let harness = IndexingHarnessBuilder::new("test_name") + .enable_daemon() // Starts RPC server on random port + .build() + .await?; + +let socket_addr = harness.daemon_socket_addr().unwrap(); +// socket_addr = "127.0.0.1:41234" (random available port) +``` + +The daemon runs in a background task, sharing the same `Core` instance with the test harness. + +### TypeScript Connection + +```typescript +import { SpacedriveClient } from "@sd/ts-client"; + +// Read config from Rust bridge +const bridgeConfig = JSON.parse(await readFile(process.env.BRIDGE_CONFIG_PATH)); + +// Connect via TCP +const client = SpacedriveClient.fromTcpSocket(bridgeConfig.socket_addr); + +// Set library context +await client.setLibrary(bridgeConfig.library_id); + +// Now use hooks normally! +const { data } = useNormalizedQuery({ + wireMethod: "query:files.directory_listing", + input: { path: { Physical: { path: "/some/path" } } }, + resourceType: "file", + // ... +}); +``` + +### Event Flow + +``` +Filesystem Change (rename/move) + ↓ +Watcher Detection (Rust) + ↓ +Indexing Update (Database) + ↓ +ResourceChanged Event (WebSocket) + ↓ +SubscriptionManager Filters (TypeScript) + ↓ +useNormalizedQuery Event Handler + ↓ +Cache Update (TanStack Query) + ↓ +React Re-render (Hooks) +``` + +## Benefits + +1. **True End-to-End Testing**: Tests the entire stack from filesystem to React hooks +2. **Real Watcher Integration**: Tests actual file system watcher behavior, not mocks +3. **Cross-Language Validation**: Ensures Rust and TypeScript stay in sync +4. **Regression Detection**: Catches breaking changes in event emission, caching, or filtering +5. **Documentation by Example**: Tests serve as living examples of how the system works + +## Adding New Tests + +1. Create a new test in `typescript_bridge_test.rs`: + +```rust +#[tokio::test] +async fn test_typescript_my_new_feature() -> anyhow::Result<()> { + let harness = IndexingHarnessBuilder::new("my_test") + .enable_daemon() + .build() + .await?; + + // Set up test data + let test_location = harness.create_test_location("test").await?; + test_location.write_file("test.txt", "content").await?; + + let location = test_location.index("Test", IndexMode::Shallow).await?; + + // Write bridge config (see other tests for example) + let bridge_config = TestBridgeConfig { /* ... */ }; + // ... + + // Spawn TypeScript test + let output = tokio::process::Command::new("bun") + .arg("test") + .arg("packages/ts-client/tests/integration/my-feature.test.ts") + .env("BRIDGE_CONFIG_PATH", config_path.to_str().unwrap()) + .output() + .await?; + + assert!(output.status.success()); + Ok(()) +} +``` + +2. Create corresponding TypeScript test: + +```typescript +import { SpacedriveClient } from "@sd/ts-client"; +import { renderHook } from "@testing-library/react"; + +test("my feature works", async () => { + const bridgeConfig = JSON.parse(/* read from env */); + const client = SpacedriveClient.fromTcpSocket(bridgeConfig.socket_addr); + + // Test your feature! + const { result } = renderHook(() => useMyFeature(...)); + // ... +}); +``` + +## Debugging + +### Enable Debug Logging + +```bash +# Rust side +RUST_LOG=debug cargo test typescript_bridge -- --nocapture + +# TypeScript side (in test file) +const query = useNormalizedQuery({ + // ... + debug: true, // Enables console.log for event processing +}); +``` + +### Common Issues + +**"Connection refused"**: Daemon didn't start or took too long. Increase sleep duration in Rust test. + +**"No events received"**: Watcher may be disabled or buffering. Check: + +- Watcher is enabled in harness (default) +- Wait time is sufficient (8+ seconds for folder renames due to buffering) +- Path is correct and indexing completed + +**"Cache not updating"**: Check: + +- Event subscription filter matches query scope +- Resource type matches ("file", "location", etc.) +- pathScope is set correctly for file queries + +## Future Enhancements + +- [ ] Add tests for batch operations +- [ ] Test error handling and retry logic +- [ ] Test network interruption scenarios +- [ ] Add performance benchmarks +- [ ] Test concurrent operations +- [ ] Add tests for content identification events +- [ ] Test tag/label updates diff --git a/packages/ts-client/tests/integration/bunfig.toml b/packages/ts-client/tests/integration/bunfig.toml new file mode 100644 index 000000000..7713fe925 --- /dev/null +++ b/packages/ts-client/tests/integration/bunfig.toml @@ -0,0 +1,4 @@ +# Bun test configuration for integration tests +# Note: setup.ts is imported directly in test files rather than preloaded +[test] + diff --git a/packages/ts-client/tests/integration/setup.ts b/packages/ts-client/tests/integration/setup.ts new file mode 100644 index 000000000..efb163779 --- /dev/null +++ b/packages/ts-client/tests/integration/setup.ts @@ -0,0 +1,11 @@ +/** + * Test setup for Bun integration tests + * Provides a DOM environment for React Testing Library using Happy DOM + * + * Happy DOM is faster and lighter than jsdom, optimized for testing. + * This one-liner registers all DOM globals automatically. + */ + +import { GlobalRegistrator } from "@happy-dom/global-registrator"; + +GlobalRegistrator.register(); diff --git a/packages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts b/packages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts new file mode 100644 index 000000000..a21e37d05 --- /dev/null +++ b/packages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts @@ -0,0 +1,239 @@ +/** + * TypeScript Integration Test: useNormalizedQuery with Folder Renames + * + * This test is spawned by a Rust test harness that provides: + * - Real Spacedrive daemon running on Unix socket + * - Indexed location with test folders + * - Connection configuration via BRIDGE_CONFIG_PATH env var + * + * Test flow: + * 1. Connect to daemon using bridge config + * 2. Query directory listing with useNormalizedQuery + * 3. Rename folder in filesystem + * 4. Verify cache updates correctly and children remain accessible + */ + +// Setup DOM environment before any other imports +import "./setup"; + +import { + describe, + test, + expect, + beforeAll, + afterAll, + afterEach, +} from "bun:test"; +import { readFile } from "fs/promises"; +import { rename } from "fs/promises"; +import { join } from "path"; +import { hostname } from "os"; +import { SpacedriveClient } from "../../src/client"; +import { renderHook, waitFor, cleanup } from "@testing-library/react"; +import { SpacedriveProvider } from "../../src/hooks/useClient"; +import { useNormalizedQuery } from "../../src/hooks/useNormalizedQuery"; +import React from "react"; + +// Bridge configuration from Rust test harness +interface BridgeConfig { + socket_addr: string; + library_id: string; + location_db_id: number; + location_path: string; + test_data_path: string; +} + +let bridgeConfig: BridgeConfig; +let client: SpacedriveClient; +const allEventsReceived: any[] = []; // Collect all events for debugging + +beforeAll(async () => { + // Read bridge config from path provided by Rust test + const configPath = process.env.BRIDGE_CONFIG_PATH; + if (!configPath) { + throw new Error("BRIDGE_CONFIG_PATH environment variable not set"); + } + + console.log(`[TS] Reading bridge config from: ${configPath}`); + const configJson = await readFile(configPath, "utf-8"); + bridgeConfig = JSON.parse(configJson); + + console.log(`[TS] Bridge config:`, bridgeConfig); + + // Connect to daemon via TCP socket + client = SpacedriveClient.fromTcpSocket(bridgeConfig.socket_addr); + + console.log(`[TS] Connected to daemon`); + + // Set library context + client.setCurrentLibrary(bridgeConfig.library_id); + console.log(`[TS] Library set to: ${bridgeConfig.library_id}`); + + // Hook into the subscription manager to collect all events + const originalCreateSubscription = (client as any).subscriptionManager + .createSubscription; + (client as any).subscriptionManager.createSubscription = function ( + filter: any, + callback: any, + ) { + const wrappedCallback = (event: any) => { + allEventsReceived.push({ + timestamp: new Date().toISOString(), + filter, + event, + }); + console.log( + `[TS] 🔔 Event received:`, + JSON.stringify(event, null, 2), + ); + callback(event); + }; + return originalCreateSubscription.call(this, filter, wrappedCallback); + }; +}); + +afterAll(async () => { + // Log all events at the end for debugging + console.log( + `[TS] ===== ALL EVENTS RECEIVED (${allEventsReceived.length}) =====`, + ); + allEventsReceived.forEach((item, idx) => { + console.log(`[TS] Event ${idx + 1} at ${item.timestamp}:`); + console.log(`[TS] Filter:`, JSON.stringify(item.filter, null, 2)); + console.log(`[TS] Event:`, JSON.stringify(item.event, null, 2)); + }); + console.log(`[TS] ===== END OF EVENTS =====`); + // No explicit disconnect needed for stateless transports +}); + +afterEach(() => { + // Clean up React components after each test + cleanup(); +}); + +describe("useNormalizedQuery - Folder Rename Integration", () => { + test("should update cache when folder is renamed", async () => { + const locationPath = bridgeConfig.location_path; + const originalPath = join(locationPath, "original_folder"); + const renamedPath = join(locationPath, "renamed_folder"); + + // Get device slug from hostname + const deviceSlug = hostname().toLowerCase().replace(/\s+/g, "-"); + + // Create wrapper for React hooks with SpacedriveProvider + const wrapper = ({ children }: { children: React.ReactNode }) => + React.createElement(SpacedriveProvider, { client }, children); + + // Query root directory listing (should contain original_folder) + const { result: rootResult } = renderHook( + () => + useNormalizedQuery({ + wireMethod: "query:files.directory_listing", + input: { + path: { + Physical: { + device_slug: deviceSlug, + path: locationPath, + }, + }, + sort_by: "name", + }, + resourceType: "file", + pathScope: { + Physical: { + device_slug: deviceSlug, + path: locationPath, + }, + }, + includeDescendants: false, + debug: true, + }), + { wrapper }, + ); + + // Query original_folder contents + const { result: folderResult } = renderHook( + () => + useNormalizedQuery({ + wireMethod: "query:files.directory_listing", + input: { + path: { + Physical: { + device_slug: deviceSlug, + path: originalPath, + }, + }, + sort_by: "name", + }, + resourceType: "file", + pathScope: { + Physical: { + device_slug: deviceSlug, + path: originalPath, + }, + }, + includeDescendants: false, + debug: true, + }), + { wrapper }, + ); + + // Wait for initial data + await waitFor(() => { + expect(rootResult.current.data).toBeDefined(); + expect(folderResult.current.data).toBeDefined(); + }); + + console.log("[TS] Initial root listing:", rootResult.current.data); + console.log( + "[TS] Initial original_folder contents:", + folderResult.current.data, + ); + + // Verify initial state + const initialRootData = rootResult.current.data as { files: any[] }; + const originalFolder = initialRootData.files.find( + (f: any) => f.name === "original_folder" && f.kind === "Directory", + ); + expect(originalFolder).toBeDefined(); + + const originalFolderUuid = originalFolder.uuid; + const originalFolderChildrenData = folderResult.current.data as { + files: any[]; + }; + expect(originalFolderChildrenData.files.length).toBeGreaterThanOrEqual( + 2, + ); // file1.txt, file2.rs + + // Rename the folder + console.log("[TS] Renaming folder: original_folder -> renamed_folder"); + await rename(originalPath, renamedPath); + + // Wait for watcher to detect and emit events + await new Promise((resolve) => setTimeout(resolve, 8000)); + + // Verify cache updated correctly + const finalRootData = rootResult.current.data as { files: any[] }; + console.log("[TS] Final root listing:", finalRootData); + + // Original folder should no longer exist in root + const originalStillExists = finalRootData.files.find( + (f: any) => f.name === "original_folder" && f.kind === "Directory", + ); + expect(originalStillExists).toBeUndefined(); + + // Renamed folder should exist in root + const renamedFolder = finalRootData.files.find( + (f: any) => f.name === "renamed_folder" && f.kind === "Directory", + ); + expect(renamedFolder).toBeDefined(); + + // UUID should be preserved (folder identity maintained) + expect(renamedFolder.uuid).toBe(originalFolderUuid); + + console.log( + "[TS] ✓ Folder rename detected and cache updated correctly", + ); + console.log("[TS] ✓ Folder UUID preserved:", renamedFolder.uuid); + }, 30000); // 30s timeout for watcher delays +}); diff --git a/packages/ts-client/tests/integration/useNormalizedQuery.test.ts b/packages/ts-client/tests/integration/useNormalizedQuery.test.ts new file mode 100644 index 000000000..5bca775ac --- /dev/null +++ b/packages/ts-client/tests/integration/useNormalizedQuery.test.ts @@ -0,0 +1,254 @@ +/** + * TypeScript Integration Test: useNormalizedQuery with File Moves + * + * This test is spawned by a Rust test harness that provides: + * - Real Spacedrive daemon running on Unix socket + * - Indexed location with test files + * - Connection configuration via BRIDGE_CONFIG_PATH env var + * + * Test flow: + * 1. Connect to daemon using bridge config + * 2. Query directory listing with useNormalizedQuery + * 3. Move files in filesystem + * 4. Verify cache updates correctly via WebSocket events + */ + +// Setup DOM environment before any other imports +import "./setup"; + +import { + describe, + test, + expect, + beforeAll, + afterAll, + afterEach, +} from "bun:test"; +import { readFile } from "fs/promises"; +import { rename } from "fs/promises"; +import { join } from "path"; +import { hostname } from "os"; +import { SpacedriveClient } from "../../src/client"; +import { renderHook, waitFor, cleanup } from "@testing-library/react"; +import { SpacedriveProvider } from "../../src/hooks/useClient"; +import { useNormalizedQuery } from "../../src/hooks/useNormalizedQuery"; +import React from "react"; + +// Bridge configuration from Rust test harness +interface BridgeConfig { + socket_addr: string; + library_id: string; + location_db_id: number; + location_path: string; + test_data_path: string; +} + +let bridgeConfig: BridgeConfig; +let client: SpacedriveClient; +const allEventsReceived: any[] = []; // Collect all events for debugging + +beforeAll(async () => { + // Read bridge config from path provided by Rust test + const configPath = process.env.BRIDGE_CONFIG_PATH; + if (!configPath) { + throw new Error("BRIDGE_CONFIG_PATH environment variable not set"); + } + + console.log(`[TS] Reading bridge config from: ${configPath}`); + const configJson = await readFile(configPath, "utf-8"); + bridgeConfig = JSON.parse(configJson); + + console.log(`[TS] Bridge config:`, bridgeConfig); + + // Connect to daemon via TCP socket + client = SpacedriveClient.fromTcpSocket(bridgeConfig.socket_addr); + + console.log(`[TS] Connected to daemon`); + + // Set library context + client.setCurrentLibrary(bridgeConfig.library_id); + console.log(`[TS] Library set to: ${bridgeConfig.library_id}`); + + // Hook into the subscription manager to collect all events + const originalCreateSubscription = (client as any).subscriptionManager + .createSubscription; + (client as any).subscriptionManager.createSubscription = function ( + filter: any, + callback: any, + ) { + const wrappedCallback = (event: any) => { + allEventsReceived.push({ + timestamp: new Date().toISOString(), + filter, + event, + }); + console.log( + `[TS] 🔔 Event received:`, + JSON.stringify(event, null, 2), + ); + callback(event); + }; + return originalCreateSubscription.call(this, filter, wrappedCallback); + }; +}); + +afterAll(async () => { + // Log all events at the end for debugging + console.log( + `[TS] ===== ALL EVENTS RECEIVED (${allEventsReceived.length}) =====`, + ); + allEventsReceived.forEach((item, idx) => { + console.log(`[TS] Event ${idx + 1} at ${item.timestamp}:`); + console.log(`[TS] Filter:`, JSON.stringify(item.filter, null, 2)); + console.log(`[TS] Event:`, JSON.stringify(item.event, null, 2)); + }); + console.log(`[TS] ===== END OF EVENTS =====`); + // No explicit disconnect needed for stateless transports +}); + +afterEach(() => { + // Clean up React components after each test + cleanup(); +}); + +describe("useNormalizedQuery - File Moves Integration", () => { + test("should update cache when file moves between folders", async () => { + const folderAPath = join(bridgeConfig.location_path, "folder_a"); + const folderBPath = join(bridgeConfig.location_path, "folder_b"); + + // Get device slug from hostname + const deviceSlug = hostname().toLowerCase().replace(/\s+/g, "-"); + + // Create wrapper for React hooks with SpacedriveProvider + const wrapper = ({ children }: { children: React.ReactNode }) => + React.createElement(SpacedriveProvider, { client }, children); + + // Query folder_a listing + const { result: folderAResult } = renderHook( + () => + useNormalizedQuery({ + wireMethod: "query:files.directory_listing", + input: { + path: { + Physical: { + device_slug: deviceSlug, + path: folderAPath, + }, + }, + sort_by: "name", + }, + resourceType: "file", + pathScope: { + Physical: { + device_slug: deviceSlug, + path: folderAPath, + }, + }, + includeDescendants: false, + debug: true, + }), + { wrapper }, + ); + + // Query folder_b listing + const { result: folderBResult } = renderHook( + () => + useNormalizedQuery({ + wireMethod: "query:files.directory_listing", + input: { + path: { + Physical: { + device_slug: deviceSlug, + path: folderBPath, + }, + }, + sort_by: "name", + }, + resourceType: "file", + pathScope: { + Physical: { + device_slug: deviceSlug, + path: folderBPath, + }, + }, + includeDescendants: false, + debug: true, + }), + { wrapper }, + ); + + // Wait for initial data + await waitFor( + () => { + console.log("[TS] Waiting for data...", { + folderA: { + data: folderAResult.current.data, + error: folderAResult.current.error, + isLoading: folderAResult.current.isLoading, + }, + folderB: { + data: folderBResult.current.data, + error: folderBResult.current.error, + isLoading: folderBResult.current.isLoading, + }, + }); + expect(folderAResult.current.data).toBeDefined(); + expect(folderBResult.current.data).toBeDefined(); + }, + { timeout: 5000 }, + ); + + console.log("[TS] Initial folder_a files:", folderAResult.current.data); + console.log("[TS] Initial folder_b files:", folderBResult.current.data); + + // Verify initial state + const initialFolderAData = folderAResult.current.data as { + files: any[]; + }; + const initialFolderBData = folderBResult.current.data as { + files: any[]; + }; + + expect(initialFolderAData.files.length).toBeGreaterThanOrEqual(2); // file1.txt, file2.rs + expect(initialFolderBData.files.length).toBeGreaterThanOrEqual(1); // file3.md + + const file1Before = initialFolderAData.files.find( + (f: any) => f.name === "file1", + ); + expect(file1Before).toBeDefined(); + + // Move file1.txt from folder_a to folder_b + console.log("[TS] Moving file1.txt from folder_a to folder_b"); + await rename( + join(folderAPath, "file1.txt"), + join(folderBPath, "file1.txt"), + ); + + // Wait for watcher to detect and emit events (watcher buffers for 500ms + tick time) + await new Promise((resolve) => setTimeout(resolve, 8000)); + + // Verify cache updated correctly + const finalFolderAData = folderAResult.current.data as { files: any[] }; + const finalFolderBData = folderBResult.current.data as { files: any[] }; + + console.log("[TS] Final folder_a files:", finalFolderAData); + console.log("[TS] Final folder_b files:", finalFolderBData); + + // file1 should no longer be in folder_a + const file1InFolderA = finalFolderAData.files.find( + (f: any) => f.name === "file1", + ); + expect(file1InFolderA).toBeUndefined(); + + // file1 should now be in folder_b + const file1InFolderB = finalFolderBData.files.find( + (f: any) => f.name === "file1", + ); + expect(file1InFolderB).toBeDefined(); + + // UUID should be preserved (move detection) + expect(file1InFolderB.uuid).toBe(file1Before.uuid); + + console.log("[TS] ✓ File move detected and cache updated correctly"); + }, 30000); // 30s timeout for watcher delays +});