# How to Write Pack Tests and Validate Custom Packs for dcg

> Learn to write pack tests and validate custom packs for Dicklesworthstone/destructive_command_guard. Implement tests for creation, patterns, and performance. Run cargo test to validate your custom packs.

- Repository: [Jeff Emanuel/destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard)
- Tags: how-to-guide
- Published: 2026-07-16

---

**To write pack tests for dcg, create a test module that imports helpers from [`src/packs/test_helpers.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/test_helpers.rs), implements tests for pack creation, destructive patterns, safe patterns, and performance, then register the pack in [`src/packs/mod.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/mod.rs) and run `cargo test` to validate.**

Destructive Command Guard (dcg) is an open-source Rust project that protects AI agents by organizing regex patterns into modular packs. Writing comprehensive tests for these packs ensures that destructive operations are correctly identified and blocked while legitimate commands remain accessible. This guide demonstrates exactly how to write pack tests and validate custom packs for dcg using the repository's built-in testing framework and reusable validation helpers.

## Understanding the dcg Testing Architecture

The dcg testing architecture consists of three reusable components located in `src/packs/`:

- **[`test_template.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/test_template.rs)** – A complete skeleton showing the required test sections and structure for any new pack.
- **[`test_helpers.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/test_helpers.rs)** – A library of assertion functions including `assert_blocks`, `assert_allows`, and `assert_matches_within_budget`, plus the optional `LoggedPackTestRunner` for JSON CI reporting.
- **[`mod.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/mod.rs)** – The registry where completed packs are registered with `REGISTRY.register(pack)` to make them available to the evaluator.

Each pack lives at `src/packs/<category>/<name>.rs` and requires a corresponding test suite, either embedded in the same file within a `#[cfg(test)] mod tests { ... }` block or in a dedicated `src/packs/<category>/<name>_tests.rs` file.

## Step 1: Create the Pack Module

Begin by implementing the `create_pack()` function in your pack file. This function must return a `Pack` struct populated with metadata and pattern definitions using the provided macros:

```rust
// src/packs/database/postgresql.rs
pub fn create_pack() -> Pack {
    Pack {
        id: "database.postgresql".to_string(),
        name: "PostgreSQL",
        description: "Blocks destructive PostgreSQL commands (DROP DATABASE, TRUNCATE).",
        keywords: &["psql", "postgres"],
        safe_patterns: vec![
            safe_pattern!("list-databases", r"(?:^|\s)psql\s+...?\s+-l"),
        ],
        destructive_patterns: vec![
            destructive_pattern!(
                "drop-database",
                r"(?:^|\s)psql\s+...?\s+DROP\s+DATABASE",
                "DROP DATABASE permanently erases a DB. Use pg_dump first.",
                Critical,
                &const {
                    [
                        PatternSuggestion::new("pg_dump", "Export the DB before dropping"),
                    ]
                }
            ),
        ],
        keyword_matcher: None,
        safe_regex_set: None,
        safe_regex_set_is_complete: false,
    }
}

```

The `id` must follow the format `lowercase` with only dots, underscores, and digits. Each **destructive pattern** requires a unique name, regex, human-readable reason, severity level (`Critical`, `High`, `Medium`, or `Low`), and optional suggestions.

## Step 2: Set Up the Test File

Create your test module by copying the structure from [`src/packs/test_template.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/test_template.rs). Replace the placeholder references with your pack's module path:

```rust
// src/packs/database/postgresql_tests.rs
#[cfg(test)]
mod tests {
    use super::*;
    use crate::packs::test_helpers::*;
    use crate::packs::Severity;

    // Tests will go here following the template sections
}

```

Alternatively, embed the tests directly in [`postgresql.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/postgresql.rs) using `#[cfg(test)] mod tests { ... }`.

## Step 3: Write Pack Creation Tests

Validate that your pack initializes correctly and meets all structural requirements using the `validate_pack` helper:

```rust
#[test]
fn test_pack_creation() {
    let pack = create_pack();
    validate_pack(&pack);
}

```

The `validate_pack` function automatically verifies that:

- The `id` follows the required lowercase format with only `.`, `_`, or digits
- `name` and `description` are non-empty strings
- At least one keyword exists in the `keywords` array
- All regex patterns compile successfully
- Every destructive pattern has a non-empty reason string
- Pattern names are unique within the pack

## Step 4: Test Destructive Patterns

Write individual tests for each destructive pattern to ensure commands are blocked with correct severity and metadata:

```rust
#[test]
fn test_drop_database_critical() {
    let pack = create_pack();
    
    // Verify the command is blocked with reason substring match
    assert_blocks(&pack, "psql -c \"DROP DATABASE secret\"",
                  "DROP DATABASE permanently erases");
    
    // Verify severity level
    assert_blocks_with_severity(&pack,
                                "psql -c \"DROP DATABASE secret\"",
                                Severity::Critical);
    
    // Verify specific pattern triggered
    assert_blocks_with_pattern(&pack,
                               "psql -c \"DROP DATABASE secret\"",
                               "drop-database");
}

```

Use `assert_blocks_with_pattern` to confirm the specific pattern name matches, and `assert_blocks_with_severity` to validate the assigned severity level.

## Step 5: Test Safe Patterns and Specificity

Ensure that safe patterns match intended commands and that unrelated commands do not trigger false positives:

```rust
#[test]
fn test_safe_list_allows() {
    let pack = create_pack();
    assert_safe_pattern_matches(&pack, "psql -l");
    assert_allows(&pack, "psql -c \"SELECT * FROM users\"");
}

#[test]
fn test_specificity_unrelated() {
    let pack = create_pack();
    // Ensure MySQL commands don't trigger PostgreSQL patterns
    assert_no_match(&pack, "mysqldump -u root");
}

```

The `assert_allows` helper confirms the pack does not block benign commands, while `assert_no_match` verifies that completely unrelated commands bypass the pack entirely.

## Step 6: Add Performance and Edge-Case Tests

Guard against catastrophic regex backtracking by testing execution time budgets:

```rust
#[test]
fn test_performance() {
    let pack = create_pack();
    assert_matches_within_budget(&pack,
                                 "psql -c \"DROP DATABASE secret\"");
}

```

Add edge-case tests for whitespace variations, quoted arguments, special characters, and empty strings. For batch validation, use `test_batch_blocks` or `test_batch_allows` to validate multiple commands in a single test.

For CI integration, instantiate `LoggedPackTestRunner` to produce deterministic JSON output:

```rust
#[test]
fn test_with_logging() {
    let pack = create_pack();
    let runner = LoggedPackTestRunner::debug(&pack);
    runner.assert_blocks("psql -c \"DROP DATABASE secret\"");
    runner.assert_allows("psql -l");
    runner.finish(); // Outputs JSON report for scripts/e2e_test.sh
}

```

## Step 7: Register the Pack and Run Tests

Add your pack to the registry in [`src/packs/mod.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/mod.rs):

```rust
REGISTRY.register(database::postgresql::create_pack());

```

Execute the test suite for your specific pack:

```bash
cargo test packs::database::postgresql

```

The test harness automatically warms up lazy regexes and measures execution time. For full validation including integration tests, run the pre-commit script:

```bash
./scripts/scan_precommit_e2e.sh

```

This ensures your new pack does not break existing functionality and validates all destructive patterns against the full command suite.

## Summary

- **Pack modules** live at `src/packs/<category>/<name>.rs` and expose a `create_pack()` function returning a configured `Pack` struct.
- **Test helpers** in [`src/packs/test_helpers.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/test_helpers.rs) provide assertions like `assert_blocks`, `assert_allows`, `assert_matches_within_budget`, and `validate_pack` to enforce correctness and performance.
- **Test structure** follows the template in [`src/packs/test_template.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/test_template.rs), covering pack creation validation, destructive pattern matching, safe pattern verification, specificity testing, and performance budgets.
- **Registration** requires adding `REGISTRY.register(pack)` in [`src/packs/mod.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/mod.rs) to make the pack active in the evaluator.
- **Execution** uses `cargo test packs::<category>::<name>` for focused testing or [`./scripts/scan_precommit_e2e.sh`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/./scripts/scan_precommit_e2e.sh) for full CI validation.

## Frequently Asked Questions

### Where should I place test files for a new dcg pack?

You can either embed tests directly in the pack file using `#[cfg(test)] mod tests { ... }` or create a separate file at `src/packs/<category>/<name>_tests.rs`. Both approaches are valid; choose based on whether the test logic is extensive enough to warrant separation from the implementation.

### How do I verify that my pack correctly identifies destructive commands?

Use the `assert_blocks` helper to verify commands trigger blocks, `assert_blocks_with_pattern` to confirm the specific pattern name matches, and `assert_blocks_with_severity` to validate the severity level (`Critical`, `High`, `Medium`, or `Low`). Always include tests that verify the reason string contains expected explanatory text.

### What performance requirements must dcg custom packs meet?

Every pack should use `assert_matches_within_budget` to prevent regex patterns with catastrophic backtracking from impacting the evaluator. This helper fails the test if pattern matching exceeds the defined execution time budget, ensuring packs remain fast enough for real-time command guarding.

### How do I integrate pack tests into CI/CD pipelines?

Import `LoggedPackTestRunner` from [`src/packs/test_helpers.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/packs/test_helpers.rs) to generate JSON test reports that CI systems can parse. The repository includes [`./scripts/scan_precommit_e2e.sh`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/./scripts/scan_precommit_e2e.sh) which consumes these reports to detect regressions automatically without relying on flaky string matching, providing deterministic pass/fail signals for deployment gates.