Skip to content

refactor: add Guzzle injection to AnalyzeCommand with tests#55

Open
jordanpartridge wants to merge 1 commit intomasterfrom
refactor/analyze-command-testable
Open

refactor: add Guzzle injection to AnalyzeCommand with tests#55
jordanpartridge wants to merge 1 commit intomasterfrom
refactor/analyze-command-testable

Conversation

@jordanpartridge
Copy link
Contributor

@jordanpartridge jordanpartridge commented Mar 5, 2026

Summary

  • Refactored AnalyzeCommand to accept injected Guzzle Client via withMocks() pattern (matching CertifyCommand)
  • Added mockRepo and mockSha properties to bypass shell_exec calls in tests
  • Created tests/Unit/Commands/AnalyzeCommandTest.php with 15 tests covering all branches: validation (missing token, missing file, invalid JSON), API interaction (success with fixes, empty fixes, error responses, Guzzle exceptions), and configuration (env vars, option overrides)
  • Applied pint style fixes across touched files

Test plan

  • All 218 tests pass
  • Pint reports no style issues
  • AnalyzeCommand test covers: no token, no file, nonexistent file, invalid JSON, empty JSON array, successful API call with fixes, empty fixes, fix without suggestion, fix with missing type/file, missing minimal_report, invalid API response, Guzzle exception, env token, API URL override, mock repo/sha

Closes #48

Summary by CodeRabbit

  • Tests

    • Added comprehensive unit test coverage for command functionality, including validation scenarios, API interactions, error handling, and configuration precedence.
  • Refactor

    • Enhanced command testability through injectable dependencies and mock support.
    • Code cleanup and style improvements across multiple modules for better maintainability.

…tern

Make AnalyzeCommand testable by injecting HTTP client and mock values
for repo/sha detection, following the same withMocks() pattern used by
CertifyCommand. Add comprehensive test suite covering all branches:
validation, API interaction, configuration, and error handling.

Also applies pint style fixes across touched files.

Closes #48
@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

The changes refactor AnalyzeCommand to support dependency injection for testing by adding an injectable HTTP client and a withMocks() method. A comprehensive test suite covering validation, API interactions, error handling, and git detection is introduced. Supporting files receive minor formatting adjustments and cleanup.

Changes

Cohort / File(s) Summary
AnalyzeCommand Refactoring
app/Commands/AnalyzeCommand.php
Added three private properties (httpClient, mockRepo, mockSha) and a public withMocks() method to enable dependency injection for testing. The handle() method uses injected HTTP client if provided, otherwise creates a new Client. Request payload uses mock values when available, falling back to detectRepo()/detectSha() for production.
AnalyzeCommand Test Suite
tests/Unit/Commands/AnalyzeCommandTest.php
Comprehensive new unit test suite with 311 lines covering input validation (missing token, invalid files, malformed JSON), API interactions (responses with/without fixes, various fix structures), error handling (invalid responses, Guzzle exceptions), configuration precedence (API token and URL overrides), and repo/sha detection with mocked values.
Code Cleanup & Formatting
app/Commands/CertifyCommand.php, app/GitHub/ChecksClient.php, app/Services/PromptAssembler.php, app/Transformers/PhpStanPromptTransformer.php, app/Transformers/TestFailurePromptTransformer.php
Removed unused imports, adjusted object instantiation style, and minor formatting corrections in string concatenation and blank line placement. No functional behavior changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops through code with testing cheer,
Mocks injected, now so clear!
Commands dance with covered paths,
No more hardcoded HTTP baths!
From zero to a hundred strong,
This refactor rights what went wrong! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% 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 clearly describes the main change: adding Guzzle injection support to AnalyzeCommand along with corresponding tests, which is the primary objective of this PR.
Linked Issues check ✅ Passed All coding requirements from issue #48 are met: Guzzle injection via withMocks() method is added, mockRepo/mockSha properties enable testable git detection, and comprehensive test suite covering validation, API interaction, and fix output is implemented.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #48 objectives. Changes to CertifyCommand, ChecksClient, and Transformer classes are minor style/formatting fixes (Pint style corrections) mentioned in PR objectives as part of touched files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/analyze-command-testable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

📊 Coverage Report

Metric Coverage Threshold Status
Lines 72.7% 100%

Files Below Threshold

File Coverage Uncovered Lines
app/Commands/CheckCommand.php 0% 30, 31, 33, 34, 37... (+91 more)
app/Transformers/PhpStanPromptTransformer.php 3.6% 49, 51, 52, 53, 54... (+76 more)
app/Transformers/TestFailurePromptTransformer.php 11.7% 47, 68, 69, 71, 72... (+101 more)
app/Commands/AnalyzeCommand.php 88.3% 56, 58, 112, 113, 114... (+2 more)
app/Services/PromptAssembler.php 88.9% 55, 56, 57, 58, 120
app/GitHub/ChecksClient.php 89.1% 222, 223, 226, 227, 230... (+8 more)

🏆 Synapse Sentinel Gate

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

🔧 Synapse Sentinel: 2 checks need attention

The following issues must be resolved before this PR can be merged:


All tests passed.---

Security Audit


Review the output and fix any issues.

Quick Reference:

  • PHPStan errors → Fix type mismatches first, then missing types
  • Test failures → Read the assertion message, trace expected vs actual
  • Style issues → Run composer format to auto-fix

🤖 Generated by Synapse Sentinel - View Run

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/Commands/AnalyzeCommand.php (1)

71-74: Injected client path bypasses resolved API URL/timeout configuration.

When withMocks() provides a client, --api-url / PREFRONTAL_API_URL and timeout settings are ignored, which creates a behavior gap between test and production paths. Consider injecting a client factory/handler instead, so handle() still builds the request client from resolved config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Commands/AnalyzeCommand.php` around lines 71 - 74, The injected HTTP
client ($this->httpClient) in AnalyzeCommand::handle bypasses resolved
configuration (api URL and timeout) when withMocks() supplies a client; change
the constructor/injection to accept a client factory or handler (e.g., a
callable or HttpClientFactory) instead of a concrete Client so that handle()
always obtains a Client built with the resolved $apiUrl and timeout before use;
update uses of $this->httpClient to call the factory (e.g., $client =
($this->httpClientFactory)($apiUrl, $timeout) or a getClient($apiUrl, $timeout)
method) and keep withMocks() providing a factory that returns a mock client to
preserve test behavior while ensuring production path uses configured values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Unit/Commands/AnalyzeCommandTest.php`:
- Around line 30-75: Add a new test in
tests/Unit/Commands/AnalyzeCommandTest.php under the validation describe block
that exercises AnalyzeCommand::handle()'s unreadable-file branch: create a
temporary failures file (use $this->failuresFile), write valid JSON into it,
change its permissions to be unreadable (e.g., chmod 000), run
$this->artisan('analyze', ['--api-token' => 'test-token', '--failures' =>
$this->failuresFile]) and assert it outputs the exact "Could not read failures
file" message and fails; finally restore permissions/delete the temp file in
teardown so CI isn’t left with a locked file.
- Around line 16-19: The createCommand closure currently forces defaults
'owner/repo' and 'abc123', preventing detection-path tests from exercising
AnalyzeCommand::detectRepo() and detectSha(); change the call to
$command->withMocks to pass through the provided values (e.g.,
$command->withMocks($httpClient, $repo, $sha) or explicitly $repo ?? null, $sha
?? null) so tests can invoke ($this->createCommand)(null, null) to trigger
detection logic in AnalyzeCommand (and keep the closure signature and
app()->singleton usage unchanged).

---

Nitpick comments:
In `@app/Commands/AnalyzeCommand.php`:
- Around line 71-74: The injected HTTP client ($this->httpClient) in
AnalyzeCommand::handle bypasses resolved configuration (api URL and timeout)
when withMocks() supplies a client; change the constructor/injection to accept a
client factory or handler (e.g., a callable or HttpClientFactory) instead of a
concrete Client so that handle() always obtains a Client built with the resolved
$apiUrl and timeout before use; update uses of $this->httpClient to call the
factory (e.g., $client = ($this->httpClientFactory)($apiUrl, $timeout) or a
getClient($apiUrl, $timeout) method) and keep withMocks() providing a factory
that returns a mock client to preserve test behavior while ensuring production
path uses configured values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 05698d19-7722-4c42-9331-5593714ee130

📥 Commits

Reviewing files that changed from the base of the PR and between 5691a60 and 9ac1804.

📒 Files selected for processing (7)
  • app/Commands/AnalyzeCommand.php
  • app/Commands/CertifyCommand.php
  • app/GitHub/ChecksClient.php
  • app/Services/PromptAssembler.php
  • app/Transformers/PhpStanPromptTransformer.php
  • app/Transformers/TestFailurePromptTransformer.php
  • tests/Unit/Commands/AnalyzeCommandTest.php

Comment on lines +16 to +19
$this->createCommand = function (?Client $httpClient = null, ?string $repo = null, ?string $sha = null) {
$command = new AnalyzeCommand;
$command->withMocks($httpClient, $repo ?? 'owner/repo', $sha ?? 'abc123');
app()->singleton(AnalyzeCommand::class, fn () => $command);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

createCommand currently blocks testing real repo/SHA detection paths.

Line 18 always substitutes 'owner/repo' and 'abc123', so tests cannot exercise detectRepo() / detectSha() fallback behavior (HTTPS/SSH/no-remote, git/no-git).

🔧 Proposed fix
-    $this->createCommand = function (?Client $httpClient = null, ?string $repo = null, ?string $sha = null) {
+    $this->createCommand = function (
+        ?Client $httpClient = null,
+        ?string $repo = 'owner/repo',
+        ?string $sha = 'abc123'
+    ) {
         $command = new AnalyzeCommand;
-        $command->withMocks($httpClient, $repo ?? 'owner/repo', $sha ?? 'abc123');
+        $command->withMocks($httpClient, $repo, $sha);
         app()->singleton(AnalyzeCommand::class, fn () => $command);
     };

Then detection-path tests can call:

($this->createCommand)($httpClient, null, null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Unit/Commands/AnalyzeCommandTest.php` around lines 16 - 19, The
createCommand closure currently forces defaults 'owner/repo' and 'abc123',
preventing detection-path tests from exercising AnalyzeCommand::detectRepo() and
detectSha(); change the call to $command->withMocks to pass through the provided
values (e.g., $command->withMocks($httpClient, $repo, $sha) or explicitly $repo
?? null, $sha ?? null) so tests can invoke ($this->createCommand)(null, null) to
trigger detection logic in AnalyzeCommand (and keep the closure signature and
app()->singleton usage unchanged).

Comment on lines +30 to +75
describe('validation', function () {
it('fails when no API token is provided', function () {
putenv('PREFRONTAL_API_TOKEN');

$this->artisan('analyze')
->expectsOutputToContain('API token required')
->assertFailed();
});

it('fails when no failures file is provided', function () {
$this->artisan('analyze', ['--api-token' => 'test-token'])
->expectsOutputToContain('Failures file required')
->assertFailed();
});

it('fails when failures file does not exist', function () {
$this->artisan('analyze', [
'--api-token' => 'test-token',
'--failures' => '/tmp/nonexistent_file_'.uniqid().'.json',
])
->expectsOutputToContain('Failures file required')
->assertFailed();
});

it('fails when failures file contains invalid JSON', function () {
file_put_contents($this->failuresFile, 'not valid json');

$this->artisan('analyze', [
'--api-token' => 'test-token',
'--failures' => $this->failuresFile,
])
->expectsOutputToContain('Invalid JSON')
->assertFailed();
});

it('fails when failures file contains empty JSON array', function () {
file_put_contents($this->failuresFile, '[]');

$this->artisan('analyze', [
'--api-token' => 'test-token',
'--failures' => $this->failuresFile,
])
->expectsOutputToContain('Invalid JSON')
->assertFailed();
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unreadable failures-file branch is still untested.

AnalyzeCommand::handle() has a dedicated "Could not read failures file" path, but this validation block does not cover it.

🧪 Suggested test case
+        it('fails when failures path cannot be read', function () {
+            $this->artisan('analyze', [
+                '--api-token' => 'test-token',
+                '--failures' => sys_get_temp_dir(), // directory exists but is not a readable JSON file
+            ])
+                ->expectsOutputToContain('Could not read failures file')
+                ->assertFailed();
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Unit/Commands/AnalyzeCommandTest.php` around lines 30 - 75, Add a new
test in tests/Unit/Commands/AnalyzeCommandTest.php under the validation describe
block that exercises AnalyzeCommand::handle()'s unreadable-file branch: create a
temporary failures file (use $this->failuresFile), write valid JSON into it,
change its permissions to be unreadable (e.g., chmod 000), run
$this->artisan('analyze', ['--api-token' => 'test-token', '--failures' =>
$this->failuresFile]) and assert it outputs the exact "Could not read failures
file" message and fails; finally restore permissions/delete the temp file in
teardown so CI isn’t left with a locked file.

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.

Refactor and test AnalyzeCommand (coverage: 0% → 100%)

1 participant