@@ -42,13 +42,14 @@ func (s *CCStatuslineTestSuite) TearDownTest() {
4242
4343// getDailyCostWithDaemonFallback Tests
4444
45- func (s * CCStatuslineTestSuite ) TestGetDailyCost_UsesDaemonWhenAvailable () {
45+ func (s * CCStatuslineTestSuite ) TestGetDailyStats_UsesDaemonWhenAvailable () {
4646 // Start mock daemon socket
4747 listener , err := net .Listen ("unix" , s .socketPath )
4848 assert .NoError (s .T (), err )
4949 s .listener = listener
5050
5151 expectedCost := 15.67
52+ expectedSessionSeconds := 3600
5253 go func () {
5354 conn , _ := listener .Accept ()
5455 defer conn .Close ()
@@ -57,9 +58,10 @@ func (s *CCStatuslineTestSuite) TestGetDailyCost_UsesDaemonWhenAvailable() {
5758 json .NewDecoder (conn ).Decode (& msg )
5859
5960 response := daemon.CCInfoResponse {
60- TotalCostUSD : expectedCost ,
61- TimeRange : "today" ,
62- CachedAt : time .Now (),
61+ TotalCostUSD : expectedCost ,
62+ TotalSessionSeconds : expectedSessionSeconds ,
63+ TimeRange : "today" ,
64+ CachedAt : time .Now (),
6365 }
6466 json .NewEncoder (conn ).Encode (response )
6567 }()
@@ -70,25 +72,27 @@ func (s *CCStatuslineTestSuite) TestGetDailyCost_UsesDaemonWhenAvailable() {
7072 SocketPath : s .socketPath ,
7173 }
7274
73- cost := getDailyCostWithDaemonFallback (context .Background (), config )
75+ stats := getDailyStatsWithDaemonFallback (context .Background (), config )
7476
75- assert .Equal (s .T (), expectedCost , cost )
77+ assert .Equal (s .T (), expectedCost , stats .Cost )
78+ assert .Equal (s .T (), expectedSessionSeconds , stats .SessionSeconds )
7679}
7780
78- func (s * CCStatuslineTestSuite ) TestGetDailyCost_FallbackWhenDaemonUnavailable () {
81+ func (s * CCStatuslineTestSuite ) TestGetDailyStats_FallbackWhenDaemonUnavailable () {
7982 // No socket exists, should fall back to cached API
8083 config := model.ShellTimeConfig {
8184 SocketPath : "/nonexistent/socket.sock" ,
82- Token : "" , // No token means FetchDailyCostCached returns 0
85+ Token : "" , // No token means FetchDailyStatsCached returns zero values
8386 }
8487
85- cost := getDailyCostWithDaemonFallback (context .Background (), config )
88+ stats := getDailyStatsWithDaemonFallback (context .Background (), config )
8689
87- // Should return 0 (from cache fallback with no token)
88- assert .Equal (s .T (), float64 (0 ), cost )
90+ // Should return zero values (from cache fallback with no token)
91+ assert .Equal (s .T (), float64 (0 ), stats .Cost )
92+ assert .Equal (s .T (), 0 , stats .SessionSeconds )
8993}
9094
91- func (s * CCStatuslineTestSuite ) TestGetDailyCost_FallbackOnDaemonError () {
95+ func (s * CCStatuslineTestSuite ) TestGetDailyStats_FallbackOnDaemonError () {
9296 // Start mock daemon that returns error
9397 listener , err := net .Listen ("unix" , s .socketPath )
9498 assert .NoError (s .T (), err )
@@ -107,57 +111,96 @@ func (s *CCStatuslineTestSuite) TestGetDailyCost_FallbackOnDaemonError() {
107111 Token : "" , // No token
108112 }
109113
110- cost := getDailyCostWithDaemonFallback (context .Background (), config )
114+ stats := getDailyStatsWithDaemonFallback (context .Background (), config )
111115
112- // Should fall back and return 0
113- assert .Equal (s .T (), float64 (0 ), cost )
116+ // Should fall back and return zero values
117+ assert .Equal (s .T (), float64 (0 ), stats .Cost )
118+ assert .Equal (s .T (), 0 , stats .SessionSeconds )
114119}
115120
116- func (s * CCStatuslineTestSuite ) TestGetDailyCost_UsesDefaultSocketPath () {
121+ func (s * CCStatuslineTestSuite ) TestGetDailyStats_UsesDefaultSocketPath () {
117122 // Test that default socket path is used when config is empty
118123 config := model.ShellTimeConfig {
119- SocketPath : "" , // Empty path
124+ SocketPath : "" , // Empty path - should use model.DefaultSocketPath
120125 Token : "" ,
121126 }
122127
123128 // This should use model.DefaultSocketPath internally
124- // Since no daemon is running, it will fall back
125- cost := getDailyCostWithDaemonFallback (context .Background (), config )
126-
127- assert .Equal (s .T (), float64 (0 ), cost )
129+ // Since no daemon is running at the default path, it will fall back to cached API
130+ // The function should not panic and should return a valid stats struct
131+ stats := getDailyStatsWithDaemonFallback (context .Background (), config )
132+
133+ // We can't assert on exact values since the global cache might have data
134+ // from previous tests. Just verify the function returns without error
135+ // and returns non-negative values
136+ assert .GreaterOrEqual (s .T (), stats .Cost , float64 (0 ))
137+ assert .GreaterOrEqual (s .T (), stats .SessionSeconds , 0 )
128138}
129139
130140// formatStatuslineOutput Tests
131141
132142func (s * CCStatuslineTestSuite ) TestFormatStatuslineOutput_AllValues () {
133- output := formatStatuslineOutput ("claude-opus-4" , 1.23 , 4.56 , 75.0 )
143+ output := formatStatuslineOutput ("claude-opus-4" , 1.23 , 4.56 , 3661 , 75.0 )
134144
135145 // Should contain all components
136146 assert .Contains (s .T (), output , "🤖 claude-opus-4" )
137147 assert .Contains (s .T (), output , "$1.23" )
138148 assert .Contains (s .T (), output , "$4.56" )
139- assert .Contains (s .T (), output , "75%" ) // Context percentage
149+ assert .Contains (s .T (), output , "1h1m" ) // Session time (3661 seconds = 1h 1m 1s)
150+ assert .Contains (s .T (), output , "75%" ) // Context percentage
140151}
141152
142153func (s * CCStatuslineTestSuite ) TestFormatStatuslineOutput_ZeroDailyCost () {
143- output := formatStatuslineOutput ("claude-sonnet" , 0.50 , 0 , 50.0 )
154+ output := formatStatuslineOutput ("claude-sonnet" , 0.50 , 0 , 300 , 50.0 )
144155
145156 // Should show "-" for zero daily cost
146157 assert .Contains (s .T (), output , "📊 -" )
158+ assert .Contains (s .T (), output , "5m0s" ) // Session time (300 seconds = 5m)
159+ }
160+
161+ func (s * CCStatuslineTestSuite ) TestFormatStatuslineOutput_ZeroSessionSeconds () {
162+ output := formatStatuslineOutput ("claude-sonnet" , 0.50 , 1.0 , 0 , 50.0 )
163+
164+ // Should show "-" for zero session seconds
165+ assert .Contains (s .T (), output , "⏱️ -" )
147166}
148167
149168func (s * CCStatuslineTestSuite ) TestFormatStatuslineOutput_HighContextPercentage () {
150- output := formatStatuslineOutput ("test-model" , 1.0 , 1.0 , 85.0 )
169+ output := formatStatuslineOutput ("test-model" , 1.0 , 1.0 , 60 , 85.0 )
151170
152171 // Should contain the percentage (color codes may vary)
153172 assert .Contains (s .T (), output , "85%" )
173+ assert .Contains (s .T (), output , "1m0s" )
154174}
155175
156176func (s * CCStatuslineTestSuite ) TestFormatStatuslineOutput_LowContextPercentage () {
157- output := formatStatuslineOutput ("test-model" , 1.0 , 1.0 , 25.0 )
177+ output := formatStatuslineOutput ("test-model" , 1.0 , 1.0 , 45 , 25.0 )
158178
159179 // Should contain the percentage
160180 assert .Contains (s .T (), output , "25%" )
181+ assert .Contains (s .T (), output , "45s" )
182+ }
183+
184+ // formatSessionDuration Tests
185+
186+ func (s * CCStatuslineTestSuite ) TestFormatSessionDuration_Seconds () {
187+ result := formatSessionDuration (45 )
188+ assert .Equal (s .T (), "45s" , result )
189+ }
190+
191+ func (s * CCStatuslineTestSuite ) TestFormatSessionDuration_Minutes () {
192+ result := formatSessionDuration (125 ) // 2m 5s
193+ assert .Equal (s .T (), "2m5s" , result )
194+ }
195+
196+ func (s * CCStatuslineTestSuite ) TestFormatSessionDuration_Hours () {
197+ result := formatSessionDuration (3665 ) // 1h 1m 5s
198+ assert .Equal (s .T (), "1h1m" , result )
199+ }
200+
201+ func (s * CCStatuslineTestSuite ) TestFormatSessionDuration_Zero () {
202+ result := formatSessionDuration (0 )
203+ assert .Equal (s .T (), "0s" , result )
161204}
162205
163206// calculateContextPercent Tests
0 commit comments