From 34369a07cab2cbea3dd70b6a71b99bb9349309dc Mon Sep 17 00:00:00 2001 From: Zious Date: Tue, 21 Apr 2026 08:38:26 -0500 Subject: [PATCH 1/2] test: corrupt team cache is non-fatal for issue view (#194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add integration test that writes truncated JSON to teams.json, runs `jr issue view`, and asserts the command succeeds with an actionable hint ("name not cached — run 'jr team list --refresh'") inline in the Team row. Scope note: the issue's original proposal called for "stderr contains a warning about the cache", but that's the I/O-error path (src/cli/issue/list.rs:951). Corrupt JSON takes a different path — src/cache.rs:23-26 maps serde_json::from_str failures to Ok(None), and the handler at list.rs:947 surfaces the same inline hint used for a cold cache. That hint is equally actionable (user runs the refresh to overwrite the corrupt file), so the test pins the real behavior rather than asserting the stderr warning that doesn't exist for this path. Refs #194 --- tests/issue_view_errors.rs | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/issue_view_errors.rs b/tests/issue_view_errors.rs index cc67c301..cd3f952c 100644 --- a/tests/issue_view_errors.rs +++ b/tests/issue_view_errors.rs @@ -132,3 +132,74 @@ async fn issue_view_network_drop_surfaces_reach_error() { ); assert!(!stderr.contains("panic"), "stderr leaked a panic: {stderr}"); } + +/// Corrupt `teams.json` must be non-fatal: `jr issue view` keeps running and +/// the Team row shows the UUID with an actionable hint pointing the user at +/// `jr team list --refresh` (same path as a cold cache). +/// +/// Behavior follows `src/cache.rs:23-26` — `serde_json::from_str` failures +/// return `Ok(None)` rather than propagating the parse error. The `Ok(None)` +/// branch at `src/cli/issue/list.rs:947` surfaces the `(name not cached — +/// run 'jr team list --refresh')` hint inline in the table. See issue #194 +/// for the divergence from the original "stderr warning" proposal. +#[tokio::test] +async fn issue_view_corrupt_team_cache_falls_back_gracefully() { + let server = MockServer::start().await; + let cache_dir = tempfile::tempdir().unwrap(); + let config_dir = tempfile::tempdir().unwrap(); + + let jr_cache_dir = cache_dir.path().join("jr"); + std::fs::create_dir_all(&jr_cache_dir).unwrap(); + // Truncated JSON — serde_json::from_str returns Err, which read_cache + // maps to Ok(None) per src/cache.rs:23-26. + std::fs::write(jr_cache_dir.join("teams.json"), "{ not json").unwrap(); + + let jr_config_dir = config_dir.path().join("jr"); + std::fs::create_dir_all(&jr_config_dir).unwrap(); + std::fs::write( + jr_config_dir.join("config.toml"), + "[fields]\nteam_field_id = \"customfield_10001\"\n", + ) + .unwrap(); + + let team_uuid = "36885b3c-1bf0-4f85-a357-c5b858c31de4"; + Mock::given(method("GET")) + .and(path("/rest/api/3/issue/PROJ-1")) + .respond_with(ResponseTemplate::new(200).set_body_json( + common::fixtures::issue_response_with_team( + "PROJ-1", + "Issue with team", + "customfield_10001", + team_uuid, + ), + )) + .mount(&server) + .await; + + let output = Command::cargo_bin("jr") + .unwrap() + .env("JR_BASE_URL", server.uri()) + .env("JR_AUTH_HEADER", "Basic dGVzdDp0ZXN0") + .env("XDG_CACHE_HOME", cache_dir.path()) + .env("XDG_CONFIG_HOME", config_dir.path()) + .args(["issue", "view", "PROJ-1"]) + .output() + .unwrap(); + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "Corrupt team cache should be non-fatal, stderr: {stderr}, stdout: {stdout}" + ); + assert!( + stdout.contains(team_uuid), + "Output should show the UUID so the user can identify the team, stdout: {stdout}" + ); + assert!( + stdout.contains("name not cached") && stdout.contains("jr team list --refresh"), + "Output should guide the user to refresh the cache, stdout: {stdout}" + ); + assert!(!stderr.contains("panic"), "stderr leaked a panic: {stderr}"); +} From e0affd0b3b72b9d5ad5dd6172306ce815741d506 Mon Sep 17 00:00:00 2001 From: Zious Date: Tue, 21 Apr 2026 08:55:10 -0500 Subject: [PATCH 2/2] fix(tests): truncate JSON content to match comment (#194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Copilot review: the comment said "Truncated JSON" but the previous content ("{ not json") was malformed tokens rather than a truncation. Use `{"teams": [` — valid JSON opening abruptly cut off — so the test fixture matches its stated partial-write scenario. Both inputs trigger the same Ok(None) fall-through (verified by the unit test at src/cache.rs:661), so coverage is unchanged. --- tests/issue_view_errors.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/issue_view_errors.rs b/tests/issue_view_errors.rs index cd3f952c..871a1dd5 100644 --- a/tests/issue_view_errors.rs +++ b/tests/issue_view_errors.rs @@ -150,9 +150,10 @@ async fn issue_view_corrupt_team_cache_falls_back_gracefully() { let jr_cache_dir = cache_dir.path().join("jr"); std::fs::create_dir_all(&jr_cache_dir).unwrap(); - // Truncated JSON — serde_json::from_str returns Err, which read_cache - // maps to Ok(None) per src/cache.rs:23-26. - std::fs::write(jr_cache_dir.join("teams.json"), "{ not json").unwrap(); + // Truncated JSON — simulates a partial write (disk-full, interrupted + // process). serde_json::from_str returns Err, which read_cache maps to + // Ok(None) per src/cache.rs:23-26. + std::fs::write(jr_cache_dir.join("teams.json"), "{\"teams\": [").unwrap(); let jr_config_dir = config_dir.path().join("jr"); std::fs::create_dir_all(&jr_config_dir).unwrap();