ditrixnew/edt-mcp/e2e
Claude Code and agent skill by ditrixnew/edt-mcp
- community
Checking that the SKILL.md file is still in the repository.
How to write and run black-box end-to-end (e2e) tests for the EDT-MCP server. Covers the architecture (shared harness + one file per tool + orchestrator), the git-fixture isolation protocol, happy-path AND negative coverage, error-quality assertions, and the anti-cheat rules. An agent that has never seen this project should be able to write a correct, non-cheating test after reading this.
Install
npx vskill@latest install ditrixnew/edt-mcp/e2eSKILL.md
EDT-MCP E2E Testing — the complete guide
Audience: an agent (or human) who has never seen this project and is asked to write or run an e2e test for one MCP tool. Read this fully before writing a single line.
1. What you are testing (and what you are NOT)
The EDT-MCP server is a Java plugin that runs inside a live 1C:EDT IDE. It speaks MCP (JSON-RPC 2.0 over Streamable HTTP) on http://127.0.0.1:8765/mcp and exposes ~61 tools that drive a real 1C project (read code, write code, create/delete/rename metadata, debug, profile, render forms, etc.).
You are writing black-box end-to-end tests: a Python test process that is a real MCP client. It sends real tools/call requests to the real, running server and asserts on the real responses and the real on-disk effect.
- You are NOT mocking anything. No fakes, no stubs. The server, the EDT model, and the project files are all real.
- This is a different layer from the Java/JUnit unit tests. JUnit runs headless (no live EDT) and covers pure logic / schema / argument-validation. It physically cannot test what happens against a real loaded project. That is exactly the gap these e2e tests fill.
If a tool "succeeded" in its response but the project did not actually change on disk, that is a bug — and a test that only checks the response would miss it. So we verify the on-disk truth, see §4.
2. Architecture (how the suite is laid out)
- `harness.py` owns: the HTTP/JSON-RPC client (+ SSE framing), the git-fixture helpers, and all assertion helpers (including error-quality). You call its functions; you never re-implement them.
- `tools/test_<tool>.py` — one file per MCP tool. It contains the test functions for that one tool: the happy path(s) and the negative matrix. Because each agent owns one file, there are no merge conflicts when many agents work in parallel.
- `run_all.py` — discovers every
@e2e_testfunction, runs them one at a time (see §3 — execution is serial), resets the fixture before each, and emits a--junit-xmlreport.
Golden rule of parallelism: writing the test files can be parallelized across agents. Running them cannot — every test mutates the same TestConfiguration project and the same git working tree, so the orchestrator runs them serially with a hard reset between each.
3. The git-fixture isolation protocol (THE core mechanism)
TestConfiguration is a git-tracked 1C project committed in this repo (TestConfiguration/). That is our test fixture and our source of on-disk truth. The protocol for every test:
- Before the test — hard reset, never trust the previous test. The orchestrator calls
reset_fixture()=git reset(unstage) +git checkout HEAD --+git clean -fdonTestConfiguration/. ALL THREE are needed: metadata delete/rename persist to disk and can land STAGED in the index, which a plaingit checkout --(restores from the index) would NOT revert.checkout HEAD --restores from the commit;resetunstages;clean -fdremoves untracked files. - Run the tool (one or more
call(...)). - Assert the effect — the assertion depends on the tool kind (see §4): on-disk git-diff (file-write), MODEL read-back (metadata-write), or empty-diff (read).
- `kind="write-metadata"` tools get extra isolation (the orchestrator does this, not you): immediately after such a test it runs
reset_fixture()thenreset_model(). Why: these tools mutate EDT's in-memory BM model, EDT may async-autosave it to disk, and a git reset alone cannot undo an unsaved model change —reset_model()(callsclean_project, which refreshes the model from the just-reset clean disk) discards it before the next test. - After the whole run the final
git status TestConfiguration/MUST be empty. A run that leaves the project dirty is a failed run.
Metadata writes now persist to disk (since commit 71a48ba — fix-card metadata-writes-not-persisted-to-disk, see [[project_metadata_write_persistence]]). create_metadata / modify_metadata now forceExport the mutated top object(s) to .mdo after the write; delete_metadata/rename_metadata_object always did (via the refactoring service); write_module_source flushes .bsl synchronously. Since #406 the write tools that extend AbstractMetadataWriteTool also WAIT for that export QUEUE to drain before answering (rename_metadata_object and build_external_objects do not extend it and still lag). That wait is ordered with the export only when the SAME call submitted it. Since #408 the generic delete_metadata path submits too, but only for the CONTAINER of the deleted node (its Configuration.mdo / owner .mdo); the deleted object's OWN file cannot be resubmitted at all (EDT builds a save task by looking the FQN up in the transaction, and a deleted FQN yields none), so poll for that one. Where the call did submit, a drained queue means nothing of that call is still pending - it does NOT prove the bytes are right (EDT logs a per-file write failure and completes the computation anyway), and the wait is skipped where the export state cannot be observed. So still ASSERT the bytes; you just no longer need to wait for them. Consequence: verify a metadata-write BOTH ways — a MODEL READ-BACK (semantic: a read tool sees the change in the model) AND the on-disk structure. For a call that SUBMITTED its own export (create/modify, and the specialized delete branches) assert the disk directly (assert_disk_lacks / assert_disk_path_gone / read_disk) and do it BEFORE any other MCP round trip. Poll instead for a file whose export the call did NOT submit — for the generic delete_metadata path that is the deleted object's own file, while its container's file is asserted immediately (§ the comment in test_delete_metadata.py, and #408). rename_metadata_object / build_external_objects poll for the separate reason that they do not inherit the barrier at all. The orchestrator reverts the on-disk .mdo via reset_fixture (git) and re-syncs the model via reset_model().
4. How to verify "something really changed" (per tool type)
Tool kind (@e2e_test(kind=...)) | Examples | Happy-path assertion | |
|---|---|---|---|
| read | list_projects, read_module_source, get_metadata_details, search_in_code, find_references | response content correct AND assert_no_diff() (nothing changed on disk — the read-tool guardrail) | |
| write (file-write) | write_module_source | assert_diff_contains(...) — the change is on disk (synchronous .bsl flush) | |
| write-metadata | create / add / delete / rename metadata | MODEL READ-BACK + on-disk structure. (1) call(...) a read tool (get_metadata_objects / get_metadata_details / get_module_structure) and assert the change in the MODEL (object present/absent, attribute present, renamed). (2) Assert it landed on disk. When the CALL ITSELF submitted the export (create/modify, and the specialized delete branches - they call forceExportToDisk), the queue is already drained when it returns unless the export state was unobservable (§3), so assert DIRECTLY (assert_diff_contains / read_disk / assert_disk_lacks / assert_disk_path_gone) and do it BEFORE any other MCP round trip - an intervening call is time an early-returning build could use to finish, which is how the assertion stops testing anything. Poll for TWO different reasons: rename_metadata_object / build_external_objects do not extend that base at all, and the generic delete_metadata path submits only its CONTAINER's export — the deleted object's own file stays unsubmittable (see the comment in test_delete_metadata.py and #408). Assert the STRUCTURE, not just the name — e.g. "<name>E2EWeight</name>" / "<catalogs>Catalog.<name></catalogs>", never the bare name (which can false-match the parent's collection reference). For a delete (removal, not addition) check that the object file is gone AND that the collection reference is removed from Configuration.mdo; for a top-object delete assert Configuration.mdo immediately (the call submits that export since #408) and POLL for the object's own file (nobody but the refactoring can schedule its removal). For delete/rename cover preview (no mutation) + confirm. For rejected/preview calls, assert_no_diff(). | |
| action | clean_project, revalidate_objects, import/export_configuration, update_database | usually assert_no_diff() (they touch validation markers / the infobase DB / an external XML dump, NOT project files) + assert the status. Do NOT run a destructive happy mutation (import/update_database) that could corrupt the fixture — cover the error/sentinel matrix. | |
| env-dependent | get_form_screenshot / get_form_layout_snapshot (JVM render flag), translate_configuration / generate_translation_strings / get_translation_project_info (LanguageTool plugin), debug/profiling (no session/infobase in this env) | env-robust branch: assert the REAL observed contract — a structured result IF the dependency is present, OR a clear, actionable SENTINEL ("LanguageTool ... not available. Install ...", "no active debug session") if not. Both branches must be mutation-sensitive (a wrong/blank payload fails). | |
| round-trip ID | get_applications → launch, find_references → read_* | assert the returned ID is consumable by the sibling tool |
Ground truth depends on the tool: a file-write is proven by the git diff; a metadata-write by the model read-back (semantic) AND the on-disk structure (asserting the specific element - directly when the call submitted its own export, polled when it did not; see §3). Always assert WHAT changed (the structural element), never merely that a diff exists or that the bare name appears somewhere; and never use assert_no_diff() as the only assertion (it must accompany a real content/sentinel check).
5. Negative coverage is MANDATORY (not just happy paths)
For every tool, in addition to the happy path, cover the negative matrix:
- Invalid path / non-existent object — a
modulePath/fqn/objectNamethat does not exist; a non-existentprojectName. - Non-existent lines / ranges —
startLine/endLineout of range; anoldSource(searchReplace) that is not in the file. - Invalid parameter combinations — every XOR branch (both / neither of
modulePathvsobjectName), every conditional-required (formNamewhenmoduleType=FormModule,commandNamewhenmoduleType=CommandModule,oldSourcewhenmode=searchReplace), an invalidenumvalue. - Missing required parameter.
If a tool has XOR / conditional / enum parameters and you only tested the happy path, the test is incomplete.
5.1 Assert the QUALITY of the error (not just "it failed")
A negative test must assert that the error is a good error:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingtests/e2e/SKILL.md
Popularity
- Repository stars
- 223
- Repository forks
- 49
- 7-day trend score
- 27
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/ditrixnew/edt-mcp/e2e)