use galaxy_core::user_preferences::GetUserPreferences as _; use galaxyui::{App, SingletonEntity}; use super::{has_completed_local_onboarding, RootView, HAS_COMPLETED_ONBOARDING_KEY}; use crate::auth::auth_manager::AuthManager; use crate::auth::AuthStateProvider; use crate::server::server_api::ServerApiProvider; fn initialize_app(app: &mut App) { app.update(crate::settings::init_and_register_user_preferences); app.add_singleton_model(|_ctx| ServerApiProvider::new_for_test()); app.add_singleton_model(|_| AuthStateProvider::new_for_test()); app.add_singleton_model(AuthManager::new_for_test); } fn set_local_onboarding_completed(app: &mut App, completed: bool) { app.update(|ctx| { ctx.private_user_preferences() .write_value( HAS_COMPLETED_ONBOARDING_KEY, serde_json::to_string(&completed).unwrap(), ) .unwrap(); }); } /// Regression test for the bug fixed by introducing /// `RootView::sync_local_onboarding_to_server`: when a user completed onboarding /// pre-login and later authenticated via a non-login-slide entrypoint (i.e. while /// already in `Terminal` state), the server-side `is_onboarded` flag was never /// flipped. The helper runs unconditionally on `AuthComplete` and must flip the /// flag when all preconditions hold. #[test] fn test_sync_flips_server_is_onboarded_when_local_onboarding_completed() { App::test((), |mut app| async move { initialize_app(&mut app); // Seed the "has_completed_local_onboarding" preference and make the user // appear not yet onboarded on the server. The default test user is // non-anonymous, so the guards in the helper won't short-circuit. set_local_onboarding_completed(&mut app, true); app.update(|ctx| { AuthStateProvider::as_ref(ctx).get().set_is_onboarded(false); assert!(has_completed_local_onboarding(ctx)); assert_eq!( AuthStateProvider::as_ref(ctx).get().is_onboarded(), Some(false) ); }); app.update(|ctx| { let auth_state = AuthStateProvider::as_ref(ctx).get().clone(); RootView::sync_local_onboarding_to_server(&auth_state, ctx); }); app.read(|ctx| { assert_eq!( AuthStateProvider::as_ref(ctx).get().is_onboarded(), Some(true), "sync should have invoked AuthManager::set_user_onboarded" ); }); }); } /// If the user hasn't completed local onboarding, the helper must leave the /// server-side flag untouched — onboarding hasn't actually happened yet. #[test] fn test_sync_noop_when_local_onboarding_not_completed() { App::test((), |mut app| async move { initialize_app(&mut app); // Do not set HAS_COMPLETED_ONBOARDING_KEY; it defaults to false. app.update(|ctx| { AuthStateProvider::as_ref(ctx).get().set_is_onboarded(false); }); app.update(|ctx| { let auth_state = AuthStateProvider::as_ref(ctx).get().clone(); RootView::sync_local_onboarding_to_server(&auth_state, ctx); }); app.read(|ctx| { assert_eq!( AuthStateProvider::as_ref(ctx).get().is_onboarded(), Some(false), "sync should not have changed is_onboarded when local onboarding is incomplete" ); }); }); } /// The server-side flag should also be left untouched when it is already set, /// even if local onboarding is complete — avoids redundant server calls. #[test] fn test_sync_noop_when_already_onboarded_on_server() { App::test((), |mut app| async move { initialize_app(&mut app); set_local_onboarding_completed(&mut app, true); app.update(|ctx| { // User::test() defaults to is_onboarded = true; assert that and // leave it in place. assert_eq!( AuthStateProvider::as_ref(ctx).get().is_onboarded(), Some(true) ); }); app.update(|ctx| { let auth_state = AuthStateProvider::as_ref(ctx).get().clone(); RootView::sync_local_onboarding_to_server(&auth_state, ctx); }); app.read(|ctx| { assert_eq!( AuthStateProvider::as_ref(ctx).get().is_onboarded(), Some(true) ); }); }); }