1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Bug fix test for project parameter handling
//!
//! ## Root Cause
//!
//! The `show_routine` at line 239 always calls `ProjectId::uuid(proj_id)`
//! regardless of whether `proj_id` is a path or UUID. This causes path projects
//! to fail with "Project not found" because it tries to find a UUID-named
//! directory instead of decoding the path.
//!
//! ## Why Not Caught
//!
//! No tests exercised `.show` command with path projects. All test scenarios
//! used UUID projects or relied on current directory detection.
//!
//! ## Fix Applied
//!
//! Added `parse_project_parameter()` helper that intelligently detects:
//! - Paths starting with `/` → `ProjectId::Path`
//! - Path-encoded starting with `-` → decode then `ProjectId::Path`
//! - Debug format `Path("...")` → extract and use `ProjectId::Path`
//! - Otherwise → `ProjectId::Uuid`
//!
//! ## Prevention
//!
//! Added test coverage for all project parameter formats:
//! - Absolute paths
//! - Path-encoded
//! - UUIDs
//! - Debug format from .list output
//! - Mixed scenarios (path project + UUID session)
//!
//! ## Pitfall
//!
//! Always assuming a string parameter is one type leads to silent failures
//! when user provides a different type. Smart detection with explicit format
//! rules prevents this class of bug.
//!
//! ## Isolation Note
//!
//! Integration tests use `CLAUDE_STORAGE_ROOT` + `TempDir` to avoid writing to
//! the real `~/.claude/`. This prevents race conditions in workspace-wide
//! `cargo nextest run --workspace`.
//!
//! Fix(issue-project-bug-fix-isolation)
//! Root cause: original integration tests wrote to real `~/.claude/` with
//! comment "CLI doesn't support custom storage root" — that comment was stale;
//! `CLAUDE_STORAGE_ROOT` support was added in the hygiene sprint.
//! Pitfall: stale comments documenting missing features can cause isolation
//! anti-patterns to persist long after the feature is added.
mod common;
use std::fs;
use tempfile::TempDir;
#[ test ]
fn test_show_with_path_project()
{
use claude_storage_core::encode_path;
let storage = TempDir::new().unwrap();
let project_path = TempDir::new().unwrap();
let encoded = encode_path( project_path.path() ).unwrap();
let project_dir = storage.path().join( "projects" ).join( &encoded );
fs::create_dir_all( &project_dir ).unwrap();
let session_id = "test-session-12345";
let session_file = project_dir.join( format!( "{session_id}.jsonl" ) );
fs::write( &session_file, r#"{"type":"user","text":"test"}"# ).unwrap();
let output = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.args( [
".show",
&format!( "session_id::{session_id}" ),
&format!( "project::{}", project_path.path().display() )
] )
.output()
.unwrap();
let stdout = String::from_utf8_lossy( &output.stdout );
let stderr = String::from_utf8_lossy( &output.stderr );
// Should NOT fail with "Project not found"
assert!(
!stderr.contains( "Project not found" ),
"Bug: Path project treated as UUID. stderr: {stderr}"
);
// Should show session details
assert!(
stdout.contains( "Session:" ) || stdout.contains( session_id ),
"Expected session details in output. stdout: {stdout}"
);
}
#[ test ]
fn test_show_with_uuid_project_still_works()
{
// Ensure our fix doesn't break UUID projects (regression test)
let storage = TempDir::new().unwrap();
let project_uuid = "test-uuid-project-123";
let project_dir = storage.path().join( "projects" ).join( project_uuid );
fs::create_dir_all( &project_dir ).unwrap();
let session_id = "test-session-uuid-789";
let session_file = project_dir.join( format!( "{session_id}.jsonl" ) );
fs::write( &session_file, r#"{"type":"user","text":"test"}"# ).unwrap();
let output = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.args( [
".show",
&format!( "session_id::{session_id}" ),
&format!( "project::{project_uuid}" )
] )
.output()
.unwrap();
let stdout = String::from_utf8_lossy( &output.stdout );
let stderr = String::from_utf8_lossy( &output.stderr );
assert!(
!stderr.contains( "Project not found" ),
"Regression: UUID project support broken. stderr: {stderr}"
);
assert!(
stdout.contains( "Session:" ),
"Expected session details. stdout: {stdout}"
);
}
#[ test ]
fn test_parse_project_parameter_unit()
{
use claude_storage::parse_project_parameter;
use claude_storage_core::ProjectId;
// Test absolute path
let result = parse_project_parameter( "/home/user/project" ).unwrap();
assert!(
matches!( result, ProjectId::Path( _ ) ),
"Absolute path should be ProjectId::Path"
);
// Test path-encoded
let result = parse_project_parameter( "-home-user-project" ).unwrap();
assert!(
matches!( result, ProjectId::Path( _ ) ),
"Path-encoded should be ProjectId::Path"
);
// Test UUID
let result = parse_project_parameter( "abc-123-def" ).unwrap();
assert!(
matches!( result, ProjectId::Uuid( _ ) ),
"UUID format should be ProjectId::Uuid"
);
// Test Debug format from .list output
let result = parse_project_parameter( r#"Path("/home/user/project")"# ).unwrap();
assert!(
matches!( result, ProjectId::Path( _ ) ),
"Debug format should extract path"
);
}