test(tools): add unit tests for the four GitLab tools#1003
test(tools): add unit tests for the four GitLab tools#1003muddlebee merged 9 commits intoTracer-Cloud:mainfrom
Conversation
Covers BaseToolContract metadata, is_available (connection_verified + project_id required), extract_params (full mapping + ref_name default to main), and run() across the three observable paths: unavailable when _resolve_config returns None, happy path returning the integration response under `commits`, and the empty-list graceful path the integration uses for non-200 responses. Refs Tracer-Cloud#993.
Covers BaseToolContract metadata, is_available (project_id and file_path both required), extract_params, and the four run() branches the tool explicitly handles: missing config, oversized files (>50_000 bytes), binary files that cannot be UTF-8 decoded, and the happy path that base64-decodes textual content. Refs Tracer-Cloud#993.
Covers BaseToolContract metadata, is_available, extract_params (full mapping + target_branch default to main), and the three run() paths: unavailable when _resolve_config returns None, happy path returning the integration MR list under `mrs`, and the empty-list graceful path. Refs Tracer-Cloud#993.
Covers BaseToolContract metadata, is_available, extract_params (full mapping + ref default to main + status default to failed), and the three run() paths: unavailable when _resolve_config returns None, happy path returning the integration pipeline list under `pipelines`, and the empty-list graceful path. Refs Tracer-Cloud#993.
Greptile SummaryAdds unit tests for all four GitLab tools ( Confidence Score: 5/5Safe to merge — all findings are P2 style suggestions with no correctness impact. The tests are well-structured, patch targets are correct, and error-message assertions align with the actual implementation strings. The only gap is a missing default-ref test in the file tool, which is a symmetry/style concern and does not affect correctness or runtime behaviour. tests/tools/test_gitlab_file_tool.py — missing default ref test for completeness Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
subgraph "Each GitLab tool test file"
A["BaseToolContract: metadata + schema + is_available smoke"] --> B["is_available: connection_verified + project_id gating"]
B --> C["extract_params: field mapping + branch defaults"]
C --> D["run - config missing: available=False"]
C --> E["run - happy path: patches _resolve_config + get_gitlab_*: available=True"]
C --> F["run - empty result: integration returns list: available=True"]
end
subgraph "GitLabFileTool extras"
E --> G["oversized file size > 50000: available=False"]
E --> H["binary file UnicodeDecodeError: available=False"]
E --> I["empty content string: available=True"]
end
Reviews (1): Last reviewed commit: "test(tools): add unit tests for GitLabPi..." | Re-trigger Greptile |
| "gitlab": { | ||
| "connection_verified": True, | ||
| "project_id": "42", | ||
| "file_path": "src/main.py", | ||
| "ref_name": "develop", | ||
| "gitlab_url": "https://gitlab.example.com", | ||
| "gitlab_token": "glpat-test", | ||
| } | ||
| } | ||
| ) | ||
| params = rt.extract_params(sources) | ||
| assert params["project_id"] == "42" | ||
| assert params["file_path"] == "src/main.py" |
There was a problem hiding this comment.
All three sibling tools include a "defaults X to main" test for their branch parameter (test_extract_params_defaults_ref_name_to_main, test_extract_params_defaults_target_branch_to_main, test_extract_params_defaults_ref_to_main), but the file tool has no equivalent. _get_gitlab_file_extract_params maps gl.get("ref_name", "main") → ref, so when ref_name is absent the default silently stays "main". A test asserting that behaviour would complete the pattern and catch a future threshold change.
def test_extract_params_defaults_ref_to_main() -> None:
rt = get_gitlab_file_contents.__opensre_registered_tool__
sources = mock_agent_state(
{"gitlab": {"connection_verified": True, "project_id": "42", "file_path": "src/main.py"}}
)
params = rt.extract_params(sources)
assert params["ref"] == "main"There was a problem hiding this comment.
Good catch — added in 44f6b29. The new test_extract_params_defaults_ref_to_main mirrors the per-tool default-branch tests already present for the other three GitLab tools and locks the ref_name → ref = main default.
Mirrors the per-tool default-branch tests already present for the other three GitLab tools (`test_extract_params_defaults_ref_name_to_main`, `test_extract_params_defaults_target_branch_to_main`, `test_extract_params_defaults_ref_to_main`). Locks `_get_gitlab_file_extract_params` to the documented `ref_name` → `ref` = `main` default so a future threshold change shows up in this file too. Refs Tracer-Cloud#993 (response to Greptile review).
|
Verified locally: Confirmed all 4 GitLab tools (list_gitlab_commits, get_gitlab_file_contents, list_gitlab_mrs, list_gitlab_pipelines) had no existing tests before this PR |
yashksaini-coder
left a comment
There was a problem hiding this comment.
Two issues found — one is a real production bug, the other is a test gap that hides it.
| "updated_after": "2026-01-01T00:00:00Z", | ||
| } | ||
| } | ||
| ) |
There was a problem hiding this comment.
This test exposes a production bug in GitLabMRsTool that needs to be fixed first.
In the production code (app/tools/GitLabMRsTool/__init__.py line 18), extract_params does:
"updated_after": gl["updated_after"],That uses a hard dict key access. If updated_after is missing from sources — which is totally possible since it's not a required field in is_available — this raises a KeyError at runtime and crashes the agent.
The sibling GitLabCommitsTool does it correctly: gl.get("since", ""). Same fix needed here: change to gl.get("updated_after", "") in the production file.
Every test in this file always provides updated_after, so the KeyError is never triggered by the test suite.
Quick fix in GitLabMRsTool/__init__.py:
"updated_after": gl.get("updated_after", ""),Then add a test calling extract_params without updated_after to verify it defaults to "".
There was a problem hiding this comment.
Good catch — fixed in fc55632, default test pinned in 4c69f13. Production code now uses gl.get("updated_after", "") to match the GitLabCommitsTool gl.get("since", "") convention, and the new test_extract_params_defaults_updated_after_to_empty_string exercises the missing-field path. HEAD is 5c545ce.
| "project_id": "42", | ||
| "updated_after": "2026-01-01T00:00:00Z", | ||
| } | ||
| } |
There was a problem hiding this comment.
Same production bug as in GitLabMRsTool — _list_gitlab_pipelines_extract_params in app/tools/GitLabPipelinesTool/__init__.py line 18 also uses gl["updated_after"] (hard key access) instead of gl.get("updated_after", "").
Every test here provides updated_after, so the KeyError is never triggered.
Fix the production code (gl.get("updated_after", "")) and add a test without updated_after in sources to confirm it defaults gracefully.
There was a problem hiding this comment.
Same fix applied here — 4dd8f64 defaults updated_after to "" and 5c545ce adds the missing-field test. HEAD is 5c545ce.
Sources may omit updated_after — it is not required by is_available — so
gl["updated_after"] raises KeyError at runtime. Match the GitLabCommitsTool
convention of gl.get("since", "") and default to an empty string.
Refs Tracer-Cloud#1003
Pin the new gl.get("updated_after", "") default so the regression cannot come
back silently. Mirrors the pattern used by the target_branch default test.
Refs Tracer-Cloud#1003
…ct_params Same hard-key-access bug as GitLabMRsTool: sources may omit updated_after since it is not enforced by is_available, and the dict access raises KeyError at runtime. Default to "" to match the GitLabCommitsTool convention. Refs Tracer-Cloud#1003
…ct_params
Pin the new gl.get("updated_after", "") default so the regression cannot come
back silently. Mirrors the pattern used by the ref default test.
Refs Tracer-Cloud#1003
yashksaini-coder
left a comment
There was a problem hiding this comment.
Both issues are properly addressed — production code fixed and tests added.
Production fixes:
GitLabMRsTool:gl["updated_after"]→gl.get("updated_after", "")✅GitLabPipelinesTool: same fix ✅
New tests:
test_extract_params_defaults_updated_after_to_empty_stringin both MRs and Pipelines tool tests — directly catches the KeyError regression ✅
Verified locally: 52 tests pass across all 4 tool test files, ruff clean, mypy clean, CI all green. Good to merge.
|
LGTM 👍 |
Fixes #993.
Describe the changes you have made in this PR -
Adds direct unit tests for the four GitLab tools that previously had no coverage under
tests/tools/:tests/tools/test_gitlab_commits_tool.py—list_gitlab_commitstests/tools/test_gitlab_file_tool.py—get_gitlab_file_contentstests/tools/test_gitlab_mrs_tool.py—list_gitlab_mrstests/tools/test_gitlab_pipelines_tool.py—list_gitlab_pipelinesEach file follows the same shape as
tests/tools/test_github_commits_tool.py:BaseToolContract— pulls metadata, schema, and basicis_available/extract_paramsinvariants fromtests/tools/conftest.py.is_available— covers theconnection_verified+project_id(andfile_pathfor the file tool) gating, plus the empty-state fallthrough.extract_params— asserts each field flows fromsources["gitlab"]to the tool input dict, including the per-tool defaults (ref_name→main,target_branch→main,ref→main,status→failed).runhappy path — patches_resolve_configand the correspondingapp.integrations.gitlab.get_gitlab_*helper to return a fake response, and verifies the tool returnsavailable=Truewith the expected key (commits,mrs,pipelines, orfile).runerror path — patches_resolve_configto returnNoneand verifies the tool returns the documentedavailable=Falsepayload (and an empty-list path for the list tools, mirroring how_request_jsoncoerces non-200 responses to[]). For the file tool, also covers the oversize-guard and the binary-file (UnicodeDecodeError) branches.Each commit is atomic — one file per commit — to keep review focused per tool.
Demo/Screenshot for feature changes and bug fixes -
Local quality gate (clean tree, run from
make):make lint—All checks passed!make format-check—908 files already formattedmake typecheck—Success: no issues found in 391 source filesmake test-cov—3363 passed, 2 skipped, 1 xfailed(the four new test files contribute 49 cases)Code Understanding and AI Usage
Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?
Explain your implementation approach:
I followed the existing GitHub-tool test pattern (
tests/tools/test_github_commits_tool.py) so reviewers see one shape repeated four times. The mocking strategy patches the two public surfaces the tool actually depends on —_resolve_config()and the matchingget_gitlab_*integration helper — instead of patchinghttpxdirectly. That keeps the tests honest to the tool boundary: the tool is responsible for shaping the result dict and gating on a missing config, while the integration layer (already covered separately byapp/integrations/gitlab_test.py, tracked by #901) is responsible for HTTP semantics. Where the tool has its own branches that aren't pure pass-through — the file tool's 50_000-byte size guard and the UTF-8 decode guard — I added explicit cases so a future change to those thresholds breaks the test rather than the runtime.I also kept
BaseToolContractas the first thing in each file because it doubles as a "did the@tooldecorator wire up correctly" smoke test; if the metadata oris_availablesignature drifts, the contract checks fail before any tool-specific assertion runs.Checklist before requesting a review