(t *testing.T)
| 220 | } |
| 221 | |
| 222 | func TestRedactJSONSecrets(t *testing.T) { |
| 223 | t.Parallel() |
| 224 | |
| 225 | t.Run("redacts top level secret fields", func(t *testing.T) { |
| 226 | t.Parallel() |
| 227 | |
| 228 | input := []byte(`{"api_key":"abc","token":"def","password":"ghi","safe":"ok"}`) |
| 229 | redacted := chatdebug.RedactJSONSecrets(input) |
| 230 | require.JSONEq(t, `{"api_key":"[REDACTED]","token":"[REDACTED]","password":"[REDACTED]","safe":"ok"}`, string(redacted)) |
| 231 | }) |
| 232 | |
| 233 | t.Run("redacts security_token exact key", func(t *testing.T) { |
| 234 | t.Parallel() |
| 235 | |
| 236 | input := []byte(`{"security_token":"s3cret","securityToken":"tok","safe":"ok"}`) |
| 237 | redacted := chatdebug.RedactJSONSecrets(input) |
| 238 | require.JSONEq(t, `{"security_token":"[REDACTED]","securityToken":"[REDACTED]","safe":"ok"}`, string(redacted)) |
| 239 | }) |
| 240 | |
| 241 | t.Run("preserves LLM token usage fields", func(t *testing.T) { |
| 242 | t.Parallel() |
| 243 | |
| 244 | input := []byte(`{"input_tokens":100,"output_tokens":50,"prompt_tokens":80,"completion_tokens":20,"reasoning_tokens":10,"cache_creation_input_tokens":5,"cache_read_input_tokens":3,"total_tokens":150,"max_tokens":4096,"max_output_tokens":2048}`) |
| 245 | redacted := chatdebug.RedactJSONSecrets(input) |
| 246 | // All usage/limit fields should be preserved, not redacted. |
| 247 | require.Equal(t, input, redacted) |
| 248 | }) |
| 249 | |
| 250 | t.Run("redacts nested objects", func(t *testing.T) { |
| 251 | t.Parallel() |
| 252 | |
| 253 | input := []byte(`{"outer":{"nested_secret":"abc","safe":1},"keep":true}`) |
| 254 | redacted := chatdebug.RedactJSONSecrets(input) |
| 255 | require.JSONEq(t, `{"outer":{"nested_secret":"[REDACTED]","safe":1},"keep":true}`, string(redacted)) |
| 256 | }) |
| 257 | |
| 258 | t.Run("redacts arrays of objects", func(t *testing.T) { |
| 259 | t.Parallel() |
| 260 | |
| 261 | input := []byte(`[{"token":"abc"},{"value":1,"credentials":{"access_key":"def"}}]`) |
| 262 | redacted := chatdebug.RedactJSONSecrets(input) |
| 263 | require.JSONEq(t, `[{"token":"[REDACTED]"},{"value":1,"credentials":"[REDACTED]"}]`, string(redacted)) |
| 264 | }) |
| 265 | |
| 266 | t.Run("concatenated JSON is replaced with diagnostic", func(t *testing.T) { |
| 267 | t.Parallel() |
| 268 | |
| 269 | input := []byte(`{"token":"abc"}{"safe":"ok"}`) |
| 270 | result := chatdebug.RedactJSONSecrets(input) |
| 271 | require.Contains(t, string(result), "extra JSON values") |
| 272 | }) |
| 273 | |
| 274 | t.Run("non JSON input is replaced with diagnostic", func(t *testing.T) { |
| 275 | t.Parallel() |
| 276 | |
| 277 | input := []byte("not json") |
| 278 | result := chatdebug.RedactJSONSecrets(input) |
| 279 | require.Contains(t, string(result), "not valid JSON") |
nothing calls this directly
no test coverage detected