Skip to content

refactor: abstract VCS interface, decouple reviewer from GitLab - #18

Merged
brucearctor merged 3 commits into
mainfrom
feat/vcs-abstraction
Jul 14, 2026
Merged

refactor: abstract VCS interface, decouple reviewer from GitLab#18
brucearctor merged 3 commits into
mainfrom
feat/vcs-abstraction

Conversation

@brucearctor

@brucearctor brucearctor commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces internal/vcs package with platform-agnostic types, fully decoupling the reviewer engine from GitLab-specific types. This is the foundation for GitHub/Bitbucket support.

Also addresses the CodeRabbit nit from PR #17 — consolidates repetitive flag/env tests into table-driven tests.

Architecture

cmd/main.go (composition root)
  ├── imports internal/gitlab  ← only place that knows about GitLab
  └── imports internal/reviewer
        └── uses internal/vcs types  ← platform-agnostic

New: internal/vcs/types.go

Platform-agnostic types that the reviewer engine uses:

  • MRChanges — file changes from a merge/pull request
  • DiffEntry — single file change
  • DiffVersion — point-in-time diff snapshot
  • Comment — comment on a PR/MR
  • InlineCommentRequest / InlineCommentPosition — inline comment payload

Changed: internal/reviewer/interfaces.go

VCSClient interface now uses vcs.* types:

type VCSClient interface {
    GetMRChanges(ctx, projectID, mrIID) (*vcs.MRChanges, error)
    PostNote(ctx, projectID, mrIID, body) (*vcs.Comment, error)
    CreateDiscussion(ctx, projectID, mrIID, vcs.InlineCommentRequest) error
    // ...
}

Changed: internal/gitlab/

  • types.go: Added toVCS() converter methods on MRChangesResponse, DiffVersion, Note
  • client.go: Public methods now return vcs.* types

Changed: internal/reviewer/

  • reviewer.go and output.go: Replaced all gitlab.* type references with vcs.*
  • The reviewer package no longer imports internal/gitlab

Changed: Tests

Verification

  • All 8 packages build cleanly
  • All tests pass with -race flag
  • Zero gitlab imports in internal/reviewer/ (verified via grep)

What This Enables (Future PRs)

// Adding GitHub support is now just:
type GitHubClient struct { /* ... */ }
func (c *GitHubClient) GetMRChanges(...) (*vcs.MRChanges, error) { /* ... */ }
func (c *GitHubClient) PostNote(...) (*vcs.Comment, error) { /* ... */ }
// ... implement VCSClient interface

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened configuration validation so invalid comment-mode and chunk-strategy values are rejected.
  • Refactor

    • Updated version-control integration to use shared, provider-neutral structures for merge request changes, diff versions, and inline comments/discussions.
  • Tests

    • Consolidated configuration flag/environment-variable checks into a single table-driven suite.
    • Added unit tests covering conversions for merge request changes, diff versions, and comments.

Introduce internal/vcs package with platform-agnostic types (MRChanges,
DiffVersion, Comment, InlineCommentRequest) that replace direct use of
gitlab.* types in the reviewer engine.

Architecture:
- internal/vcs: new package with platform-agnostic VCS types
- internal/reviewer: VCSClient interface now uses vcs.* types
- internal/gitlab: client methods return vcs.* types via toVCS() converters
- internal/gitlab: types.go retains JSON tags for API deserialization
- cmd/main.go: remains the only composition root that imports gitlab

The reviewer package no longer imports internal/gitlab. This enables
adding GitHub/Bitbucket support by implementing VCSClient against the
same vcs.* types.

Also consolidates repetitive flag/env config tests into table-driven
tests (CodeRabbit nit from PR #17).
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 01c249ae-a701-4999-94ec-4e08e5535afe

📥 Commits

Reviewing files that changed from the base of the PR and between 278a7a3 and a8112e8.

⛔ Files ignored due to path filters (2)
  • go.mod is excluded by !go.mod
  • go.sum is excluded by !**/*.sum, !go.sum
📒 Files selected for processing (1)
  • internal/gitlab/types_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/gitlab/types_test.go

📝 Walkthrough

Walkthrough

The change introduces shared VCS types, converts GitLab responses at the client boundary, updates reviewer contracts and tests, and consolidates configuration-loading tests with additional validation cases.

Changes

Shared VCS abstraction

Layer / File(s) Summary
Shared VCS contracts
internal/vcs/types.go, internal/reviewer/interfaces.go
Defines platform-agnostic types for changes, diff versions, comments, inline positions, and reviewer client methods.
GitLab type conversion
internal/gitlab/types.go, internal/gitlab/client.go, internal/gitlab/client_test.go, internal/gitlab/types_test.go
Converts GitLab API responses to VCS types and translates inline comment requests into GitLab discussion payloads, with conversion tests.
Reviewer VCS integration
internal/reviewer/output.go, internal/reviewer/reviewer.go, internal/reviewer/*_test.go
Updates posting, version selection, mocks, and incremental-review tests to use shared VCS types.

Configuration test consolidation

Layer / File(s) Summary
Configuration validation matrix
internal/config/config_test.go
Combines flag and environment option tests into a table-driven test and adds invalid comment-mode and chunk-strategy cases.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Reviewer
  participant GitLabClient
  participant GitLabAPI
  Reviewer->>GitLabClient: Request MR changes, versions, or notes
  GitLabClient->>GitLabAPI: Fetch GitLab response
  GitLabAPI-->>GitLabClient: Return GitLab response structs
  GitLabClient->>GitLabClient: Convert responses with toVCS()
  GitLabClient-->>Reviewer: Return shared VCS values
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactor: introducing a shared VCS interface and removing reviewer dependence on GitLab-specific types.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/vcs-abstraction

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/reviewer/reviewer_test.go (1)

1017-1019: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Local var vcs shadows the newly-imported vcs package.

Each of these tests declares vcs := &mockVCS{...}, which now shadows the package identifier vcs imported at Line 12 for the rest of the enclosing test function. It still compiles today because the initializer's vcs.DiffVersion reference is resolved before the new variable comes into scope, but it's a readability trap and blocks any later same-function use of the vcs package. internal/reviewer/output_test.go already renamed the equivalent local variable from vcs to mockClient for this exact reason — apply the same rename here for consistency.

♻️ Suggested rename (repeat for each occurrence)
-	vcs := &mockVCS{
-		mrVersions: []vcs.DiffVersion{{ID: 1, HeadSHA: "abc123", BaseSHA: "def456", StartSHA: "ghi789"}},
-	}
+	mockClient := &mockVCS{
+		mrVersions: []vcs.DiffVersion{{ID: 1, HeadSHA: "abc123", BaseSHA: "def456", StartSHA: "ghi789"}},
+	}

Then update the corresponding assertions (e.g. vcs.getMRVersionsCallsmockClient.getMRVersionsCalls) and the NewWithDiffSource(cfg, mm, vcs, ds) call site in each test.

Also applies to: 1081-1087, 1142-1146, 1232-1238

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/reviewer/reviewer_test.go` around lines 1017 - 1019, Rename each
local mockVCS variable named vcs in the affected tests to mockClient, including
the declarations at all listed occurrences. Update every corresponding
reference, assertion, and NewWithDiffSource argument to use mockClient while
preserving the imported vcs.DiffVersion package references.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/reviewer/reviewer_test.go`:
- Around line 1017-1019: Rename each local mockVCS variable named vcs in the
affected tests to mockClient, including the declarations at all listed
occurrences. Update every corresponding reference, assertion, and
NewWithDiffSource argument to use mockClient while preserving the imported
vcs.DiffVersion package references.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 822c1a06-9f03-4b03-a4f7-9dc1af6cfc34

📥 Commits

Reviewing files that changed from the base of the PR and between 2167d00 and e82549a.

📒 Files selected for processing (10)
  • internal/config/config_test.go
  • internal/gitlab/client.go
  • internal/gitlab/client_test.go
  • internal/gitlab/types.go
  • internal/reviewer/interfaces.go
  • internal/reviewer/output.go
  • internal/reviewer/output_test.go
  • internal/reviewer/reviewer.go
  • internal/reviewer/reviewer_test.go
  • internal/vcs/types.go

…rter tests

- Rename local 'vcs' variables to 'mockClient' in reviewer_test.go
  to avoid shadowing the vcs package import (consistency with
  output_test.go which already used this pattern)
- Add explicit unit tests for toVCS() converters in types_test.go:
  MRChangesResponse, DiffVersion, Note, plus empty-input edge case
  (gitlab coverage 77.7% → 78.2%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/gitlab/types_test.go (1)

8-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Convert to table-driven tests per path instructions.

All four test functions use repetitive sequential if/t.Errorf blocks instead of table-driven cases with t.Run subtests. Table-driven style would reduce duplication, name each assertion group as a labeled case, and let all sub-cases report independently on failure (e.g., separating MRChangesResponse.toVCS field checks from DiffEntry sub-checks, and giving TestMRChangesResponse_toVCS_Empty its own case within a shared table for toVCS).

As per path instructions, "Verify table-driven tests, proper cleanup, and race condition safety" for files matching **/*_test.go.

♻️ Example restructure for TestMRChangesResponse_toVCS
 func TestMRChangesResponse_toVCS(t *testing.T) {
-	resp := &MRChangesResponse{...}
-	got := resp.toVCS()
-	if got.ID != 42 { ... }
-	...
+	tests := []struct {
+		name string
+		resp *MRChangesResponse
+		want *vcs.MRChanges
+	}{
+		{
+			name: "renamed and new files",
+			resp: &MRChangesResponse{...},
+			want: &vcs.MRChanges{...},
+		},
+		{
+			name: "empty response",
+			resp: &MRChangesResponse{},
+			want: &vcs.MRChanges{Changes: []vcs.DiffEntry{}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.resp.toVCS()
+			if diff := cmp.Diff(tt.want, got); diff != "" {
+				t.Errorf("toVCS() mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/gitlab/types_test.go` around lines 8 - 154, Convert the four tests
around MRChangesResponse.toVCS, DiffVersion.toVCS, and Note.toVCS into
table-driven tests using named cases and t.Run subtests. Group related
assertions into labeled cases, including separate MRChangesResponse field and
DiffEntry checks, and include the empty-response scenario in the shared toVCS
table where appropriate. Preserve all existing coverage and expected values
while allowing independent subcase failures.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/gitlab/types_test.go`:
- Around line 8-154: Convert the four tests around MRChangesResponse.toVCS,
DiffVersion.toVCS, and Note.toVCS into table-driven tests using named cases and
t.Run subtests. Group related assertions into labeled cases, including separate
MRChangesResponse field and DiffEntry checks, and include the empty-response
scenario in the shared toVCS table where appropriate. Preserve all existing
coverage and expected values while allowing independent subcase failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d7018c53-38e3-4649-9338-454a17ef7175

📥 Commits

Reviewing files that changed from the base of the PR and between e82549a and 278a7a3.

📒 Files selected for processing (2)
  • internal/gitlab/types_test.go
  • internal/reviewer/reviewer_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/reviewer/reviewer_test.go

Address CodeRabbit nit: convert sequential if/Errorf assertions into
table-driven tests with t.Run subtests and cmp.Diff for cleaner output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant