test: integration coverage report, suite docs and harness isolation fix
This commit is contained in:
1 parent
ae6d83c3bd
commit
446ec11954
6 files changed
+342
-5
No files matched your search
@@ -21,7 +21,8 @@ message instead of skipping.
|
||||
- Gitea is configured through `GITEA__section__KEY` env vars (sqlite3,
|
||||
`INSTALL_LOCK`, random `SECRET_KEY`/`INTERNAL_TOKEN`/OAuth2 JWT secret,
|
||||
Actions, packages and OAuth2 enabled, mailer off, webhook allowed host list
|
||||
`*`, `MAX_RESPONSE_ITEMS=50`).
|
||||
`*`, local network migrations allowed so the instance can mirror itself, repository
|
||||
hook scripts editable, `MAX_RESPONSE_ITEMS=50`).
|
||||
- Readiness: polls `GET /api/v1/version` for up to 90 s. On failure the last 100
|
||||
log lines of the container are written to stderr.
|
||||
- Seeding: admin `aegis-admin` (random password) via `gitea admin user create`,
|
||||
@@ -50,3 +51,40 @@ message instead of skipping.
|
||||
`create_org`, `create_repo`, `unique`.
|
||||
- `regular_user` (session): non-admin user with its own full-scope token.
|
||||
- `unique_name` (function): a unique name derived from the test name.
|
||||
|
||||
## The suite
|
||||
|
||||
Tests call tools through the MCP server (an in-memory `Client` on `build_server`, run as
|
||||
the admin of the throwaway instance), so schema validation, the result envelope, the audit
|
||||
log and the executor are all exercised. State lives in `tmp_path`.
|
||||
|
||||
- `test_scenario_*.py`: happy paths with real state changes (org and team, repository
|
||||
files, branches and tags, issues, pull requests and reviews, releases and attachments,
|
||||
secrets and variables, webhooks, admin and sudo, notifications, current-user features,
|
||||
generic packages, and a workflow run on an `act_runner` container).
|
||||
- `test_sweep.py`: every one of the 538 tools is called once against a seeded instance
|
||||
(`sweep_seed.py` creates the data, `sweep_plan.py` builds arguments and expectations).
|
||||
Order: read-only first, then creates, updates and deletes (leaf resources first). A call
|
||||
passes with a 2xx, a status the swagger documents for that operation, or an entry of
|
||||
`ALLOWANCES` (each with a reason). All failing tools are listed at once.
|
||||
`test_sweep_isolated.py` covers the tools the sweep must not call.
|
||||
- `test_per_user_oauth.py`: the HTTP app with per-user OAuth, driven in process with a
|
||||
seeded OAuth store; calls run as the signed-in user, admin tools and private repositories
|
||||
of others are refused by Gitea, the static bearer still runs as admin.
|
||||
- `test_setup_live.py`: `aegis-gitea-mcp setup` against the instance (OAuth app, env file
|
||||
mode, idempotence, non-admin token).
|
||||
- `tests/test_sweep_plan.py` (no Docker) keeps the sweep plan consistent with the catalog.
|
||||
|
||||
`AEGIS_IT_WRITE_REPORT=1` makes the session write `docs/integration-coverage.md` (per
|
||||
domain: tools with a 2xx, documented non-2xx statuses, allowances).
|
||||
|
||||
### Actions runner
|
||||
|
||||
`start_runner` starts `gitea/act_runner` (digest-pinned in `ACT_RUNNER_IMAGE`) on the run's
|
||||
network, registered with a token from `gitea_actions_repo_create_runner_registration_token`,
|
||||
label `aegis-host:host`. Jobs run inside the runner container itself: no Docker socket, no
|
||||
bind mounts. The runner reaches Gitea by container name, so it works in both network modes.
|
||||
It is removed with the rest of the run. If it cannot start or come online, the test is
|
||||
marked xfail with the reason; a failure after the run completed is a real failure.
|
||||
|
||||
Runtime: about two minutes locally (Gitea start 15 s, sweep 40 s, runner 10 s).
|
||||
@@ -127,7 +127,10 @@ def test_cleanup_removes_container_and_network() -> None:
|
||||
assert run_id not in harness._ACTIVE
|
||||
|
||||
|
||||
def test_cleanup_all_handles_registered_runs() -> None:
|
||||
def test_cleanup_all_handles_registered_runs(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# cleanup_all removes every tracked run, including the session's own Gitea, so this
|
||||
# test works on a private registry instead of the real one.
|
||||
monkeypatch.setattr(harness, "_ACTIVE", {})
|
||||
run_id = harness.secrets.token_hex(4)
|
||||
name = f"{harness.NAME_PREFIX}{run_id}"
|
||||
harness.register_resources(RunResources(run_id=run_id, container=name, network=name))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from tests.integration.conftest import McpSession, RegularUser, RepoRef, b64
|
||||
@@ -109,7 +110,7 @@ async def test_issue_lifecycle(
|
||||
found = await mcp.ok("gitea_issue_list_issues", q="widget", **repo)
|
||||
hits = [item["number"] for item in found.data]
|
||||
if index not in hits:
|
||||
time.sleep(0.5)
|
||||
await asyncio.sleep(0.5)
|
||||
assert index in hits
|
||||
assert (await mcp.ok("gitea_issue_search_issues", q="Broken widget")).data is not None
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import time
|
||||
from typing import Any
|
||||
@@ -204,7 +205,7 @@ async def test_repo_collaborators_fork_transfer_archive_search(
|
||||
search = await mcp.ok("gitea_repo_search", q=fresh_repo.name)
|
||||
found = names(search.data["data"])
|
||||
if fresh_repo.name not in found:
|
||||
time.sleep(0.5)
|
||||
await asyncio.sleep(0.5)
|
||||
assert fresh_repo.name in found
|
||||
|
||||
# Fork as another user (admin sudo), then list forks.
|
||||
|
||||
@@ -29,7 +29,9 @@ def sweep_context(gitea: GiteaInstance, regular_user: RegularUser) -> Ctx:
|
||||
return seed(gitea, regular_user.login, regular_user.token)
|
||||
|
||||
|
||||
async def test_every_tool_reaches_gitea(mcp: McpSession, sweep_context: Ctx) -> None:
|
||||
async def test_every_tool_reaches_gitea(
|
||||
mcp: McpSession, sweep_context: Ctx, gitea: GiteaInstance, regular_user: RegularUser
|
||||
) -> None:
|
||||
catalog = load_catalog()
|
||||
listed = {tool.name for tool in (await mcp.client.list_tools()).tools}
|
||||
expected = {op.tool_name for op in catalog.operations} | {RAW_TOOL}
|
||||
@@ -52,3 +54,20 @@ async def test_every_tool_reaches_gitea(mcp: McpSession, sweep_context: Ctx) ->
|
||||
assert not bad, f"{len(bad)} tool(s) with an unexpected result ({summary}):\n{report}"
|
||||
success_share = summary.get("success", 0) / len(records)
|
||||
assert success_share >= MIN_SUCCESS_SHARE, f"only {success_share:.0%} of tools got a 2xx"
|
||||
|
||||
# Whatever the 538 calls did, no credential or secret value came back in a result.
|
||||
hidden = [
|
||||
gitea.admin_token,
|
||||
gitea.admin_password,
|
||||
regular_user.token,
|
||||
regular_user.password,
|
||||
"rotated-org-value",
|
||||
"rotated-repo-value",
|
||||
"rotated-user-value",
|
||||
"org-secret-value",
|
||||
"repo-secret-value",
|
||||
"user-secret-value",
|
||||
]
|
||||
for value in hidden:
|
||||
assert all(value not in text for text in mcp.texts), "a secret leaked into a result"
|
||||
assert value not in mcp.audit_text(), "a secret leaked into the audit log"
|
||||
Reference in new issue
Block a user