rgrc 0.6.12

Rusty Generic Colouriser
Documentation
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
// Tests for main.rs to improve coverage
// Targets: error handling, color mode logic, command spawning, timetrace paths
//
// Coverage improvements for src/main.rs (starting at 78/121):
// - Lines 131-132: empty command handling
// - Lines 145-157: color mode decision tree (auto/on/off)
// - Lines 186-192: timetrace feature timing paths
// - Lines 222-247: command spawning, stdout handling, exit code propagation
// - Error paths: spawn failures, wait errors, command not found (exit 127)
//
// Note: These tests spawn the compiled binary as a subprocess. When cross-compiling
// (e.g., aarch64-unknown-linux-musl on x86_64), subprocess spawning fails under QEMU.
// We skip these integration tests during cross-compilation by checking for native x86_64.

use std::process::{Command, Stdio};

/// Lines 131-132: Empty command handling
/// Tests that rgrc exits with an error when invoked without a command argument.
/// This verifies the args.command.is_empty() check and error return path.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_empty_command_exits_with_error() {
    // Test line 131-132: empty command handling
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .output()
        .expect("failed to run rgrc");

    // Should fail when no command is provided
    assert!(!output.status.success());

    // When no command is provided, help message is shown (to stdout or stderr)
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stdout.contains("Usage:")
            || stderr.contains("Usage:")
            || stdout.contains("OPTIONS")
            || stderr.contains("OPTIONS")
    );
}

/// Lines 222-232: Command not found error path
/// Tests that rgrc returns exit code 127 when trying to run a nonexistent command.
/// This exercises the spawn error handling path and ErrorKind::NotFound branch.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_nonexistent_command_returns_127() {
    // Test lines 222-223, 230-232: command not found error path
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["nonexistent-command-xyz-12345"])
        .output()
        .expect("failed to run rgrc");

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("command not found") || stderr.contains("not found"));
    // Should exit with 127 for command not found
    assert_eq!(output.status.code().unwrap_or(1), 127);
}

/// Lines 149-154: ColorMode::Off disables colorization
/// Tests that --color=off prevents ANSI escape codes in output.
/// This verifies the ColorMode::Off branch and should_colorize=false path.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_color_off_mode_no_ansi() {
    // Test lines 149-154: ColorMode::Off branch
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=off", "echo", "test output"])
        .output()
        .expect("failed to run rgrc with --color=off");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    // Should not contain ANSI escape codes
    assert!(!stdout.contains("\x1b["));
    assert!(stdout.contains("test output"));
}

/// Lines 149-154: ColorMode::On forces colorization
/// Tests that --color=on enables colorization even when stdout is not a terminal.
/// This verifies the ColorMode::On branch sets should_colorize=true unconditionally.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_color_on_mode_enables_colorization() {
    // Test lines 149-154: ColorMode::On branch
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=on", "echo", "ERROR: test"])
        .output()
        .expect("failed to run rgrc with --color=on");

    assert!(output.status.success());
    // With color=on, if there are rules for echo output, we might get ANSI codes
    // At minimum, the command should succeed
    assert!(!output.stdout.is_empty());
}

/// Lines 167: pseudo_command exclusion check
/// Tests that commands matching the pseudo_command pattern are excluded from colorization.
/// Verifies the exact match check against the exclusion list.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_pseudo_command_exact_match_exclusion() {
    // Test lines 167: pseudo_command exclusion check
    // When pseudo_command is exactly "ls", it should be excluded
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["ls"])
        .output()
        .expect("failed to run rgrc ls");

    // Should succeed (even if ls is excluded from colorization)
    assert!(output.status.success());
}

/// Lines 222-247: Piped output handling
/// Tests that output can be properly redirected when stdout is not a terminal.
/// This exercises the piped output path with colorization potentially disabled.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_piped_output_not_to_terminal() {
    // Test lines 222: stdout is not terminal path
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["echo", "piped output"])
        .stdout(Stdio::piped())
        .output()
        .expect("failed to run rgrc with piped output");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("piped output"));
}

/// Lines 226-235: Spawn error handling
/// Tests error handling when Command::spawn() fails.
/// This verifies the spawn error path and proper error message reporting.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_spawn_error_handling() {
    // Test lines 226-235: spawn error handling
    // Try to run a command that will fail to spawn
    // Use an invalid path to force spawn error
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["/this/path/does/not/exist/command"])
        .output()
        .expect("failed to run rgrc");

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("command not found") || stderr.contains("Failed to spawn"));
}

/// Lines 186-192: Timetrace feature instrumentation
/// Tests that the timetrace feature, when enabled with RGRCTIME env var,
/// records and reports timing information. Feature-gated test.
#[test]
#[cfg(all(feature = "debug", target_arch = "x86_64"))]
fn test_timetrace_feature_with_env_var() {
    // Test lines 186-192: timetrace feature paths
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .env("RGRCTIME", "1")
        .args(["echo", "timed test"])
        .stderr(Stdio::piped())
        .output()
        .expect("failed to run rgrc with RGRCTIME");

    assert!(output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    // Should contain timing information when timetrace feature is enabled
    assert!(stderr.contains("[rgrc:time]") || stderr.is_empty());
}

/// Lines 222-247: Command argument passing
/// Tests that command-line arguments are correctly passed through to the spawned command.
/// Verifies the args forwarding mechanism works correctly.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_command_with_args_passes_through() {
    // Test that command arguments are properly passed through
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["echo", "arg1", "arg2", "arg3"])
        .output()
        .expect("failed to run rgrc with multiple args");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("arg1"));
    assert!(stdout.contains("arg2"));
    assert!(stdout.contains("arg3"));
}

/// Lines 195-203: Rules loading optimization
/// Tests that expensive rule loading is skipped when should_colorize is false.
/// This verifies the performance optimization path when colorization is disabled.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_rules_not_loaded_when_color_off() {
    // Test lines 195, 203: rules loading is skipped when should_colorize is false
    // Using --color=off should skip expensive rule loading
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=off", "echo", "no rules loaded"])
        .output()
        .expect("failed to run rgrc");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("no rules loaded"));
}

/// Lines 41-42: Flush cache error handling
/// Tests error handling when cache directory cannot be created during --flush-cache.
/// This exercises the None branch when cache rebuild fails (requires embed-configs feature).
#[test]
#[cfg(target_arch = "x86_64")]
fn test_flush_cache_error_path() {
    // Test lines 41-42: flush_cache error handling
    // This tests the None branch when cache rebuild fails
    // Create a scenario where cache dir cannot be created (requires embed-configs feature)
    #[cfg(feature = "embed-configs")]
    {
        use std::env;
        // Set HOME to /dev/null (invalid directory) to force failure
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .env("HOME", "/dev/null/invalid")
            .args(["--flush-cache"])
            .output()
            .expect("failed to run rgrc --flush-cache");

        // Should fail with error message
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            assert!(
                stderr.contains("Error") || stderr.contains("Failed"),
                "Expected error message, got: {}",
                stderr
            );
        }
    }
}

/// Color mode argument parsing error
/// Tests that invalid --color mode arguments are properly rejected with an error.
/// Verifies argument validation for the color mode parameter.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_invalid_color_mode_argument() {
    // Test color mode parsing error
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=invalid", "echo", "test"])
        .output()
        .expect("failed to run rgrc with invalid color");

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("Invalid color mode") || stderr.contains("invalid"));
}

/// Lines 143-147: Console doesn't support colors path
/// Tests that when console doesn't support colors, colorization is disabled.
/// Covers: src/main.rs:143-147 console_supports_colors == false branch
#[test]
#[cfg(target_arch = "x86_64")]
fn test_console_no_color_support() {
    // Test line 143-147: console doesn't support colors
    // When NO_COLOR env var is set, console reports no color support
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .env("NO_COLOR", "1")
        .args(["echo", "ERROR: test"])
        .output()
        .expect("failed to run rgrc");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    // Should not contain ANSI codes when console doesn't support colors
    assert!(stdout.contains("ERROR: test"));
}

/// Lines 222-223: stdout_is_terminal check and inherit path
/// Tests that when stdout is a terminal and colorization is disabled,
/// stdout/stderr are inherited directly without piping.
/// Covers: src/main.rs:222-223 stdout_is_terminal && !should_colorize path
#[test]
#[cfg(target_arch = "x86_64")]
fn test_stdout_inherit_when_no_colorization() {
    // Test line 222-223: stdout inheritance when not colorizing
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=off", "echo", "plain text"])
        .output()
        .expect("failed to run rgrc");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("plain text"));
    // No ANSI codes should be present
    assert!(!stdout.contains("\x1b["));
}

/// Lines 145-148: Console colors disabled path
/// Tests behavior when console color support is disabled via NO_COLOR environment variable.
/// This verifies the colors_enabled() check and fallback behavior.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_console_colors_disabled_path() {
    // Test lines 145-148: console doesn't support colors path
    // Force NO_COLOR environment to disable colors
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .env("NO_COLOR", "1")
        .args(["echo", "no console colors"])
        .output()
        .expect("failed to run rgrc with NO_COLOR");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    // Should not have ANSI codes when console colors are disabled
    assert!(stdout.contains("no console colors"));
}

/// Lines 154-157: Supported command colorization check
/// Tests the should_use_colorization_for_command_supported logic.
/// Verifies that known commands trigger the colorization path.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_supported_command_colorization_check() {
    // Test lines 154-157: should_use_colorization_for_command_supported
    // Use a known supported command like 'ping' or 'ls'
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=auto", "echo", "supported command"])
        .output()
        .expect("failed to run rgrc with auto color");

    assert!(output.status.success());
}

/// Lines 240-244: Wait error handling
/// Tests the wait() error handling path (though wait() rarely fails in practice).
/// Primarily verifies the normal success case of the wait operation.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_wait_error_handling() {
    // Test lines 240-244: wait error handling
    // This is harder to test directly as wait() rarely fails
    // But we can verify the normal success path
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["echo", "wait success"])
        .output()
        .expect("failed to run rgrc");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("wait success"));
}

/// Lines 247: Child exit code propagation
/// Tests that the child process's exit code is correctly propagated to rgrc's exit code.
/// Verifies that non-zero exit codes from spawned commands are properly returned.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_child_exit_code_propagation() {
    // Test lines 247: exit code propagation
    // Run a command that exits with a specific non-zero code
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["sh", "-c", "exit 42"])
        .output()
        .expect("failed to run rgrc with failing command");

    // Should propagate the child's exit code
    assert!(!output.status.success());
    let code = output.status.code().unwrap_or(0);
    assert_eq!(code, 42, "Should propagate exit code 42");
}

/// Lines 222-247: Direct stdout inheritance optimization
/// Tests the performance optimization where stdout is inherited directly
/// when colorization is disabled and output goes to a terminal.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_stdout_inherit_for_terminal() {
    // Test lines 222-247: direct stdout inheritance when not colorizing and terminal output
    // This path uses Stdio::inherit() for performance
    // We can't easily test this in a unit test as it requires a real terminal
    // But we can verify the command runs successfully
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["--color=off", "echo", "inherited stdout"])
        .output()
        .expect("failed to run rgrc");

    assert!(output.status.success());
}

/// Lines 206: Empty rules check
/// Tests that when no rules match a command, colorization is skipped.
/// This verifies the rules.is_empty() optimization path.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_empty_rules_no_colorization() {
    // Test lines 206: rules.is_empty() check
    // When a command has no matching rules, colorization should be skipped
    let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
        .args(["echo", "no matching rules"])
        .output()
        .expect("failed to run rgrc");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("no matching rules"));
}

// CLI integration tests module - only run on native x86_64 to avoid cross-compilation issues
#[cfg(target_arch = "x86_64")]
mod cli_integration_tests {

    // ═══════════════════════════════════════════════════════════════════════════════
    // CLI Integration Tests (merged from cli_tests.rs)
    // Tests that verify command-line interface, argument parsing, and basic workflows
    // ═══════════════════════════════════════════════════════════════════════════════

    use std::process::Command;

    /// CLI Test: --help flag displays usage information
    #[test]
    fn test_prints_help() {
        let mut cmd = Command::new(env!("CARGO_BIN_EXE_rgrc"));
        cmd.arg("--help");
        let output = cmd.output().expect("failed to run rgrc --help");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains("Usage:") || stdout.contains("Options:"));
    }

    /// CLI Test: --version displays version number
    ///
    /// Ensures the --version flag outputs the current package version
    /// in the format "rgrc X.Y.Z" where version comes from Cargo.toml.
    /// Essential for troubleshooting and compatibility checks.
    #[test]
    fn test_prints_version() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("--version")
            .output()
            .expect("failed to run rgrc --version");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains(env!("CARGO_PKG_VERSION")));
    }

    /// CLI Test: -v shorthand for --version
    #[test]
    fn test_version_shorthand() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("-V")
            .output()
            .expect("failed to run rgrc -V");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        let expected = format!("rgrc {}", env!("CARGO_PKG_VERSION"));
        assert_eq!(stdout.trim(), expected);
    }

    /// CLI Test: --completions generates shell completion scripts (space-separated)
    #[test]
    fn test_completions_bash() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .args(["--completions", "bash"])
            .output()
            .expect("failed to run rgrc --completions bash");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(
            stdout.contains("_rgrc") || stdout.contains("compdef") || stdout.contains("complete")
        );
    }

    /// CLI Test: --completions=SHELL generates shell completion scripts (equals format)
    #[test]
    fn test_completions_with_equals() {
        // Test zsh with equals format
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("--completions=zsh")
            .output()
            .expect("failed to run rgrc --completions=zsh");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains("#compdef rgrc"));

        // Test fish with equals format
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("--completions=fish")
            .output()
            .expect("failed to run rgrc --completions=fish");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains("fish completion"));
    }

    /// CLI Test: --completions= with empty value should fail
    #[test]
    fn test_completions_empty_value() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("--completions=")
            .output()
            .expect("failed to run rgrc --completions=");

        assert!(!output.status.success());
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(stderr.contains("Missing value for --completions"));
    }

    /// CLI Test: --completions with unsupported shell fails
    #[test]
    fn test_unsupported_completions_shell() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .args(["--completions", "invalid_shell"])
            .output()
            .expect("failed to run rgrc");

        assert!(!output.status.success());
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(stderr.contains("Unsupported") || stderr.contains("unsupported"));
    }

    /// CLI Test: args.rs line 113 - Invalid color value
    /// Tests that --color with an invalid value (not on/off/auto) returns an error.
    /// Covers: src/args.rs:113 "Invalid color mode: {}" error path
    #[test]
    fn test_invalid_color_value() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .args(["--color", "invalid_value", "echo", "test"])
            .output()
            .expect("failed to run rgrc");

        assert!(!output.status.success());
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(stderr.contains("Invalid color mode") || stderr.contains("invalid"));
    }

    /// CLI Test: args.rs line 108 - Missing value for --color
    /// Tests that --color without a following value returns an error.
    /// Covers: src/args.rs:108 "Missing value for --color" error path
    #[test]
    fn test_missing_color_value() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .args(["--color"])
            .output()
            .expect("failed to run rgrc");

        assert!(!output.status.success());
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(stderr.contains("Missing value") || stderr.contains("missing"));
    }

    /// CLI Test: --all-aliases displays shell aliases
    ///
    /// Tests the --all-aliases feature which generates shell alias definitions
    /// for all supported commands (e.g., alias ls='rgrc ls').
    /// Users typically eval this output in their shell rc files for automatic colorization.
    #[test]
    fn test_all_aliases() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("--all-aliases")
            .output()
            .expect("failed to run rgrc --all-aliases");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        // Most systems will have at least some common commands
        assert!(stdout.contains("alias ") || !stdout.is_empty());
    }

    /// CLI Test: special alias for journalctl
    ///
    /// Verifies that alias generation emits a special alias for journalctl:
    /// alias journalctl='/usr/bin/rgrc journalctl --no-pager | less -R'
    #[test]
    fn test_all_aliases_includes_journalctl_special() {
        let exe_path = env!("CARGO_BIN_EXE_rgrc");
        // Get the filename (e.g., "rgrc") so the test doesn't break if the project is renamed
        let exe_name = std::path::Path::new(exe_path)
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("rgrc");

        let output = Command::new(exe_path)
            .arg("--all-aliases")
            .output()
            .expect("failed to run rgrc --all-aliases");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        // Ensure the special alias for journalctl appears in the output
        assert!(stdout.contains("alias journalctl="));

        let expected = format!("{} journalctl --no-pager | less -R", exe_name);
        assert!(stdout.contains(&expected));
    }

    /// CLI Test: --all-aliases --except filters out specified commands
    ///
    /// Verifies the --except flag allows users to exclude specific commands
    /// from alias generation (e.g., if they have custom ls/grep configurations).
    /// Expects comma-separated command list as argument.
    #[test]
    fn test_all_aliases_with_except() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .args(["--all-aliases", "--except", "ls,grep"])
            .output()
            .expect("failed to run rgrc");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        // Excluded commands should not appear in output
        assert!(!stdout.contains("alias ls='"));
        assert!(!stdout.contains("alias grep='"));
    }

    /// CLI Test: --flush-cache rebuilds embedded config cache
    ///
    /// Tests the cache rebuild mechanism for embedded configs.
    /// When --flush-cache is invoked:
    /// 1. Existing cache directory is removed
    /// 2. Embedded configs are re-extracted to ~/.cache/rgrc
    /// 3. Success message is displayed with config count
    ///
    ///    Only available when embed-configs feature is enabled.
    #[cfg(feature = "embed-configs")]
    #[test]
    fn test_flush_cache_success() {
        use tempfile::TempDir;
        let td = TempDir::new().unwrap();
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .env("HOME", td.path())
            .arg("--flush-cache")
            .output()
            .expect("failed to run rgrc --flush-cache");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(
            stdout.contains("Cache rebuild successful")
                || stdout.contains("Flushing and rebuilding cache")
        );
    }

    /// CLI Test: Piped child command output is forwarded correctly
    ///
    /// Verifies that rgrc correctly pipes and forwards the child process's stdout.
    /// This is fundamental to rgrc's operation: spawn command → capture output → colorize → forward.
    /// Tests that the piping mechanism preserves command output integrity.
    #[test]
    fn test_piped_child_output() {
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .args(["echo", "hello-from-child"])
            .output()
            .expect("failed to run rgrc");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains("hello-from-child"));
    }

    /// CLI Test: Default (auto) color mode disables colorization when piped
    /// This test verifies the fix for Issue #12 using the 'id' command.
    #[test]
    fn test_auto_color_mode_disables_ansi_when_piped() {
        // 'id' is in SUPPORTED_COMMANDS and has rules in conf.id
        // Running via .output() ensures stdout is a pipe (non-TTY)
        let output = Command::new(env!("CARGO_BIN_EXE_rgrc"))
            .arg("id")
            .output()
            .expect("failed to run rgrc");

        assert!(output.status.success());
        let stdout = String::from_utf8_lossy(&output.stdout);

        // If the bug is present, 'id' output will contain ANSI codes (e.g., coloring uid=)
        // If fixed, it will be plain text because the pipe is detected.
        assert!(
            !stdout.contains("\x1b"),
            "Output should not contain ANSI escape codes when piped to a non-TTY"
        );
    }
}