F3: Kimi inference pipeline + full grok cloud-surface excision

Sampler / inference (PRD F3):
- kimi_compat.rs: single adaptation point for the Kimi chat/completions
  dialect (thinking-field mapping, model_id stripping, empty-content
  tool-call message fix, stream_options.include_usage), with kimi-cli
  source citations
- Rate-limit handling reworked for Kimi/Moonshot semantics; UA kigi/{version}
- /models replaces the xAI models-v2 endpoint everywhere; idle model
  refresh carries X-Msh-* device headers only (X-XAI-Token-Auth and
  x-grok-client-mode/CLIENT_MODE_HEADER machinery deleted)

Cloud-surface excision (PRD §5, zero-egress):
- remote/ conversations lane, cli-chat-proxy-types crate, prod/ dir,
  share command, credit bar: deleted (single local session lane;
  paginate() replaces merge_and_paginate)
- Subscription/tier gate stack deleted end-to-end: AppView
  gate/tier/team/ZDR fields, app/subscription.rs watch loop,
  dispatch/billing.rs paywall + SuperGrok upsell, free-usage-exhausted
  chain, tier-restricted commands, GateInfo, RemoteSettings gate fields,
  SettingsUpdateNotification gate fields
- /privacy + coding-data-sharing setting deleted (backed by a dead xAI
  RPC; Kigi is zero-egress — nothing to share or retain remotely)

Auth UX correctness (user-reported):
- Device-flow fixtures now mirror the live Kimi payload shape
  (https://www.kimi.com/code/authorize_device?user_code=..., verified
  against auth.kimi.com); the fabricated auth.kimi.com/device?code=...
  URLs are gone
- open_browser_detached is a no-op under cfg(test): unit tests drove
  wiremock fixture URLs into the real browser (root cause of the
  "garbage mock link" ABCD-1234 tabs)
- Welcome/pager-minimal rebrand: Grok Build -> Kigi, grok.com ->
  kimi.com, "Sign in to Grok" -> "Sign in to Kimi"
This commit is contained in:
2026-07-17 16:05:51 -04:00
parent fe1f885bb3
commit ea0ce9d15f
231 changed files with 4730 additions and 26358 deletions
@@ -1,4 +1,4 @@
//! Tests for session status, sharing, privacy, and coding-data-sharing dispatchers.
//! Tests for session status and sharing dispatchers.
use super::*;
@@ -60,659 +60,6 @@ fn send_while_idle_with_nonempty_shared_queue_routes_to_server() {
assert_eq!(q.last().map(|e| e.text.as_str()), Some("c"));
}
#[test]
fn show_privacy_info_zdr() {
let mut app = test_app_with_agent();
app.is_zdr = true;
let effects = dispatch(Action::ShowPrivacyInfo, &mut app);
assert!(effects.is_empty());
let text = last_system_text(&app, AgentId(0));
assert!(text.contains("Zero Data Retention"));
}
/// `/privacy` info-print uses the desktop-aligned "privacy mode" /
/// "share data" labels from the user's intentional rewrite.
#[test]
fn show_privacy_info_opted_out() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true;
let effects = dispatch(Action::ShowPrivacyInfo, &mut app);
assert!(effects.is_empty());
let text = last_system_text(&app, AgentId(0));
assert!(
text.contains("Privacy: privacy mode"),
"info-print must use 'Privacy: privacy mode' (desktop-aligned label): {text}",
);
assert!(text.contains("/privacy opt-in"));
}
#[test]
fn show_privacy_info_opted_in() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false;
let effects = dispatch(Action::ShowPrivacyInfo, &mut app);
assert!(effects.is_empty());
let text = last_system_text(&app, AgentId(0));
assert!(
text.contains("Privacy: share data"),
"info-print must use 'Privacy: share data' (desktop-aligned label): {text}",
);
assert!(text.contains("/privacy opt-out"));
}
/// The info-print uses desktop-aligned labels ("privacy mode" /
/// "share data"). This test pins those labels to catch accidental
/// regressions to the registry's "Opt in" / "Opt out" display
/// strings.
#[test]
fn show_privacy_info_does_not_use_old_desktop_labels() {
// opted-out → "Privacy: privacy mode"
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true;
let _ = dispatch(Action::ShowPrivacyInfo, &mut app);
let text = last_system_text(&app, AgentId(0));
assert!(
text.contains("privacy mode"),
"[opted-out] info-print must contain 'privacy mode': {text:?}",
);
// opted-in → "Privacy: share data"
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false;
let _ = dispatch(Action::ShowPrivacyInfo, &mut app);
let text = last_system_text(&app, AgentId(0));
assert!(
text.contains("share data"),
"[opted-in] info-print must contain 'share data': {text:?}",
);
}
// ── coding_data_sharing dispatch tests ───
//
// The dispatcher uses **optimistic + rollback + toast**, matching the
// `set_yolo_mode` pattern. These tests pin the contract:
// - Guards (ZDR, non-admin team) toast and short-circuit.
// - Idempotent dispatch toasts but emits no Effect.
// - Optimistic mutation flips `app.coding_data_retention_opt_out`
// BEFORE the Effect is emitted.
// - `Effect::SetCodingDataSharing` carries
// `rollback_to_opted_in = previous_value`.
// - `TaskResult::CodingDataSharingFailed` reverts the optimistic
// mutation; `TaskResult::CodingDataSharingUpdated` re-anchors
// to the server-confirmed value.
/// Idempotent re-dispatch when already opted-in toasts but emits
/// no Effect (avoids a wasted ACP round-trip).
///
/// Toast uses the **display name** ("Opt in", not the
/// snake-case canonical "opt-in") AND the **destructive `⚠`
/// glyph** on the opt-in direction (privacy-degrading).
#[test]
fn set_coding_data_sharing_idempotent_opt_in() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false; // currently opted-in
let effects = dispatch(Action::SetCodingDataSharing { opted_in: true }, &mut app);
assert!(
effects.is_empty(),
"idempotent re-dispatch must NOT emit Effect"
);
let toast = read_toast(&app);
assert!(
toast.contains("Opt in"),
"toast must show display name 'Opt in' (PR 9 R1, General-3 Issue 6): {toast}",
);
assert!(
!toast.contains("opt-in"),
"toast must NOT use snake-case canonical 'opt-in' — display name only: {toast}",
);
assert!(
toast.contains('\u{26A0}'),
"idempotent opt-in toast uses ⚠ destructive-warning glyph (PR 9 R1, \
General-3 Issue 5): {toast}",
);
// State unchanged.
assert!(
!app.coding_data_retention_opt_out,
"idempotent path must not mutate state",
);
}
/// Idempotent re-dispatch when already opted-out toasts but emits
/// no Effect.
///
/// Opt-out direction uses the **uniform `✓` glyph**
/// (restoring the safe default) and the display name "Opt out".
#[test]
fn set_coding_data_sharing_idempotent_opt_out() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true; // currently opted-out
let effects = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
assert!(
effects.is_empty(),
"idempotent re-dispatch must NOT emit Effect"
);
let toast = read_toast(&app);
assert!(
toast.contains("Opt out"),
"toast must show display name 'Opt out': {toast}",
);
assert!(
toast.contains('\u{2713}'),
"idempotent opt-out toast uses ✓ safe-default glyph: {toast}",
);
assert!(
!toast.contains('\u{26A0}'),
"opt-out is the safe direction — must NOT use ⚠: {toast}",
);
// State unchanged.
assert!(
app.coding_data_retention_opt_out,
"idempotent path must not mutate state",
);
}
/// ZDR teams are blocked from toggling. The blocked path
/// toasts (not scrollback) and short-circuits with no Effect.
#[test]
fn set_coding_data_sharing_blocked_by_zdr() {
let mut app = test_app_with_agent();
app.is_zdr = true;
app.coding_data_retention_opt_out = false;
let effects = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
assert!(effects.is_empty(), "ZDR block must NOT emit Effect");
let toast = read_toast(&app);
assert!(
toast.contains("Zero Data Retention"),
"ZDR toast must surface the policy: {toast}",
);
assert!(
toast.contains('\u{2717}'),
"blocked toast uses ✗ glyph: {toast}"
);
// State unchanged — the user was blocked, the optimistic
// mutation never happened.
assert!(
!app.coding_data_retention_opt_out,
"ZDR block must not mutate state",
);
}
/// ZDR block fires even when the toggle would be a no-op
/// (defense-in-depth: don't quietly accept a same-value toggle
/// from a user the policy says shouldn't be touching this).
#[test]
fn set_coding_data_sharing_blocked_by_zdr_even_if_idempotent() {
let mut app = test_app_with_agent();
app.is_zdr = true;
app.coding_data_retention_opt_out = false;
let effects = dispatch(Action::SetCodingDataSharing { opted_in: true }, &mut app);
assert!(effects.is_empty());
assert!(read_toast(&app).contains("Zero Data Retention"));
}
/// Non-admin team members are blocked from toggling (matches
/// desktop). The blocked path toasts and short-circuits.
#[test]
fn set_coding_data_sharing_blocked_non_admin() {
let mut app = test_app_with_agent();
app.team_name = Some("Acme".into());
app.team_role = Some("Member".into());
app.coding_data_retention_opt_out = false;
let effects = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
assert!(effects.is_empty());
let toast = read_toast(&app);
assert!(
toast.contains("team admin"),
"non-admin toast must mention team admin: {toast}",
);
}
/// Admin team members CAN toggle. The admin-allowed path produces
/// an Effect carrying the rollback value.
#[test]
fn set_coding_data_sharing_allowed_for_admin() {
let mut app = test_app_with_agent();
app.team_name = Some("Acme".into());
app.team_role = Some("Admin".into());
app.coding_data_retention_opt_out = false; // currently opted-in
let effects = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
assert_eq!(effects.len(), 1);
match &effects[0] {
Effect::SetCodingDataSharing {
opted_in,
rollback_to_opted_in,
..
} => {
assert!(!*opted_in, "Effect must carry opted_in=false");
assert!(
*rollback_to_opted_in,
"rollback_to_opted_in must capture pre-toggle opt-in=true",
);
}
other => panic!("expected SetCodingDataSharing Effect, got {other:?}"),
}
// Optimistic mutation already applied.
assert!(
app.coding_data_retention_opt_out,
"admin-allowed dispatch must optimistically flip state",
);
}
/// Non-idempotent dispatch emits one Effect AND mutates state
/// optimistically AND toasts.
#[test]
fn set_coding_data_sharing_produces_effect_and_optimistic_mutation() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false; // currently opted-in
let effects = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
assert_eq!(effects.len(), 1, "non-idempotent dispatch emits one Effect");
match &effects[0] {
Effect::SetCodingDataSharing {
agent_id,
opted_in,
rollback_to_opted_in,
} => {
assert_eq!(*agent_id, AgentId(0));
assert!(!*opted_in);
assert!(
*rollback_to_opted_in,
"rollback_to_opted_in must be pre-toggle value (true == opted-in)",
);
}
other => panic!("expected SetCodingDataSharing Effect, got {other:?}"),
}
// Optimistic mutation applied.
assert!(
app.coding_data_retention_opt_out,
"dispatch must optimistically mutate state",
);
// Toast on every dispatch (SHELL setter contract).
assert!(app.agents[&AgentId(0)].toast.is_some());
}
/// `TaskResult::CodingDataSharingUpdated` re-anchors state to the
/// server-confirmed value (defense-in-depth) and re-toasts.
#[test]
fn coding_data_sharing_updated_re_anchors_state_and_re_toasts() {
let mut app = test_app_with_agent();
// Simulate post-optimistic state: opted-out.
app.coding_data_retention_opt_out = true;
let id = AgentId(0);
// Server confirms opt-out (same as optimistic).
let effects = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingUpdated {
agent_id: id,
opted_in: false,
}),
&mut app,
);
assert!(effects.is_empty(), "TaskResult arm must NOT emit Effect");
// State re-anchored (was already true, stays true).
assert!(app.coding_data_retention_opt_out);
// Re-toast on confirmation uses display name + ✓.
let toast = read_toast(&app);
assert!(
toast.contains("Opt out"),
"confirmation toast must use display name 'Opt out': {toast}",
);
assert!(
toast.contains('\u{2713}'),
"opt-out confirmation toast uses ✓: {toast}",
);
}
/// `TaskResult::CodingDataSharingUpdated` corrects the in-memory
/// state if the server reshapes the boolean (e.g. policy
/// override). Pins the defense-in-depth re-anchor contract.
#[test]
fn coding_data_sharing_updated_corrects_state_if_server_disagrees() {
let mut app = test_app_with_agent();
// Optimistic mutation said "opt-out" — but the server
// overrides to "opt-in" (e.g. policy that prevents opt-out).
app.coding_data_retention_opt_out = true;
let id = AgentId(0);
let effects = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingUpdated {
agent_id: id,
opted_in: true, // server says opted-in
}),
&mut app,
);
assert!(effects.is_empty());
// State corrected to match server.
assert!(
!app.coding_data_retention_opt_out,
"server-confirmed opt-in must overwrite optimistic opt-out",
);
// Server-correction toast uses the destructive ⚠
// pattern for the opt-in direction (the privacy-degrading
// override deserves the warning glyph even if the SERVER, not
// the user, made the call).
let toast = read_toast(&app);
assert!(
toast.contains("Opt in"),
"post-correction toast uses display name 'Opt in': {toast}",
);
assert!(
toast.contains('\u{26A0}'),
"opt-in direction always uses ⚠ glyph, even on server-correction path: {toast}",
);
}
/// `TaskResult::CodingDataSharingFailed` REVERTS the optimistic
/// mutation and surfaces a failure toast. Pins the rollback
/// contract.
///
/// Failure toast uses the standardised "coding data sharing"
/// wording.
#[test]
fn coding_data_sharing_failed_rolls_back_and_toasts_error() {
let mut app = test_app_with_agent();
// Simulate post-optimistic state: user picked opt-out, state
// was flipped, then the ACP call failed. The pre-toggle value
// was opt-in (true), so `rollback_to_opted_in = true`.
app.coding_data_retention_opt_out = true;
let id = AgentId(0);
let effects = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingFailed {
agent_id: id,
error: "server error".into(),
rollback_to_opted_in: true,
}),
&mut app,
);
assert!(effects.is_empty(), "rollback path must NOT emit Effect");
// State reverted to pre-toggle (opted-in).
assert!(
!app.coding_data_retention_opt_out,
"rollback must revert optimistic mutation",
);
// Failure toast surfaces the error using full label.
let toast = read_toast(&app);
assert!(
toast.contains("coding data sharing"),
"PR 9 R1: failure toast wording standardised to include 'coding data sharing' \
(G2 Issue 2): {toast}",
);
assert!(toast.contains("server error"), "error in toast: {toast}");
assert!(toast.contains('\u{2717}'), "failure toast uses ✗: {toast}");
}
/// `TaskResult::CodingDataSharingFailed` reverts in the OTHER
/// direction too (the pre-toggle state could have been either).
#[test]
fn coding_data_sharing_failed_rolls_back_to_opt_out() {
let mut app = test_app_with_agent();
// Post-optimistic: opted-in (user picked opt-in, server
// failed, pre-toggle was opt-out).
app.coding_data_retention_opt_out = false;
let id = AgentId(0);
let effects = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingFailed {
agent_id: id,
error: "network timeout".into(),
rollback_to_opted_in: false,
}),
&mut app,
);
assert!(effects.is_empty());
// Reverted to pre-toggle opt-out.
assert!(
app.coding_data_retention_opt_out,
"rollback to opt-out must set state=true",
);
}
/// Optimistic mutation refreshes any open settings modal.
/// Without this refresh, the modal indicator would stay at the
/// pre-toggle value until manual re-render.
#[test]
fn set_coding_data_sharing_refreshes_open_modal_snapshot() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false;
// Open a settings modal (capture initial snapshot).
let _ = dispatch(Action::OpenSettings, &mut app);
// Verify snapshot reads opted-in.
let agent_id = AgentId(0);
{
let state = match &app.agents[&agent_id].active_modal {
Some(crate::views::modal::ActiveModal::Settings { state }) => state,
_ => panic!("expected Settings modal open after OpenSettings dispatch"),
};
assert!(
!state.pager_snapshot.coding_data_sharing_opt_out,
"initial snapshot must read opt_out=false (opted-in)",
);
}
// Dispatch the toggle.
let _ = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
// Snapshot now reflects the optimistic mutation.
let state = match &app.agents[&agent_id].active_modal {
Some(crate::views::modal::ActiveModal::Settings { state }) => state,
_ => panic!("Settings modal must still be open after SetCodingDataSharing dispatch"),
};
assert!(
state.pager_snapshot.coding_data_sharing_opt_out,
"snapshot must refresh to reflect opt_out=true (opted-out) after dispatch",
);
}
/// Rollback also refreshes the modal — the user sees the
/// reverted value, not the stale optimistic one.
#[test]
fn coding_data_sharing_failed_refreshes_open_modal_snapshot() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false;
let _ = dispatch(Action::OpenSettings, &mut app);
// Optimistic flip.
let _ = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
// ACP failure.
let _ = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingFailed {
agent_id: AgentId(0),
error: "x".into(),
rollback_to_opted_in: true,
}),
&mut app,
);
let state = match &app.agents[&AgentId(0)].active_modal {
Some(crate::views::modal::ActiveModal::Settings { state }) => state,
_ => panic!("Settings modal must still be open after rollback TaskResult"),
};
assert!(
!state.pager_snapshot.coding_data_sharing_opt_out,
"rollback must refresh snapshot back to opt_out=false (opted-in)",
);
}
// ── coding_data_sharing toast tests ─────────────
/// The opt-in transition
/// uses the **`⚠` destructive-warning glyph** + spelled-out
/// consequence text — mirroring `yolo_toast`'s
/// "Always-approve ON: all tool actions auto-run" pattern. The
/// consequence text is verbatim-pinned because the toast is the
/// only post-commit feedback for a privacy-degrading transition;
/// a future PR that softens the wording silently degrades the
/// safety affordance.
#[test]
fn set_coding_data_sharing_opt_in_renders_destructive_warning_toast() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true; // currently opted-out
let effects = dispatch(Action::SetCodingDataSharing { opted_in: true }, &mut app);
assert_eq!(effects.len(), 1, "non-idempotent opt-in must emit Effect");
let toast = read_toast(&app);
assert!(
toast.contains('\u{26A0}'),
"opt-in toast MUST use ⚠ glyph (PR 9 R1, General-3 Issue 5 — \
privacy-degrading transition deserves destructive-warning glyph): {toast}",
);
assert!(
!toast.contains('\u{2713}'),
"opt-in toast MUST NOT use the uniform ✓ glyph — that's the \
safe-default toast for opt-out: {toast}",
);
assert!(
toast.contains("Opt in"),
"destructive toast still uses display name 'Opt in': {toast}",
);
// Consequence text pinned: a future PR softening this loses
// the safety affordance.
assert!(
toast.contains("code samples"),
"destructive toast must spell out the consequence \
(mention 'code samples'): {toast}",
);
assert!(
toast.contains("training"),
"destructive toast must spell out the consequence \
(mention 'training'): {toast}",
);
}
/// The opt-out transition uses the
/// uniform `✓` glyph (safe default), NOT the destructive `⚠`.
/// Mirrors `yolo_toast(false)` precedent — restoring the safe
/// default doesn't warrant the heavier visual.
#[test]
fn set_coding_data_sharing_opt_out_renders_safe_default_toast() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = false; // currently opted-in
let _ = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
let toast = read_toast(&app);
assert!(
toast.contains('\u{2713}'),
"opt-out toast uses ✓ safe-default glyph: {toast}",
);
assert!(
!toast.contains('\u{26A0}'),
"opt-out toast MUST NOT use ⚠ — that's reserved for the privacy-degrading \
direction (PR 9 R1): {toast}",
);
assert!(toast.contains("Opt out"));
}
/// The toast renders
/// the registered `EnumChoice.display` ("Opt in" / "Opt out"),
/// NOT the persisted canonical ("opt-in" / "opt-out"). Mirrors
/// the `set_theme_toast_format_uses_display_name` contract.
/// The display strings here are pinned by the
/// `coding_data_sharing_choices_use_canonical_strings` e2e test
/// (registry side) AND
/// `pr9_coding_data_sharing_choices_use_canonical_strings` (which
/// also pins the display labels via the same EnumChoice
/// entries).
#[test]
fn coding_data_sharing_toast_format_uses_display_name() {
let mut app = test_app_with_agent();
// Opt-in direction.
app.coding_data_retention_opt_out = true;
let _ = dispatch(Action::SetCodingDataSharing { opted_in: true }, &mut app);
let opt_in_toast = read_toast(&app);
assert!(
opt_in_toast.contains("Opt in"),
"opt-in toast uses display 'Opt in', not canonical 'opt-in': {opt_in_toast}",
);
// Clear and test opt-out direction.
app.agents.get_mut(&AgentId(0)).unwrap().toast = None;
app.coding_data_retention_opt_out = false;
let _ = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
let opt_out_toast = read_toast(&app);
assert!(
opt_out_toast.contains("Opt out"),
"opt-out toast uses display 'Opt out', not canonical 'opt-out': {opt_out_toast}",
);
}
/// The failure toast
/// substitutes a generic placeholder when the error string is
/// too long OR contains control characters / newlines. Pins the
/// scrub contract.
#[test]
fn coding_data_sharing_failed_scrubs_long_error_messages() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true;
let id = AgentId(0);
// ~500-char error simulating a stack trace / HTML 502 page.
let huge_error = "a".repeat(500);
let _ = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingFailed {
agent_id: id,
error: huge_error.clone(),
rollback_to_opted_in: false,
}),
&mut app,
);
let toast = read_toast(&app);
assert!(
!toast.contains(&huge_error),
"long error MUST be scrubbed from the toast: {} chars",
toast.len(),
);
assert!(
toast.contains("see logs"),
"scrubbed toast must point at the log for full details: {toast}",
);
}
/// Control characters (CR/LF/NUL)
/// in the error trigger the scrub path even on short strings —
/// preserves the toast's single-line layout.
#[test]
fn coding_data_sharing_failed_scrubs_control_chars_in_error() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true;
let id = AgentId(0);
// Short message with embedded newlines.
let multiline = "line1\nline2\nline3".to_string();
let _ = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingFailed {
agent_id: id,
error: multiline.clone(),
rollback_to_opted_in: false,
}),
&mut app,
);
let toast = read_toast(&app);
assert!(
!toast.contains('\n'),
"newlines MUST be scrubbed from the toast (would break single-line layout): \
{toast:?}",
);
assert!(
toast.contains("see logs"),
"control-char-scrubbed toast points at logs: {toast}",
);
}
/// The scrub path preserves short,
/// sanitised error messages verbatim — the typical happy-path
/// shell-side error string stays unscrubbed.
#[test]
fn coding_data_sharing_failed_preserves_short_clean_error_message() {
let mut app = test_app_with_agent();
app.coding_data_retention_opt_out = true;
let id = AgentId(0);
let short_clean = "network timeout".to_string();
let _ = dispatch(
Action::TaskComplete(TaskResult::CodingDataSharingFailed {
agent_id: id,
error: short_clean.clone(),
rollback_to_opted_in: false,
}),
&mut app,
);
let toast = read_toast(&app);
assert!(
toast.contains(&short_clean),
"short clean error must appear verbatim in the toast: {toast}",
);
assert!(
!toast.contains("see logs"),
"short clean error must NOT trigger the scrub fallback: {toast}",
);
}
/// Direct unit test of the `scrub_error_for_toast` helper —
/// pins the threshold and the fallback string against drift.
#[test]
@@ -766,31 +113,6 @@ fn scrub_error_for_toast_unit() {
);
}
/// The no-agent path
/// returns empty cleanly — no toast (the show_toast call would
/// no-op anyway), no panic, no Effect emitted. A "✗ No active
/// session" toast would be dead UX (no agent = no toast surface
/// to render on), so this path emits a tracing::warn! instead.
#[test]
fn set_coding_data_sharing_no_agents_returns_empty_without_panic() {
let mut app = test_app_with_agent();
// Remove every agent so the dispatcher hits the no-agent path.
app.agents.clear();
// Force the view off Agent so the dispatcher falls through to
// app.agents.keys().next() which is now empty.
app.active_view = ActiveView::Welcome;
let effects = dispatch(Action::SetCodingDataSharing { opted_in: false }, &mut app);
assert!(
effects.is_empty(),
"no-agent path must return empty (no Effect to fire)",
);
// State unchanged (we never reach the optimistic mutation).
assert!(
!app.coding_data_retention_opt_out,
"no-agent path must NOT mutate state",
);
}
#[test]
fn dispatch_rename_session_updates_display_name_locally() {
let mut app = test_app_with_agent();
@@ -898,27 +220,6 @@ fn show_usage_on_welcome_screen_is_noop() {
);
}
#[test]
fn show_usage_with_redirect_url_shows_link_and_skips_fetch() {
let mut app = test_app_with_agent();
app.usage_billing_redirect_url = Some("https://billing.example.com/me".to_string());
let before = agent_scrollback_len(&app);
let effects = dispatch(Action::ShowUsage, &mut app);
assert!(
effects.is_empty(),
"with a redirect URL set, ShowUsage should not fetch (billing or auto-topup), got: {effects:?}"
);
assert_eq!(
agent_scrollback_len(&app),
before + 1,
"redirect path should push one system message with the billing link"
);
assert!(
last_system_text(&app, AgentId(0)).contains("https://billing.example.com/me"),
"redirect message should use the remote settings-provided URL"
);
}
// ── Minimal update-notice tests ──────────────────────────────────────
#[test]