From 369a39288c1d54e2af992df7c70f15c045d90130 Mon Sep 17 00:00:00 2001 From: Aqil Aziz Date: Thu, 21 May 2026 07:07:59 +0700 Subject: [PATCH] test(e2e): wipe memory tree during test reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Extends `openhuman.test_reset` so E2E resets also wipe Memory Tree state via the existing `memory_tree_wipe_all` path. - Adds reset summary fields for memory-tree rows, content directories, and Composio sync-state rows so Appium logs show exactly what was cleared. - Adds a focused async unit test covering memory-tree content directory cleanup through the new reset helper. ## Problem - #1862 tracks that `openhuman.test_reset` only cleared auth/onboarding/cron state, while Memory Tree data could survive between specs in a shared Appium session. - That means memory-oriented specs can pass or fail based on chunks, wiki content, or sync cursors left by an earlier spec. ## Solution - Calls `read_rpc::wipe_all_rpc(&config)` from `test_support::rpc::reset` after cron cleanup and before config/auth clearing. - Surfaces `memory_tree_rows_deleted`, `memory_tree_dirs_removed`, and `memory_tree_sync_state_cleared` in `ResetSummary`, `reset_json`, and the controller schema. - Keeps this as a scoped #1862 slice; other domains listed in the issue can land as separate hook PRs. ## Submission Checklist - [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — focused unit test covers Memory Tree content-dir cleanup; existing `wipe_all_rpc` owns table/sync-state failure behavior. - [x] **Diff coverage >= 80%** — new Rust test covers the new helper path; CI coverage gate is authoritative. - [x] Coverage matrix updated — N/A: E2E test-support reset plumbing, no product feature row added/removed. - [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — N/A: no feature matrix row. - [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy)) - [x] Manual smoke checklist updated if this touches release-cut surfaces ([`docs/RELEASE-MANUAL-SMOKE.md`](../docs/RELEASE-MANUAL-SMOKE.md)) — N/A: test-support RPC only. - [x] Linked issue closed via `Closes #NNN` in the `## Related` section — N/A: scoped slice; references #1862 without closing the umbrella. ## Impact - E2E specs that call `resetApp(...)` now start without prior Memory Tree chunks, summary/wiki files, or sync cursors. - User runtime behavior is unchanged unless the E2E-only `openhuman.test_reset` controller is compiled/enabled. ## Related - Refs #1862 - Follow-up PR(s)/TODOs: add hook coverage for remaining #1862 domains: channels, skills, webview_accounts, threads, notifications, webhooks, cost, referral, composio. --- ## AI Authored PR Metadata (required for Codex/Linear PRs) ### Linear Issue - Key: N/A - URL: N/A ### Commit & Branch - Branch: `codex/1862-test-reset-memory-tree` - Commit SHA: `48630b40f69400d6a3c5e055e80c25486e3bba6d` ### Validation Run - [x] `pnpm --filter openhuman-app format:check` — N/A: no frontend files changed. - [x] `pnpm typecheck` — N/A: no TypeScript files changed. - [x] Focused tests: attempted `cargo test -p openhuman test_support::rpc::tests::wipe_memory_tree_removes_content_dirs_and_reports_summary --lib`. - [x] Rust fmt/check (if changed): `cargo fmt --all --check`; `git diff --check`. - [x] Tauri fmt/check (if changed): N/A: no Tauri shell files changed. ### Validation Blocked - `command:` `cargo test -p openhuman test_support::rpc::tests::wipe_memory_tree_removes_content_dirs_and_reports_summary --lib` - `error:` local Windows build fails before tests in `whisper-rs-sys` because `clang.dll` / `libclang.dll` is missing and `LIBCLANG_PATH` is unset. - `impact:` focused test did not execute locally; CI Linux/Windows runners with libclang are expected to compile and run it. ### Behavior Changes - Intended behavior change: E2E-only test reset now wipes Memory Tree state in addition to cron/auth/onboarding state. - User-visible effect: none in normal builds; E2E logs show memory-tree wipe counts. ### Parity Contract - Legacy behavior preserved: cron cleanup, auth clearing, onboarding reset, and active-user removal still run and still short-circuit on failure. - Guard/fallback/dispatch parity checks: Memory Tree wipe reuses the existing user-facing `wipe_all_rpc` implementation instead of adding a second deletion path. ### Duplicate / Superseded PR Handling - Duplicate PR(s): N/A - Canonical PR: this PR - Resolution (closed/superseded/updated): N/A ## Summary by CodeRabbit * **New Features** * Reset now clears memory-tree persistent data during fresh-install resets and reports rows deleted, directories removed, and sync-state entries cleared. * **Documentation** * Updated reset operation schema and outputs to include memory-tree cleanup fields. * **Tests** * Added a unit test verifying memory-tree wipe removes content directories and reports summary metrics. * **Bug Fixes** * Increased core startup readiness timeout to reduce startup failures. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2308?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) Co-authored-by: aqilaziz Co-authored-by: Steven Enamakel --- app/src-tauri/src/core_process.rs | 15 +++--- src/openhuman/test_support/rpc.rs | 72 ++++++++++++++++++++++++++- src/openhuman/test_support/schemas.rs | 20 +++++++- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/app/src-tauri/src/core_process.rs b/app/src-tauri/src/core_process.rs index 5b31be033..b3ca79086 100644 --- a/app/src-tauri/src/core_process.rs +++ b/app/src-tauri/src/core_process.rs @@ -276,14 +276,17 @@ impl CoreProcessHandle { } } - // Readiness budget: 100 iterations × 100ms = 10s. The embedded + // Readiness budget: 200 iterations × 100ms = 20s. The embedded // core's JSON-RPC controller registry has grown over time and - // the previous 4s budget started flaking under CI worker load + // earlier 4s/10s budgets started flaking under CI worker load // (issue: core_process tests intermittently failing with - // "core process did not become ready"). 10s is still well - // under any user-visible startup expectation and matches the - // upper end of observed cold-start times. - for _ in 0..100 { + // "core process did not become ready"), especially under + // cargo-llvm-cov instrumentation where the binary runs ~2x + // slower. 20s is still well under any user-visible startup + // expectation: in normal runs the ready signal arrives in well + // under 1s and the loop exits immediately; the headroom only + // matters on heavily loaded instrumented CI workers. + for _ in 0..200 { if !received_ready { match ready_rx.try_recv() { Ok(ready_signal) => { diff --git a/src/openhuman/test_support/rpc.rs b/src/openhuman/test_support/rpc.rs index 959f304d7..12e5db476 100644 --- a/src/openhuman/test_support/rpc.rs +++ b/src/openhuman/test_support/rpc.rs @@ -4,6 +4,7 @@ //! - no authenticated user (active_user.toml removed, api_key cleared) //! - onboarding not yet completed (chat_onboarding_completed=false) //! - no cron jobs (so the post-onboarding seed re-creates `morning_briefing`) +//! - no memory-tree chunks, summaries, content dirs, or sync cursors //! //! It is intentionally in-process: the sidecar keeps running. Specs reload //! the webview after this call so the renderer also starts from a blank slate. @@ -14,6 +15,7 @@ use serde_json::json; use crate::openhuman::config::Config; use crate::openhuman::config::{clear_active_user, default_root_openhuman_dir}; use crate::openhuman::cron; +use crate::openhuman::memory::tree::read_rpc; use crate::rpc::RpcOutcome; const E2E_MODE_ENV_VAR: &str = "OPENHUMAN_E2E_MODE"; @@ -22,11 +24,21 @@ const E2E_MODE_ENV_VAR: &str = "OPENHUMAN_E2E_MODE"; #[derive(Debug, Serialize)] pub struct ResetSummary { pub cron_jobs_removed: usize, + pub memory_tree_rows_deleted: u64, + pub memory_tree_dirs_removed: Vec, + pub memory_tree_sync_state_cleared: u64, pub onboarding_was_completed: bool, pub api_key_was_set: bool, pub active_user_cleared: bool, } +#[derive(Debug, Serialize)] +struct MemoryTreeResetSummary { + rows_deleted: u64, + dirs_removed: Vec, + sync_state_cleared: u64, +} + fn ensure_e2e_mode_enabled() -> Result<(), String> { ensure_e2e_mode_value(std::env::var(E2E_MODE_ENV_VAR).ok().as_deref()) } @@ -69,6 +81,15 @@ pub async fn reset() -> Result, String> { .map_err(|e| format!("test_reset: cron wipe failed: {e:#}"))?; log::debug!("[test_reset] step=wipe_cron ok removed={cron_jobs_removed}"); + log::debug!("[test_reset] step=wipe_memory_tree start"); + let memory_tree = wipe_memory_tree(&config).await?; + log::debug!( + "[test_reset] step=wipe_memory_tree ok rows={} dirs={:?} sync_state={}", + memory_tree.rows_deleted, + memory_tree.dirs_removed, + memory_tree.sync_state_cleared + ); + log::debug!("[test_reset] step=clear_config_fields start"); config.chat_onboarding_completed = false; config.api_key = None; @@ -88,8 +109,16 @@ pub async fn reset() -> Result, String> { root.display() ); + let memory_tree_log = format!( + "memory_tree wiped rows={} dirs={:?} sync_state={}", + memory_tree.rows_deleted, memory_tree.dirs_removed, memory_tree.sync_state_cleared + ); + let summary = ResetSummary { cron_jobs_removed, + memory_tree_rows_deleted: memory_tree.rows_deleted, + memory_tree_dirs_removed: memory_tree.dirs_removed, + memory_tree_sync_state_cleared: memory_tree.sync_state_cleared, onboarding_was_completed, api_key_was_set, active_user_cleared: true, @@ -104,6 +133,7 @@ pub async fn reset() -> Result, String> { summary, vec![ format!("removed {cron_jobs_removed} cron jobs"), + memory_tree_log, format!("chat_onboarding_completed: {onboarding_was_completed} → false"), format!("api_key cleared (was set: {api_key_was_set})"), "active_user.toml removed".to_string(), @@ -111,12 +141,27 @@ pub async fn reset() -> Result, String> { )) } +async fn wipe_memory_tree(config: &Config) -> Result { + let outcome = read_rpc::wipe_all_rpc(config) + .await + .map_err(|e| format!("test_reset: memory_tree wipe failed: {e}"))?; + let value = outcome.value; + Ok(MemoryTreeResetSummary { + rows_deleted: value.rows_deleted, + dirs_removed: value.dirs_removed, + sync_state_cleared: value.sync_state_cleared, + }) +} + /// Convenience helper for handlers that prefer a raw JSON envelope. #[allow(dead_code)] pub async fn reset_json() -> Result { let outcome = reset().await?; Ok(json!({ "removed_cron_jobs": outcome.value.cron_jobs_removed, + "memory_tree_rows_deleted": outcome.value.memory_tree_rows_deleted, + "memory_tree_dirs_removed": outcome.value.memory_tree_dirs_removed, + "memory_tree_sync_state_cleared": outcome.value.memory_tree_sync_state_cleared, "previously_onboarded": outcome.value.onboarding_was_completed, "previously_authenticated": outcome.value.api_key_was_set, })) @@ -124,8 +169,9 @@ pub async fn reset_json() -> Result { #[cfg(test)] mod tests { - use super::{ensure_e2e_mode_value, reset, E2E_MODE_ENV_VAR}; + use super::*; use std::sync::{Mutex, OnceLock}; + use tempfile::TempDir; static E2E_MODE_ENV_LOCK: OnceLock> = OnceLock::new(); @@ -163,4 +209,28 @@ mod tests { ensure_e2e_mode_value(Some("true")).expect("true enables E2E mode"); ensure_e2e_mode_value(Some("yes")).expect("yes enables E2E mode"); } + + #[tokio::test] + async fn wipe_memory_tree_removes_content_dirs_and_reports_summary() { + let tmp = TempDir::new().unwrap(); + let mut config = Config::default(); + config.workspace_dir = tmp.path().join("workspace"); + + let content_root = config.memory_tree_content_root(); + let raw_dir = content_root.join("raw"); + let wiki_dir = content_root.join("wiki"); + std::fs::create_dir_all(&raw_dir).unwrap(); + std::fs::create_dir_all(&wiki_dir).unwrap(); + std::fs::write(raw_dir.join("chunk.md"), "test chunk").unwrap(); + std::fs::write(wiki_dir.join("summary.md"), "test summary").unwrap(); + + let summary = wipe_memory_tree(&config).await.unwrap(); + + assert_eq!(summary.rows_deleted, 0); + assert_eq!(summary.sync_state_cleared, 0); + assert!(summary.dirs_removed.contains(&"raw".to_string())); + assert!(summary.dirs_removed.contains(&"wiki".to_string())); + assert!(!raw_dir.exists()); + assert!(!wiki_dir.exists()); + } } diff --git a/src/openhuman/test_support/schemas.rs b/src/openhuman/test_support/schemas.rs index a210e2216..c429aded7 100644 --- a/src/openhuman/test_support/schemas.rs +++ b/src/openhuman/test_support/schemas.rs @@ -51,7 +51,7 @@ pub fn schemas(function: &str) -> ControllerSchema { namespace: "test", function: "reset", description: - "Wipe persistent sidecar state in-place: clears auth, onboarding, and cron jobs. \ + "Wipe persistent sidecar state in-place: clears auth, onboarding, cron jobs, and memory tree. \ E2E specs call this between tests so each starts from a fresh-install baseline.", inputs: vec![], outputs: vec![FieldSchema { @@ -64,6 +64,24 @@ pub fn schemas(function: &str) -> ControllerSchema { comment: "Number of cron jobs deleted from the workspace database.", required: true, }, + FieldSchema { + name: "memory_tree_rows_deleted", + ty: TypeSchema::U64, + comment: "Number of memory-tree SQLite rows deleted.", + required: true, + }, + FieldSchema { + name: "memory_tree_dirs_removed", + ty: TypeSchema::Array(Box::new(TypeSchema::String)), + comment: "Memory-tree content directories removed.", + required: true, + }, + FieldSchema { + name: "memory_tree_sync_state_cleared", + ty: TypeSchema::U64, + comment: "Number of Composio memory-tree sync-state rows deleted.", + required: true, + }, FieldSchema { name: "onboarding_was_completed", ty: TypeSchema::Bool,