Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions .github/workflows/ci-plugin-catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches: [main]
paths:
- "tests/test_plugin_catalog.py"
- "tests/test_repository_policy.py"
- "deny.toml"
- ".github/workflows/**"
- "Cargo.toml"
- "tools/plugin_catalog.py"
- "tools/validate_ci_selection.py"
- "pyproject.toml"
Expand All @@ -13,16 +17,15 @@ on:
- "plugins/rust/python-package/*/uv.lock"
- "plugins/python/**"
- "plugins/rust/python-package/*/Cargo.toml"
- "plugins/rust/python-package/*/plugin-manifest.yaml"
- ".github/workflows/ci-plugin-catalog.yaml"
- ".github/workflows/ci-rust-python-package.yaml"
- ".github/workflows/release-rust-python-package.yaml"
- ".github/workflows/ci-python-package.yaml"
- ".github/workflows/release-python-package.yaml"
- "plugins/rust/python-package/*/cpex_*/plugin-manifest.yaml"
pull_request:
branches: [main]
paths:
- "tests/test_plugin_catalog.py"
- "tests/test_repository_policy.py"
- "deny.toml"
- ".github/workflows/**"
- "Cargo.toml"
- "tools/plugin_catalog.py"
- "tools/validate_ci_selection.py"
- "pyproject.toml"
Expand All @@ -31,12 +34,7 @@ on:
- "plugins/rust/python-package/*/uv.lock"
- "plugins/python/**"
- "plugins/rust/python-package/*/Cargo.toml"
- "plugins/rust/python-package/*/plugin-manifest.yaml"
- ".github/workflows/ci-plugin-catalog.yaml"
- ".github/workflows/ci-rust-python-package.yaml"
- ".github/workflows/release-rust-python-package.yaml"
- ".github/workflows/ci-python-package.yaml"
- ".github/workflows/release-python-package.yaml"
- "plugins/rust/python-package/*/cpex_*/plugin-manifest.yaml"

concurrency:
group: ci-plugin-catalog-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref_name }}
Expand All @@ -55,5 +53,8 @@ jobs:
with:
python-version: "3.12"

- name: Run plugin catalog tests
run: python3 -m unittest tests/test_plugin_catalog.py
- name: Validate repository catalog
run: python3 tools/plugin_catalog.py validate .

- name: Run catalog behavior and repository policy tests
run: python3 -m unittest tests/test_plugin_catalog.py tests/test_repository_policy.py
23 changes: 4 additions & 19 deletions .github/workflows/ci-scaffold-generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@ jobs:
- name: Install scaffold dependencies
run: python3 -m pip install jinja2

- name: Test --help
run: python3 tools/scaffold_plugin.py --help

- name: Reject invalid hook names
run: |
python3 tools/scaffold_plugin.py --non-interactive --name reject_test \
--hooks typo_hook && exit 1 || true
if python3 tools/scaffold_plugin.py --non-interactive --name reject_test --hooks typo_hook; then
echo "Scaffolder accepted an invalid hook" >&2
exit 1
fi

- name: Install uv and maturin
run: python3 -m pip install uv==0.9.30 maturin==1.12.6
Expand Down Expand Up @@ -76,13 +75,6 @@ jobs:
--description "CI scaffold default test plugin" \
--author "CI"

- name: Verify default plugin standalone CPEX imports
run: |
! grep -R "mcpgateway\.plugins\.framework\|from mcpgateway\|import mcpgateway\|mcpgateway\.plugins\|gateway\.plugins" plugins/rust/python-package/ci_scaffold_default
grep -R "cpex.framework" plugins/rust/python-package/ci_scaffold_default
grep -F '"cpex>=0.1.0rc1,<0.2"' plugins/rust/python-package/ci_scaffold_default/pyproject.toml
python3 -m py_compile plugins/rust/python-package/ci_scaffold_default/cpex_ci_scaffold_default/ci_scaffold_default.py

- name: Sync default plugin
working-directory: plugins/rust/python-package/ci_scaffold_default
run: make sync
Expand All @@ -99,13 +91,6 @@ jobs:
--author "CI" \
--hooks prompt_pre_fetch,prompt_post_fetch,tool_pre_invoke,tool_post_invoke,resource_pre_fetch,resource_post_fetch,agent_pre_invoke,agent_post_invoke,http_pre_request,http_post_request,http_auth_resolve_user,http_auth_check_permission

- name: Verify all-hooks plugin standalone CPEX imports
run: |
! grep -R "mcpgateway\.plugins\.framework\|from mcpgateway\|import mcpgateway\|mcpgateway\.plugins\|gateway\.plugins" plugins/rust/python-package/ci_scaffold_all_hooks
grep -R "cpex.framework" plugins/rust/python-package/ci_scaffold_all_hooks
grep -F '"cpex>=0.1.0rc1,<0.2"' plugins/rust/python-package/ci_scaffold_all_hooks/pyproject.toml
python3 -m py_compile plugins/rust/python-package/ci_scaffold_all_hooks/cpex_ci_scaffold_all_hooks/ci_scaffold_all_hooks.py

- name: Sync all-hooks plugin
working-directory: plugins/rust/python-package/ci_scaffold_all_hooks
run: make sync
Expand Down
75 changes: 34 additions & 41 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,37 @@ This is a monorepo of standalone plugin packages for the ContextForge Plugin Ext

## Testing Strategy

### Test Location by Type

- **Unit tests**: Located within each plugin's own directory
- Python: `plugins/rust/python-package/<slug>/tests/` (current hybrid) or `plugins/python/<slug>/tests/` (pure Python)
- Rust: inline `mod tests` within source files (e.g., `src/lib.rs`)
- Test individual plugin functionality in isolation
- Fast, deterministic tests
- Run during plugin development and CI
- Scope: Plugin logic, Rust functions, Python bindings

- **Plugin-framework integration tests**: Located in `plugins/rust/python-package/<slug>/tests/` for Rust plugins and `plugins/tests/<slug>/` for pure-Python plugins
- Test plugin integration with the local plugin framework, including the PyO3 interface for Rust plugins
- Run via `make test-integration` within the plugin directory
- Scope: Plugin entry points (including PyO3 for Rust), loading by the Python framework, hook dispatch

- **Gateway integration tests**: Located in `mcp-context-forge/tests/integration/`
- Test plugin integration with the full gateway
- Test cross-plugin interactions
- Test plugin lifecycle management
- Scope: Plugin loading in gateway context, hook execution, framework interaction

- **E2E tests**: Located in `mcp-context-forge/tests/e2e/`
- Test complete workflows with plugins enabled
- Test plugin behavior in realistic scenarios
- Test multi-gateway plugin coordination
- Scope: Full request/response cycles, real-world usage patterns

### Cross-Repository Testing Coordination

When developing a plugin:

1. Write unit tests in the plugin's own directory (Rust: inline `mod tests` plus binding tests; pure Python: `plugins/python/<slug>/tests/`) and plugin-framework integration tests in the Rust plugin's `tests/` directory or `plugins/tests/<slug>/` for pure Python
2. Run local tests: `make test-all` and `make test-integration` from plugin directory
3. After plugin PR is merged, coordinate with `mcp-context-forge` team
4. Write gateway integration/E2E tests in `mcp-context-forge/tests/`
5. Ensure both repositories' CI passes before release

See `mcp-context-forge/tests/AGENTS.md` for integration/E2E test conventions.
Keep a test when it protects an observable plugin decision, a language/framework
boundary, a release contract, a security policy, or a concrete regression.
Before adding one, identify the failure it should catch and check whether an
existing test already catches it. Test behavior at the lowest layer that owns it.

- Rust algorithms and policy decisions: inline Rust tests in the plugin crate.
- Pure-Python logic: `plugins/python/<slug>/tests/`.
- Python/Rust binding and hook behavior: `plugins/tests/<slug>/`. Existing
plugins use this shared harness; it supplies controlled hook models and is
not a full gateway. The scaffolder emits plugin-local Python hook smoke tests.
- Catalog and wheel tooling behavior: `tests/`, using temporary fixture repos.
- Repository security policies: `tests/test_repository_policy.py`.
- Built-package installation: release workflows, outside the source tree.
- Full gateway and cross-plugin workflows: `mcp-context-forge/tests/integration/`
and `mcp-context-forge/tests/e2e/`.

Do not add tests for exact workflow/Makefile text, documentation wording,
private module layout, generated stub formatting, standard-library behavior,
or generic Pydantic serialization. Build, lint, type-check, and artifact jobs
own those checks. Never commit tests whose only outcome is `pass`, `hasattr`,
or a constructor returning a non-null object.

Use table-driven cases for distinct input classes and failure policies. Avoid
replaying a Rust algorithm's whole input matrix through Python; Python tests
should catch conversion, payload isolation, hook results, and error mapping.
Retain Redis failure/TLS tests and detection/redaction/privacy regressions:
these exercise real operational and security behavior.

Run `make plugins-validate` for repository tooling. For plugin changes, run
`make test-all`, `make test-integration`, and the plugin's `make ci` target as
appropriate. See [TESTING.md](TESTING.md) for commands and the keep/remove decisions.

## Plugin Development Workflows

Expand Down Expand Up @@ -85,9 +78,9 @@ The plugin framework is currently implemented in Python (`mcpgateway/plugins/fra
```bash
cd plugins/rust/python-package/<slug>
# Add Rust unit tests inline in src/ using mod tests
# Add Python unit tests in tests/
# Add plugin-framework integration tests in tests/ (run via make test-integration)
make test-all # Run Rust + Python unit tests
# Add Python boundary tests in plugins/tests/<slug>/
# Extend existing tests for changed behavior
make test-all # Run Rust tests and Python hook tests
make test-integration # Run plugin-framework integration tests
```

Expand Down
6 changes: 4 additions & 2 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ make plugin-test PLUGIN=pii_filter

`make plugins-validate` runs the same convention checks that the repo contract CI workflow runs.
It runs the catalog validator plus the shared repo contract test modules:
`tests/test_plugin_catalog.py` and `tests/test_install_built_wheel.py`.
`tests/test_plugin_catalog.py`, `tests/test_install_built_wheel.py`, and
`tests/test_repository_policy.py`. See [TESTING.md](TESTING.md) for test ownership
and criteria for keeping a test.

## Secret Detection

Expand Down Expand Up @@ -102,7 +104,7 @@ This interactive tool will:
- Prompt for plugin name, description, author, and version
- Let you select from 12 available hooks across 5 categories
- Generate complete plugin structure with all required files
- Create comprehensive unit tests (Python and Rust)
- Add behavior tests for plugin decisions and Python/Rust boundary behavior
- Set up build configuration and documentation

For non-interactive mode:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins-list:

plugins-validate:
@python3 tools/plugin_catalog.py validate .
@python3 -m unittest tests/test_plugin_catalog.py tests/test_install_built_wheel.py
@python3 -m unittest discover -s tests

detect-secrets-scan: ## Regenerate secrets baseline
@uv tool run $(DETECT_SECRETS_SPEC) scan \
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ Rust crates are owned by the top-level workspace in `Cargo.toml`; all Python dis

Testing spans two repositories:

- **Unit tests**: within each plugin's own directory — pure Python in `plugins/python/<slug>/tests/`, Rust inline via `mod tests`, and Python binding tests for Rust packages under their plugin directory
- **Plugin-framework integration tests**: `plugins/tests/<slug>/` for pure-Python plugins and the Rust plugin's own `tests/` directory — test framework discovery, loading, and hook dispatch (`make test-integration`)
- **Core behavior tests**: pure Python in `plugins/python/<slug>/tests/`; Rust inline via `mod tests`
- **Binding and hook tests**: `plugins/tests/<slug>/` for existing plugins; generated plugins start with local Python hook smoke tests
- **Repository tooling tests**: `tests/` — catalog behavior, wheel installation, and security policies
- **Gateway integration tests**: `mcp-context-forge/tests/integration/` — test plugin integration with the full gateway
- **E2E tests**: `mcp-context-forge/tests/e2e/` — test complete workflows with plugins

Unit tests live in each plugin's own directory. Plugin-framework integration tests live under `plugins/tests/<slug>/` for pure-Python plugins and in the plugin-local `tests/` directory for Rust plugins. Gateway integration and E2E tests live in `mcp-context-forge`.

See [TESTING.md](TESTING.md) for detailed testing guidelines and cross-repository coordination.
Test meaningful decisions and boundary behavior at the layer that owns them. See [TESTING.md](TESTING.md) for the keep/remove criteria and commands.

## Plugin Development

Expand Down Expand Up @@ -99,7 +98,7 @@ This interactive tool will:
- Python package files (`__init__.py`, `plugin.py`)
- Build configuration (`Cargo.toml`, `pyproject.toml`, `Makefile`)
- Documentation (`README.md`)
- Comprehensive unit tests (Python and Rust)
- Rust configuration tests and Python hook smoke tests
- Benchmark scaffolding

For non-interactive mode:
Expand Down
Loading
Loading