This document describes the testing strategy and provides instructions for running and writing tests for the GitHub Guard.
The GitHub Guard includes tests covering:
- ✅ Permission Level Parsing: Verify permission string parsing
- ✅ Permission Level Checks: Test writer/maintainer detection
- ✅ Bot Account Detection: Test bot username identification
- ✅ WASM Build Verification: Ensure WASM compiles correctly
The project includes a Makefile for easy test execution:
# Run default pipeline (build, unit, wasm, integration, integrity)
make test
# Run default pipeline + Copilot test
make test-all
# Run unit tests only
make test-unit
# Verify WASM build
make test-wasm
# Run integration tests (requires Docker + .env)
make test-integration
# Run corpus-driven WASM integrity harness tests
make test-integrity-harness
# Refresh integrity corpus from real open-source data
make capture-integrity-corpus
# Run with Copilot CLI (default: yolo mode)
make test-copilot
# Run with Copilot CLI in different security modes
make test-copilot-yolo # No protection (development)
make test-copilot-all # AllowOnly repos=all
make test-copilot-public-only # Public-safe filtering behavior
make test-copilot-owner-only # Owner-scoped policy behavior
make test-copilot-repo-only # Repo-scoped policy behavior
make test-copilot-prefix-only # Repo-prefix policy behavior
make test-copilot-multi-only # Multi-entry scope behavior
make test-copilot-lockdown # GitHub MCP lockdown mode
# Show all available targets
make help| Target | Description |
|---|---|
make test |
Default pipeline: build + test-unit + test-wasm + test-integration + test-integrity-harness |
make test-all |
Default pipeline plus test-copilot |
make test-unit |
Rust unit tests (cargo test --lib) |
make test-wasm |
WASM build verification |
make test-integration |
Integration tests using gateway + MCP containers |
make test-integrity-harness |
Corpus-driven WASM integrity harness |
make capture-integrity-corpus |
Refresh integrity corpus fixture from live OSS repos |
make test-copilot |
Copilot test runner (default yolo mode) |
make test-copilot-yolo |
No guard / no DIFC |
make test-copilot-all |
AllowOnly repos=all policy mode |
make test-copilot-public-only |
Public-only AllowOnly mode |
make test-copilot-owner-only |
Owner-scoped AllowOnly mode |
make test-copilot-repo-only |
Repo-scoped AllowOnly mode |
make test-copilot-prefix-only |
Repo-prefix AllowOnly mode |
make test-copilot-multi-only |
Multi-entry AllowOnly scope mode |
make test-copilot-lockdown |
Lockdown mode (--lockdown-mode) |
The runner supports four documented modes for Copilot testing:
-
YOLO Mode (
make test-copilot-yolo)- No guard, no DIFC enforcement
- Use for development and debugging
-
Public-Only Mode (
make test-copilot-public-only)
- Runs DIFC in filter mode with
{"allow-only":{"repos":"public","min-integrity": "none"}} - Uses
allow-only.min-integrity=none - Intended to filter/block private data exposure
- Use for public-safe testing
- Owner-Only Mode (
make test-copilot-owner-only)
- Runs DIFC in filter mode
- Uses
{"allow-only":{"repos":["<allow_owner>/*"],"min-integrity": "none"}} - Use for owner-scoped validation
- Repo-Only Mode (
make test-copilot-repo-only)
- Runs DIFC in filter mode
- Uses
{"allow-only":{"repos":["<owner>/<repo>"],"min-integrity": "none"}} - Use for repo-scoped validation against one repository
See docs/OVERVIEW.md for detailed mode descriptions.
cd rust-guard && cargo testExpected output:
running 3 tests
test permissions::tests::test_bot_detection ... ok
test permissions::tests::test_permission_level_checks ... ok
test permissions::tests::test_permission_level_parsing ... ok
test result: ok. 3 passed; 0 failed; 0 ignored
cd rust-guard && cargo test -- --nocapture# Test permission parsing
cd rust-guard && cargo test test_permission_level_parsing
# Test bot detection
cd rust-guard && cargo test test_bot_detection- Copilot CLI logs:
/tmp/copilot/logs/(for exampleprocess-*.log) - Gateway container output during run:
/tmp/copilot/gateway.log - Gateway log copied to repository root on runner cleanup:
./gateway.log
- Gateway container output during run:
/tmp/gateway.log - Gateway log copied to repository root after run:
./gateway.log
The gateway itself writes internal logs to /tmp/gh-aw/mcp-logs inside the container.
These are not persisted to the host unless that path is mounted from the host.
Integration tests run the guard with actual MCP Gateway and GitHub MCP server containers.
- Docker must be running
- GitHub Personal Access Token (PAT) with appropriate scopes
- GitHub Container Registry (ghcr.io) authentication
Create a GitHub Personal Access Token at https://github.com/settings/tokens with these scopes:
repo- Repository access for MCP server operationsread:org- Organization read accessread:user- User profile read accessread:packages- Required for pulling container images from ghcr.io
# Create .env file with your GitHub token (NEVER commit this!)
echo 'GITHUB_TOKEN=ghp_your_token_here' > .env
# The .env file is already in .gitignore# Using GitHub CLI (recommended)
gh auth token | docker login ghcr.io -u $(gh api user -q .login) --password-stdin
# Verify authentication
docker pull ghcr.io/lpcox/github-guard:latestWhen validating current gateway behavior, prefer a locally built gateway image over ghcr.io.
The local image reflects your latest code and avoids lag from published tags.
Build the local gateway image from gh-aw-mcpg branch lpcox/github-difc:
# Match CROSS_REPO_RELEASE.md source repository/branch
git clone https://github.com/github/gh-aw-mcpg.git
cd gh-aw-mcpg
git fetch origin lpcox/github-difc
git checkout lpcox/github-difc
# Build local image used by integration tests
docker build -t local/gh-aw-mcpg:latest .Use github/gh-aw-mcpg with the same branch/tagging flow.
# Run integration tests against the local image
GATEWAY_IMAGE=local/gh-aw-mcpg:latest make test-integration# Run integration tests pinned to your local gateway build
GATEWAY_IMAGE=local/gh-aw-mcpg:latest make test-integration- Gateway startup with guard loaded
- MCP session initialization
- Tool listing through the gateway
- Read operations (
get_me,search_repositories)
The integrity harness executes the compiled WASM artifact and validates label_agent,
label_resource, and label_response against explicit ground truth.
- Harness runtime: Go + wazero (
src/integrity_harness_test.go) - Corpus fixture:
src/testdata/integrity/corpus_v1.json - Schema:
src/testdata/integrity/schema_v1.json - Backend behavior: replayed through mocked
env.call_backendhost import - Operation matrix: auto-discovers operations from Rust source and exercises every implemented tool in
label_resourceacross public/private repo visibility contexts while asserting operation class and integrity coverage. - Strict visibility guard: fails when a discovered tool has no inferable visibility scenarios unless explicitly marked visibility-agnostic.
Run:
make test-integrity-harnessRefresh corpus from live OSS repos (cli/cli, octocat/Hello-World):
make capture-integrity-corpusNotes:
- Requires
ghandjqfor corpus capture. - Harness tests are deterministic and do not need live network access.
The tests in rust-guard/src/permissions.rs validate parsing and helper logic for repository
permission levels (admin, maintain, write, triage, read, etc.).
Integrity initialization for issue/PR content is driven by GitHub response field
author_association and is implemented in rust-guard/src/labels/helpers.rs
(author_association_floor_from_str).
These are related concepts but tested in different places.
Tests the permission level parsing from GitHub API strings.
Test Cases:
"admin"→PermissionLevel::Admin"maintain"→PermissionLevel::Maintain"write"/"push"→PermissionLevel::Write"read"/"pull"→PermissionLevel::Read"unknown"→PermissionLevel::None
Why these matter:
- Ensures correct interpretation of GitHub permission strings
- Handles case-insensitive matching
- Maps legacy permission names (push/pull)
Tests permission level methods.
Test Cases:
Admin.is_maintainer()→trueMaintain.is_maintainer()→trueWrite.is_maintainer()→falseAdmin.is_contributor()→trueWrite.is_contributor()→trueRead.is_contributor()→false
Why these matter:
- Validates hierarchical permission model
- Ensures proper access control decisions
For content labeling, the guard maps GitHub author_association values to an initial
integrity floor (case-insensitive) as follows:
OWNER→ approvedMEMBER→ approvedCOLLABORATOR→ approvedCONTRIBUTOR→ unapprovedFIRST_TIME_CONTRIBUTOR→ unapprovedFIRST_TIMER→ noneNONE→ none- missing/unknown value → none
In the implementation:
- approved floor uses
writer_integrity(scope) - unapproved floor uses
reader_integrity(scope) - none floor uses empty labels (
[])
After initialization, tool-specific logic can further elevate integrity (for example, private repo defaults or merged/default-branch evidence).
Tests bot account identification.
Test Cases:
"dependabot[bot]"→ detected as bot"renovate"→ detected as bot"github-actions"→ detected as bot"octocat"→ not a bot"user-bot-helper"→ detected as bot (ends with-bot)
Why these matter:
- Bots receive approved-level integrity (with unapproved floor)
- Prevents false negatives on bot detection
- Case-insensitive matching
Tests are located in rust-guard/src/ files, typically in a #[cfg(test)] module:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_feature() {
let result = function_under_test("input");
assert_eq!(result, "expected");
}
}When adding new labeling rules, add tests in the relevant file under rust-guard/src/labels/:
#[test]
fn test_new_labeling_rule() {
let scope = "owner/repo";
// Test approved-level integrity generation
let labels = writer_integrity(scope);
assert!(labels.contains(&"unapproved:owner/repo".to_string()));
assert!(labels.contains(&"approved:owner/repo".to_string()));
}Unit tests run without the MCP Gateway, so backend calls (like count_merged_prs) cannot be tested directly. To test backend integration:
- Use
make test-copilotwith a live gateway - Run
make test-integrationwith Docker - Test manually in a real environment
Unit tests run on the native target, not WASM. The WASM build is verified separately with make test-wasm.
Create .github/workflows/test.yml:
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-wasip1
override: true
- name: Run tests
run: make test
- name: Build WASM
run: make buildEnsure you have the correct Rust version:
rustc --version
# Should be 1.70+ for wasm32-wasip1 targetAdd the WASI target:
rustup target add wasm32-wasip1Check Docker is running and containers are accessible:
docker ps
docker pull ghcr.io/lpcox/github-guard:latestTo generate a coverage report (requires cargo-tarpaulin):
cargo install cargo-tarpaulin
cd rust-guard && cargo tarpaulin --out Html
open tarpaulin-report.html