Skip to content

test: extract mock fixtures - #28

Merged
loadinglucian merged 5 commits into
mainfrom
test/extract-mock-fixtures
Oct 5, 2025
Merged

test: extract mock fixtures#28
loadinglucian merged 5 commits into
mainfrom
test/extract-mock-fixtures

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Oct 5, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added an injectable CLI prompter service providing standard prompt types and cleaner terminal spacing.
  • Bug Fixes
    • CLI now respects explicitly empty string options and correctly handles boolean true/false options.
  • Refactor
    • All console input routed through the centralized prompter for consistent prompting behavior.
  • Tests
    • Added test fixtures and helpers (mock filesystem, SSH, prompter, test command) and unit/integration tests validating prompter behavior and spacing suppression.

Refactor TestHelpers.php to use dedicated mock classes for better maintainability.
- Extract inline anonymous classes to separate MockFilesystem and MockSSHService classes in fixtures/
- Add comprehensive docblocks with usage examples
- Organize functions into logical sections with comments
- Use match expressions for cleaner conditional logic
- Improve defaults and type safety throughout
Extract Laravel Prompts wrappers from ConsoleInputTrait into dedicated PrompterService.

- Add PrompterService with all prompt methods and spacing suppression
- Update ConsoleInputTrait to delegate to injected PrompterService
- Inject PrompterService into BaseCommand constructor
- Add MockPrompter fixture for testing
- Update all tests to use non-interactive options and mock prompter
- Add unit tests for PrompterService ANSI suppression
@coderabbitai

coderabbitai Bot commented Oct 5, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Injects a new PrompterService into BaseCommand and console commands, refactors ConsoleInputTrait to delegate all prompt calls to the PrompterService, and adds test fixtures and helpers (MockPrompter, MockSSHService, MockFilesystem) plus unit/integration test updates to use the new prompter-based flow.

Changes

Cohort / File(s) Summary
Core prompting service
app/Services/PrompterService.php
New PrompterService wrapping Laravel Prompts methods (text, password, confirm, pause, select, multiselect, suggest, search, spin) and emitting ANSI spacing-suppression sequences before prompts.
Base command injection
app/Contracts/BaseCommand.php
Adds use Bigpixelrocket\DeployerPHP\Services\PrompterService; and injects PrompterService via constructor promotion as protected readonly PrompterService $prompter.
Trait refactor to service
app/Traits/ConsoleInputTrait.php
Replaces direct Laravel Prompts calls with $this->prompter->...; removes trait-level spacing suppression; updates getOptionOrPrompt to treat booleans as-is and consider non-null strings (including empty) as provided.
Test fixtures & helpers
tests/Fixtures/MockPrompter.php, tests/Fixtures/MockSSHService.php, tests/Fixtures/MockFilesystem.php, tests/Fixtures/TestConsoleCommand.php, tests/TestHelpers.php
Adds MockPrompter (queued responses), MockSSHService (configurable connect behavior, no I/O), MockFilesystem (in-memory with configurable errors), updates TestConsoleCommand to accept PrompterService, and adds multiple test helper factories (mockPrompter, mockSSHServiceWithBehavior, mockFilesystem, mockServerRepository, mockTestConsoleCommand, etc.).
Integration tests (server console)
tests/Integration/Console/Server/ServerAddCommandTest.php, .../ServerDeleteCommandTest.php, .../ServerListCommandTest.php
Update test constructors to pass mock PrompterService, adjust imports (ServerRepository), and add non-interactive options to avoid runtime prompts in tests.
Unit tests updated / added
tests/Unit/Contracts/BaseCommandTest.php, tests/Unit/Services/PrompterServiceTest.php, tests/Unit/Traits/ConsoleInputTraitTest.php, tests/Unit/TestHelpersTest.php
BaseCommandTest now provides PrompterService; new PrompterServiceTest verifies spacing suppression and spin behavior; ConsoleInputTraitTest adjusted for empty-string handling and removed ANSI assertions; TestHelpersTest data updated paths.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Command as Console Command
  participant Trait as ConsoleInputTrait
  participant Prompter as PrompterService
  participant LP as Laravel Prompts

  User->>Command: run
  Command->>Trait: getOptionOrPrompt(name, option)
  alt Option present (bool or non-null string)
    Trait-->>Command: return option
  else Option missing (null)
    Trait->>Prompter: promptX(label, ...)
    Prompter->>Prompter: emit spacing-suppression (ANSI)
    Prompter->>LP: call promptX(...)
    LP-->>Prompter: value
    Prompter-->>Trait: value
    Trait-->>Command: value
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • bigpixelrocket/deployer-php#26 — Related changes centralizing prompting; directly connected to adding PrompterService and refactoring prompt usage.
  • bigpixelrocket/deployer-php#27 — Related constructor/consumer changes to BaseCommand and console commands; overlaps on dependency injection updates.
  • bigpixelrocket/deployer-php#17 — Prior edits to BaseCommand; closely related because this diff further modifies BaseCommand constructor and injected services.

Poem

A whisk of prompts, a hop of cheer,
I queue the answers, far and near.
I hush the lines and spin with grace,
Mock tunnels set — no flashing space.
A rabbit’s patch: prompts in place. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title only references test fixture extraction but the changeset also introduces a new PrompterService, injects it into BaseCommand and console traits, and updates production code beyond tests, making the title misleading and too narrow. Please update the title to reflect the broader scope of changes, such as “feat: add PrompterService and extract mock fixtures,” or split the PR into two focused pull requests.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 82.35% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/extract-mock-fixtures

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
tests/Fixtures/MockFilesystem.php (1)

47-47: Initialize directories array conditionally for clearer intent.

The ternary operator used here works correctly, but the logic could be clearer. Consider using an if-statement or adding a comment to explain why .deployer is only added when throwOnMkdir is false.

-        $this->directories = $this->throwOnMkdir ? [] : ['.deployer'];
+        // Pre-populate default directory unless mkdir operations should fail
+        $this->directories = $this->throwOnMkdir ? [] : ['.deployer'];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9679b29 and efe5a02.

📒 Files selected for processing (14)
  • app/Contracts/BaseCommand.php (2 hunks)
  • app/Services/PrompterService.php (1 hunks)
  • app/Traits/ConsoleInputTrait.php (11 hunks)
  • tests/Fixtures/MockFilesystem.php (1 hunks)
  • tests/Fixtures/MockPrompter.php (1 hunks)
  • tests/Fixtures/MockSSHService.php (1 hunks)
  • tests/Fixtures/TestConsoleCommand.php (2 hunks)
  • tests/Integration/Console/Server/ServerAddCommandTest.php (16 hunks)
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php (2 hunks)
  • tests/Integration/Console/Server/ServerListCommandTest.php (2 hunks)
  • tests/TestHelpers.php (7 hunks)
  • tests/Unit/Contracts/BaseCommandTest.php (4 hunks)
  • tests/Unit/Services/PrompterServiceTest.php (1 hunks)
  • tests/Unit/Traits/ConsoleInputTraitTest.php (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 for all PHP code
Declare strict_types and write code assuming strict typing
Prefer PHP 8.x language features where appropriate (union types, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must have explicit return types, and use proper generics in types (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class), except value objects, DTOs, and pure data structures
In production code, access the container via constructor injection (no service locator or global access)
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments as visual separators with section headers/subheaders and avoid obvious or stale comments

Files:

  • app/Traits/ConsoleInputTrait.php
  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/Services/PrompterServiceTest.php
  • tests/Fixtures/MockPrompter.php
  • tests/Fixtures/MockFilesystem.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Fixtures/MockSSHService.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • app/Contracts/BaseCommand.php
  • app/Services/PrompterService.php
  • tests/TestHelpers.php
app/Traits/ConsoleInputTrait.php

📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)

app/Traits/ConsoleInputTrait.php: Add new input-gathering methods to ConsoleInputTrait and implement them using Laravel Prompts; methods should operate via $this->input
ConsoleInputTrait wrappers (promptText, promptPassword, promptConfirm, promptSelect, promptMultiselect, promptSuggest, promptSearch, promptPause, promptSpin) should call Laravel Prompts and suppress extra spacing

Files:

  • app/Traits/ConsoleInputTrait.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/Services/PrompterServiceTest.php
  • tests/Fixtures/MockPrompter.php
  • tests/Fixtures/MockFilesystem.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Fixtures/MockSSHService.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/TestHelpers.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, it's allowed to instantiate the Container directly for isolation
Exclude tests from PHPStan static analysis

tests/**/*.php: Write Pest tests using it() syntax
In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container
Use the DI container only in integration tests when verifying multiple services together or DI configuration
DI container conventions for production do not apply to tests
Apply the AAA pattern (Arrange, Act, Assert) in every test
For exception tests, combine phases with a // ACT & ASSERT comment when the act triggers the assertion
Organize tests with describe() blocks and shared setup via beforeEach(); extract helpers/traits for DRY tests
Keep test files under 1.8x the size of the source they test, without sacrificing readability
Focus tests on core business logic; avoid testing the framework itself
Use dataset-driven testing with ->with([]) for multiple scenarios
Eliminate overlap: avoid multiple tests covering the same functionality
Consolidate assertions using chained expectations (e.g., expect($x)->toBe(1)->and($y)->toBe(2))
Mock only external dependencies; do not perform real I/O in unit tests
Avoid performance tests unless performance is the primary concern
Forbidden in tests: toBeInstanceOf(...), toBeArray(), generic not->toBeNull(), expect(true)->toBeTrue(), and sleep(...) (use time mocking)
Prefer meaningful assertions on behavior and values (e.g., checking specific config values, validator outcomes, and configured mock interactions)
Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds
Integration tests: allow real file operations and external processes; cover CLI commands and full workflows
Do not run or gate tests on PHPStan compliance; ignore PHPStan issues in tests and avoid excessive phpdoc just for types

Files:

  • tests/Unit/Contracts/BaseCommandTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/Services/PrompterServiceTest.php
  • tests/Fixtures/MockPrompter.php
  • tests/Fixtures/MockFilesystem.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Fixtures/MockSSHService.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/TestHelpers.php
app/Contracts/BaseCommand.php

📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)

Keep BaseCommand limited to shared initialization, configuration, and orchestration logic; do not implement individual I/O operations here

Files:

  • app/Contracts/BaseCommand.php
🧠 Learnings (3)
📚 Learning: 2025-10-04T12:08:48.838Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.838Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : ConsoleInputTrait wrappers (promptText, promptPassword, promptConfirm, promptSelect, promptMultiselect, promptSuggest, promptSearch, promptPause, promptSpin) should call Laravel Prompts and suppress extra spacing

Applied to files:

  • app/Traits/ConsoleInputTrait.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/Services/PrompterServiceTest.php
  • app/Services/PrompterService.php
📚 Learning: 2025-10-04T12:08:48.838Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.838Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : Add new input-gathering methods to ConsoleInputTrait and implement them using Laravel Prompts; methods should operate via $this->input

Applied to files:

  • app/Traits/ConsoleInputTrait.php
  • app/Services/PrompterService.php
📚 Learning: 2025-10-04T12:08:48.838Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.838Z
Learning: Use laravel/prompts for all user interactions (text, password, confirm, select, multiselect, suggest, search, spin) rather than raw Symfony IO

Applied to files:

  • app/Traits/ConsoleInputTrait.php
  • app/Services/PrompterService.php
🧬 Code graph analysis (13)
app/Traits/ConsoleInputTrait.php (2)
app/Services/PrompterService.php (9)
  • text (33-51)
  • password (56-72)
  • confirm (77-93)
  • pause (98-103)
  • select (110-128)
  • multiselect (138-158)
  • suggest (165-187)
  • search (192-210)
  • spin (221-229)
tests/Fixtures/MockPrompter.php (9)
  • text (41-54)
  • password (59-71)
  • confirm (76-88)
  • pause (93-100)
  • select (107-120)
  • multiselect (130-144)
  • suggest (151-166)
  • search (171-184)
  • spin (195-201)
tests/Unit/Contracts/BaseCommandTest.php (5)
app/Services/PrompterService.php (1)
  • PrompterService (24-246)
app/Contracts/BaseCommand.php (1)
  • __construct (36-45)
tests/Fixtures/MockPrompter.php (1)
  • __construct (30-32)
tests/Fixtures/TestConsoleCommand.php (1)
  • __construct (32-41)
tests/TestHelpers.php (1)
  • mockPrompter (258-269)
tests/Unit/Traits/ConsoleInputTraitTest.php (1)
tests/Fixtures/TestConsoleCommand.php (2)
  • setTestMethod (46-50)
  • execute (61-92)
tests/Unit/Services/PrompterServiceTest.php (2)
app/Services/PrompterService.php (10)
  • PrompterService (24-246)
  • text (33-51)
  • password (56-72)
  • confirm (77-93)
  • pause (98-103)
  • select (110-128)
  • multiselect (138-158)
  • suggest (165-187)
  • search (192-210)
  • spin (221-229)
tests/Fixtures/MockPrompter.php (9)
  • text (41-54)
  • password (59-71)
  • confirm (76-88)
  • pause (93-100)
  • select (107-120)
  • multiselect (130-144)
  • suggest (151-166)
  • search (171-184)
  • spin (195-201)
tests/Fixtures/MockPrompter.php (4)
app/Services/PrompterService.php (10)
  • PrompterService (24-246)
  • text (33-51)
  • password (56-72)
  • confirm (77-93)
  • pause (98-103)
  • select (110-128)
  • multiselect (138-158)
  • suggest (165-187)
  • search (192-210)
  • spin (221-229)
app/Contracts/BaseCommand.php (1)
  • __construct (36-45)
tests/Fixtures/TestConsoleCommand.php (1)
  • __construct (32-41)
tests/Unit/Contracts/BaseCommandTest.php (1)
  • __construct (27-37)
tests/Fixtures/TestConsoleCommand.php (3)
app/Contracts/BaseCommand.php (1)
  • __construct (36-45)
tests/Fixtures/MockPrompter.php (1)
  • __construct (30-32)
tests/Unit/Contracts/BaseCommandTest.php (1)
  • __construct (27-37)
tests/Fixtures/MockSSHService.php (1)
app/Services/SSHService.php (1)
  • SSHService (40-327)
tests/Integration/Console/Server/ServerListCommandTest.php (3)
app/DTOs/ServerDTO.php (1)
  • ServerDTO (7-17)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-168)
  • loadInventory (31-43)
tests/TestHelpers.php (2)
  • mockSSHService (203-209)
  • mockPrompter (258-269)
tests/Integration/Console/Server/ServerAddCommandTest.php (3)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-168)
  • loadInventory (31-43)
tests/TestHelpers.php (2)
  • mockSSHService (203-209)
  • mockPrompter (258-269)
app/Console/Server/ServerAddCommand.php (1)
  • execute (50-202)
tests/Integration/Console/Server/ServerDeleteCommandTest.php (2)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-168)
  • loadInventory (31-43)
tests/TestHelpers.php (1)
  • mockPrompter (258-269)
app/Contracts/BaseCommand.php (1)
app/Services/PrompterService.php (1)
  • PrompterService (24-246)
app/Services/PrompterService.php (1)
tests/Fixtures/MockPrompter.php (9)
  • confirm (76-88)
  • multiselect (130-144)
  • password (59-71)
  • pause (93-100)
  • search (171-184)
  • select (107-120)
  • spin (195-201)
  • suggest (151-166)
  • text (41-54)
tests/TestHelpers.php (4)
tests/Fixtures/MockFilesystem.php (2)
  • MockFilesystem (28-132)
  • exists (53-83)
tests/Fixtures/MockPrompter.php (9)
  • MockPrompter (16-202)
  • text (41-54)
  • password (59-71)
  • confirm (76-88)
  • select (107-120)
  • multiselect (130-144)
  • suggest (151-166)
  • search (171-184)
  • pause (93-100)
tests/Fixtures/MockSSHService.php (1)
  • MockSSHService (27-87)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
🪛 GitHub Actions: Pint
app/Services/PrompterService.php

[error] 1-1: Pint PSR-12 style issue: no_extra_blank_lines, blank_line_between…

🪛 PHPMD (2.15.0)
tests/Fixtures/MockPrompter.php

43-43: Avoid unused parameters such as '$placeholder'. (undefined)

(UnusedFormalParameter)


44-44: Avoid unused parameters such as '$default'. (undefined)

(UnusedFormalParameter)


45-45: Avoid unused parameters such as '$required'. (undefined)

(UnusedFormalParameter)


46-46: Avoid unused parameters such as '$validate'. (undefined)

(UnusedFormalParameter)


47-47: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


61-61: Avoid unused parameters such as '$placeholder'. (undefined)

(UnusedFormalParameter)


62-62: Avoid unused parameters such as '$required'. (undefined)

(UnusedFormalParameter)


63-63: Avoid unused parameters such as '$validate'. (undefined)

(UnusedFormalParameter)


64-64: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


78-78: Avoid unused parameters such as '$default'. (undefined)

(UnusedFormalParameter)


79-79: Avoid unused parameters such as '$yes'. (undefined)

(UnusedFormalParameter)


80-80: Avoid unused parameters such as '$no'. (undefined)

(UnusedFormalParameter)


81-81: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


93-93: Avoid unused parameters such as '$message'. (undefined)

(UnusedFormalParameter)


109-109: Avoid unused parameters such as '$options'. (undefined)

(UnusedFormalParameter)


110-110: Avoid unused parameters such as '$default'. (undefined)

(UnusedFormalParameter)


111-111: Avoid unused parameters such as '$scroll'. (undefined)

(UnusedFormalParameter)


112-112: Avoid unused parameters such as '$validate'. (undefined)

(UnusedFormalParameter)


113-113: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


132-132: Avoid unused parameters such as '$options'. (undefined)

(UnusedFormalParameter)


133-133: Avoid unused parameters such as '$default'. (undefined)

(UnusedFormalParameter)


134-134: Avoid unused parameters such as '$scroll'. (undefined)

(UnusedFormalParameter)


135-135: Avoid unused parameters such as '$required'. (undefined)

(UnusedFormalParameter)


136-136: Avoid unused parameters such as '$validate'. (undefined)

(UnusedFormalParameter)


137-137: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


153-153: Avoid unused parameters such as '$options'. (undefined)

(UnusedFormalParameter)


154-154: Avoid unused parameters such as '$placeholder'. (undefined)

(UnusedFormalParameter)


155-155: Avoid unused parameters such as '$default'. (undefined)

(UnusedFormalParameter)


156-156: Avoid unused parameters such as '$scroll'. (undefined)

(UnusedFormalParameter)


157-157: Avoid unused parameters such as '$required'. (undefined)

(UnusedFormalParameter)


158-158: Avoid unused parameters such as '$validate'. (undefined)

(UnusedFormalParameter)


159-159: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


173-173: Avoid unused parameters such as '$options'. (undefined)

(UnusedFormalParameter)


174-174: Avoid unused parameters such as '$placeholder'. (undefined)

(UnusedFormalParameter)


175-175: Avoid unused parameters such as '$scroll'. (undefined)

(UnusedFormalParameter)


176-176: Avoid unused parameters such as '$validate'. (undefined)

(UnusedFormalParameter)


177-177: Avoid unused parameters such as '$hint'. (undefined)

(UnusedFormalParameter)


197-197: Avoid unused parameters such as '$message'. (undefined)

(UnusedFormalParameter)

tests/Fixtures/MockSSHService.php

35-35: Avoid unused parameters such as '$host'. (undefined)

(UnusedFormalParameter)


36-36: Avoid unused parameters such as '$port'. (undefined)

(UnusedFormalParameter)


37-37: Avoid unused parameters such as '$username'. (undefined)

(UnusedFormalParameter)


38-38: Avoid unused parameters such as '$privateKeyPath'. (undefined)

(UnusedFormalParameter)


47-47: Avoid unused parameters such as '$host'. (undefined)

(UnusedFormalParameter)


48-48: Avoid unused parameters such as '$port'. (undefined)

(UnusedFormalParameter)


49-49: Avoid unused parameters such as '$username'. (undefined)

(UnusedFormalParameter)


50-50: Avoid unused parameters such as '$command'. (undefined)

(UnusedFormalParameter)


51-51: Avoid unused parameters such as '$privateKeyPath'. (undefined)

(UnusedFormalParameter)


57-57: Avoid unused parameters such as '$host'. (undefined)

(UnusedFormalParameter)


58-58: Avoid unused parameters such as '$port'. (undefined)

(UnusedFormalParameter)


59-59: Avoid unused parameters such as '$username'. (undefined)

(UnusedFormalParameter)


60-60: Avoid unused parameters such as '$scriptPath'. (undefined)

(UnusedFormalParameter)


61-61: Avoid unused parameters such as '$privateKeyPath'. (undefined)

(UnusedFormalParameter)


67-67: Avoid unused parameters such as '$host'. (undefined)

(UnusedFormalParameter)


68-68: Avoid unused parameters such as '$port'. (undefined)

(UnusedFormalParameter)


69-69: Avoid unused parameters such as '$username'. (undefined)

(UnusedFormalParameter)


70-70: Avoid unused parameters such as '$localPath'. (undefined)

(UnusedFormalParameter)


71-71: Avoid unused parameters such as '$remotePath'. (undefined)

(UnusedFormalParameter)


72-72: Avoid unused parameters such as '$privateKeyPath'. (undefined)

(UnusedFormalParameter)


78-78: Avoid unused parameters such as '$host'. (undefined)

(UnusedFormalParameter)


79-79: Avoid unused parameters such as '$port'. (undefined)

(UnusedFormalParameter)


80-80: Avoid unused parameters such as '$username'. (undefined)

(UnusedFormalParameter)


81-81: Avoid unused parameters such as '$remotePath'. (undefined)

(UnusedFormalParameter)


82-82: Avoid unused parameters such as '$localPath'. (undefined)

(UnusedFormalParameter)


83-83: Avoid unused parameters such as '$privateKeyPath'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (30)
tests/Integration/Console/Server/ServerAddCommandTest.php (2)

18-33: LGTM!

The test helper correctly wires the new prompter dependency and passes all required dependencies to ServerAddCommand. The use of mockPrompter() ensures tests run non-interactively.


44-71: Confirmed CLI options coverage for non-interactive execution
ServerAddCommand defines all required options (--name, --host, --port, --username, --private-key-path, --skip, --yes); non-interactive runs will skip prompts.

tests/Unit/Traits/ConsoleInputTraitTest.php (2)

120-122: LGTM!

The note correctly reflects that spacing suppression is now handled by PrompterService internally, and MockPrompter outputs no ANSI sequences in tests. This aligns with the broader refactoring.


36-47: Confirm empty string handling in commands

ServerAddCommand uses default: '' for private-key-path and then normalizes '' to null, so providing --private-key-path='' yields the intended null value. Tests correctly reflect this behavior.

tests/Integration/Console/Server/ServerListCommandTest.php (1)

18-46: LGTM!

The test helper correctly wires the new prompter dependency, consistent with changes in other integration tests.

app/Traits/ConsoleInputTrait.php (3)

56-59: LGTM!

The updated boolean handling correctly treats both true and false as valid option values. This is more correct than the previous implementation which may have only considered true as provided.


87-103: LGTM!

All prompt wrapper methods correctly delegate to $this->prompter instead of calling global Laravel Prompts functions directly. This aligns with the broader refactoring to centralize prompt handling through PrompterService.

Based on learnings.


61-68: Empty string options are intentionally supported and correctly handled (e.g., --private-key-path='' → null in ServerAddCommand).

app/Contracts/BaseCommand.php (1)

11-11: LGTM!

The PrompterService dependency is correctly injected via constructor using property promotion, following Symfony DI best practices. This makes the prompter available to all commands extending BaseCommand and aligns with the trait's requirement for a $prompter property.

As per coding guidelines.

Also applies to: 42-42

tests/Fixtures/TestConsoleCommand.php (1)

12-12: LGTM!

The test fixture correctly accepts and propagates the PrompterService dependency to the parent BaseCommand constructor, maintaining consistency with the production code changes.

Also applies to: 38-40

tests/Unit/Services/PrompterServiceTest.php (2)

14-36: Verify test behavior in different terminal environments.

The test captures raw output and asserts ANSI escape sequences are present. This approach works for testing spacing suppression but may be fragile in different terminal environments or CI systems where TTY detection behaves differently.

Consider running this test in your CI environment to ensure it behaves consistently. If it fails intermittently, you may need to mock the output or adjust the test strategy.

Based on learnings.


78-102: LGTM!

The test correctly verifies that spin() does not call suppressPromptSpacing() and returns the callback result. The assertions check both the absence of ANSI sequences and the proper callback execution.

tests/Integration/Console/Server/ServerDeleteCommandTest.php (2)

8-8: LGTM!

The addition of the ServerRepository import eliminates the need for fully qualified names, improving readability.


38-44: LGTM!

The integration of mockPrompter() correctly wires the new PrompterService dependency into the command. Since all tests use --yes to bypass interactive prompts, the empty mock prompter is appropriate for these non-interactive test scenarios.

tests/Unit/Contracts/BaseCommandTest.php (2)

12-12: LGTM!

The PrompterService import and constructor updates correctly implement the new dependency injection pattern for BaseCommand testing.

Also applies to: 33-36


65-68: LGTM!

Both test cases correctly instantiate the mock prompter and wire it into the TestableBaseCommand. The empty mock is appropriate since these tests focus on command configuration and execution flow rather than prompt interactions.

Also applies to: 87-88

tests/Fixtures/MockSSHService.php (1)

1-87: LGTM!

The MockSSHService fixture is well-designed for testing SSH-dependent code paths without network I/O. The configurable connection behavior enables both success and failure scenarios.

Note: The PHPMD warnings about unused parameters are false positives. Mock implementations must preserve method signatures for substitutability, even when parameters aren't used internally.

app/Services/PrompterService.php (2)

23-229: LGTM!

The PrompterService provides a clean, injectable wrapper around Laravel Prompts functions. The design correctly:

  • Enables dependency injection and testability
  • Suppresses extra spacing for all interactive prompts (text, password, confirm, pause, select, multiselect, suggest, search)
  • Preserves spinner behavior by not suppressing spacing for spin()

Based on learnings.


240-245: LGTM!

The ANSI escape sequence implementation correctly addresses the extra newline that Laravel Prompts adds before each prompt. The approach is clean and maintains a consistent user experience.

tests/Fixtures/MockPrompter.php (1)

1-202: LGTM!

The MockPrompter fixture provides excellent test infrastructure:

  • Queue-based design enables deterministic testing with predefined responses
  • Clear error messages when queues are exhausted aid test debugging
  • The spin() implementation correctly executes callbacks without displaying spinners in tests
  • All methods maintain API compatibility with PrompterService

Note: PHPMD warnings about unused parameters are false positives—mock implementations must preserve method signatures for substitutability.

tests/TestHelpers.php (10)

13-15: LGTM!

The addition of fixture imports and section headers significantly improves code organization and adheres to the coding guidelines for comment-separated sections.

Also applies to: 21-23, 46-48, 100-102, 304-306, 344-346


50-73: LGTM!

The mockFilesystem() helper is well-designed with clear examples demonstrating both basic usage and error simulation scenarios.


75-98: LGTM!

The mockFilesystemService() helper correctly wraps mockFilesystem() to provide a higher-level service mock, maintaining consistency with the existing helper patterns.


155-171: LGTM!

The enhanced mockInventoryService() helper now accepts both array data (auto-converted to YAML) and raw YAML strings, providing flexibility for different testing scenarios. The match expression handles all cases cleanly.


192-210: LGTM!

The mockSSHService() helper provides a real SSHService instance with mocked dependencies, enabling tests that depend on SSHService without requiring network calls.


212-232: LGTM!

The mockSSHServiceWithBehavior() helper enables behavior-driven testing by allowing tests to simulate both successful and failed SSH connections. The clear examples demonstrate the intended usage patterns.


234-270: LGTM!

The mockPrompter() helper provides a clean factory function for creating MockPrompter instances with predefined prompt responses. The design enables deterministic testing of interactive command flows. The comprehensive examples demonstrate various usage patterns.


289-301: LGTM!

The match expression in mockVersionService() cleanly handles all constructor parameter combinations, improving upon the previous implementation.


328-342: LGTM!

The mockServerRepository() helper correctly accepts both array and string data (matching the updated mockInventoryService() signature) and returns a fully initialized repository ready for use in tests.


368-382: LGTM!

The mockTestConsoleCommand() helper now correctly wires the mockPrompter() dependency, completing the integration of the PrompterService across the test infrastructure.

Comment thread app/Services/PrompterService.php
Comment thread tests/Fixtures/MockFilesystem.php
Update exists() and getContents() to support basename matching alongside path suffix matching.

This allows more flexible mocking of files by name without requiring full path specification, improving test usability.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/Services/PrompterService.php (1)

1-246: Formatting issue already flagged.

The Pint/PSR-12 formatting violations have been identified in a previous review comment. Please address those style issues before merging.

🧹 Nitpick comments (3)
tests/Fixtures/MockFilesystem.php (2)

47-47: Document the magic .deployer directory initialization.

The default initialization of the .deployer directory is undocumented and may cause confusion. If tests depend on this directory existing by default, it should be documented in the class docblock or made explicit via a constructor parameter.

Consider either:

  1. Documenting this behavior in the class docblock with an explanation of why .deployer is special, or
  2. Removing the magic initialization and requiring tests to explicitly call mkdir('.deployer') when needed
+    /**
+     * @param bool $initialExists Whether the initial file should exist
+     * @param string $initialContent Content of the initial file
+     * @param bool $throwOnRead Simulate read permission errors
+     * @param bool $throwOnMkdir Simulate directory creation errors
+     * @param bool $throwOnDump Simulate write errors
+     * @param string $initialPath Path of the initial file
+     * 
+     * Note: The .deployer directory is pre-created by default unless throwOnMkdir is true.
+     */
     public function __construct(

69-74: Clarify the assumptions about stored file keys.

The matching logic assumes that keys in $this->files are simple filenames (e.g., 'config.yml') rather than paths with directories (e.g., 'config/app.yml'). The basename($files) === $storedPath check on line 71 would not work correctly if $storedPath contains directory separators.

Consider documenting this assumption in the class docblock or adding validation in the constructor/dumpFile():

 /**
  * Mock filesystem for testing with error simulation and in-memory storage.
  *
  * Simulates filesystem operations without touching disk, supporting:
  * - File existence checks with path matching
+ * - File keys should be simple filenames without directory separators
  * - File read/write operations

Or add validation when storing files:

 public function dumpFile(string $filename, mixed $content): void
 {
     if ($this->throwOnDump) {
         throw new IOException('Write failed', 0, null, $filename);
     }

+    // Store using basename for consistent key format
+    $key = basename($filename);
-    $this->files[$filename] = (string) $content;
+    $this->files[$key] = (string) $content;
 }
app/Services/PrompterService.php (1)

1-246: LGTM! Clean wrapper enabling DI and testability.

The implementation correctly wraps Laravel Prompts functions and centralizes spacing suppression as intended. The architecture is sound:

  • Enables dependency injection and mocking (as demonstrated by MockPrompter test fixture)
  • Consistent method signatures with proper type hints and docblocks
  • Correctly omits spacing suppression for spin() since spinners manage their own output
  • Follows coding guidelines (strict types, explicit return types, proper generics, section comments)

Optional nitpick: The docblock comment on line 236 uses informal language ("annoying newline"). While clear and accurate, a more professional tone like "Suppress the extra newline that Laravel Prompts inserts before prompts" would better align with typical API documentation standards.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efe5a02 and b559c61.

📒 Files selected for processing (2)
  • app/Services/PrompterService.php (1 hunks)
  • tests/Fixtures/MockFilesystem.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 for all PHP code
Declare strict_types and write code assuming strict typing
Prefer PHP 8.x language features where appropriate (union types, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must have explicit return types, and use proper generics in types (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class), except value objects, DTOs, and pure data structures
In production code, access the container via constructor injection (no service locator or global access)
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments as visual separators with section headers/subheaders and avoid obvious or stale comments

Files:

  • app/Services/PrompterService.php
  • tests/Fixtures/MockFilesystem.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Fixtures/MockFilesystem.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, it's allowed to instantiate the Container directly for isolation
Exclude tests from PHPStan static analysis

tests/**/*.php: Write Pest tests using it() syntax
In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container
Use the DI container only in integration tests when verifying multiple services together or DI configuration
DI container conventions for production do not apply to tests
Apply the AAA pattern (Arrange, Act, Assert) in every test
For exception tests, combine phases with a // ACT & ASSERT comment when the act triggers the assertion
Organize tests with describe() blocks and shared setup via beforeEach(); extract helpers/traits for DRY tests
Keep test files under 1.8x the size of the source they test, without sacrificing readability
Focus tests on core business logic; avoid testing the framework itself
Use dataset-driven testing with ->with([]) for multiple scenarios
Eliminate overlap: avoid multiple tests covering the same functionality
Consolidate assertions using chained expectations (e.g., expect($x)->toBe(1)->and($y)->toBe(2))
Mock only external dependencies; do not perform real I/O in unit tests
Avoid performance tests unless performance is the primary concern
Forbidden in tests: toBeInstanceOf(...), toBeArray(), generic not->toBeNull(), expect(true)->toBeTrue(), and sleep(...) (use time mocking)
Prefer meaningful assertions on behavior and values (e.g., checking specific config values, validator outcomes, and configured mock interactions)
Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds
Integration tests: allow real file operations and external processes; cover CLI commands and full workflows
Do not run or gate tests on PHPStan compliance; ignore PHPStan issues in tests and avoid excessive phpdoc just for types

Files:

  • tests/Fixtures/MockFilesystem.php
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.865Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : Add new input-gathering methods to ConsoleInputTrait and implement them using Laravel Prompts; methods should operate via $this->input
📚 Learning: 2025-10-04T12:08:48.865Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.865Z
Learning: Use laravel/prompts for all user interactions (text, password, confirm, select, multiselect, suggest, search, spin) rather than raw Symfony IO

Applied to files:

  • app/Services/PrompterService.php
📚 Learning: 2025-10-04T12:08:48.865Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.865Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : ConsoleInputTrait wrappers (promptText, promptPassword, promptConfirm, promptSelect, promptMultiselect, promptSuggest, promptSearch, promptPause, promptSpin) should call Laravel Prompts and suppress extra spacing

Applied to files:

  • app/Services/PrompterService.php
📚 Learning: 2025-10-04T12:08:48.865Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.865Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : Add new input-gathering methods to ConsoleInputTrait and implement them using Laravel Prompts; methods should operate via $this->input

Applied to files:

  • app/Services/PrompterService.php
🧬 Code graph analysis (1)
app/Services/PrompterService.php (1)
tests/Fixtures/MockPrompter.php (9)
  • confirm (76-88)
  • multiselect (130-144)
  • password (59-71)
  • pause (93-100)
  • search (171-184)
  • select (107-120)
  • spin (195-201)
  • suggest (151-166)
  • text (41-54)
🔇 Additional comments (1)
tests/Fixtures/MockFilesystem.php (1)

111-133: LGTM!

The mkdir() and dumpFile() methods correctly implement in-memory storage with appropriate error simulation. Ignoring the $mode parameter in mkdir() is acceptable for a test mock.

Replace hardcoded '.deployer' directory in MockFilesystem with dynamic extraction from the initial path parameter. Simplifies path matching logic by removing basename checks in favor of consistent path ending matches.

Changes:
- Extract parent directory from initialPath in MockFilesystem constructor
- Simplify exists() and readFile() methods to use consistent path matching
- Update mockFilesystem() default path from '.deployer/inventory.yml' to 'inventory.yml'
- Update test datasets to reflect new flexible directory handling

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/Fixtures/MockFilesystem.php (1)

69-107: Suffix matching still yields false positives.
The str_ends_with() fallback continues to report unrelated files (e.g., myinventory.yml) as present, so mocks can hide real bugs. Please normalize paths and require exact equality instead of suffix matching.

-        // Check files (direct match or path ends with stored key)
-        if (isset($this->files[$files])) {
-            return true;
-        }
-
-        foreach (array_keys($this->files) as $storedPath) {
-            if (str_ends_with($files, $storedPath)) {
-                return true;
-            }
-        }
+        if (isset($this->files[$files])) {
+            return true;
+        }
+
+        $normalizedQuery = $this->normalizePath($files);
+
+        foreach (array_keys($this->files) as $storedPath) {
+            if ($normalizedQuery === $this->normalizePath($storedPath)) {
+                return true;
+            }
+        }
@@
-        // Try direct match first
-        if (array_key_exists($filename, $this->files)) {
-            return $this->files[$filename];
-        }
-
-        // Try path ending match
-        foreach ($this->files as $storedPath => $content) {
-            if (str_ends_with($filename, $storedPath)) {
-                return $content;
-            }
-        }
+        if (array_key_exists($filename, $this->files)) {
+            return $this->files[$filename];
+        }
+
+        $normalizedFilename = $this->normalizePath($filename);
+
+        foreach ($this->files as $storedPath => $content) {
+            if ($normalizedFilename === $this->normalizePath($storedPath)) {
+                return $content;
+            }
+        }
+
+        throw new IOException("File does not exist: {$filename}", 0, null, $filename);
+    }
+
+    private function normalizePath(string $path): string
+    {
+        $normalized = str_replace('\\', '/', $path);
+
+        while (str_contains($normalized, '//')) {
+            $normalized = str_replace('//', '/', $normalized);
+        }
+
+        $normalized = rtrim($normalized, '/');
+
+        if (str_starts_with($normalized, './')) {
+            $normalized = substr($normalized, 2);
+        }
+
+        return $normalized;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b559c61 and 1eca423.

📒 Files selected for processing (3)
  • tests/Fixtures/MockFilesystem.php (1 hunks)
  • tests/TestHelpers.php (6 hunks)
  • tests/Unit/TestHelpersTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 for all PHP code
Declare strict_types and write code assuming strict typing
Prefer PHP 8.x language features where appropriate (union types, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must have explicit return types, and use proper generics in types (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class), except value objects, DTOs, and pure data structures
In production code, access the container via constructor injection (no service locator or global access)
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments as visual separators with section headers/subheaders and avoid obvious or stale comments

Files:

  • tests/Fixtures/MockFilesystem.php
  • tests/Unit/TestHelpersTest.php
  • tests/TestHelpers.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Fixtures/MockFilesystem.php
  • tests/Unit/TestHelpersTest.php
  • tests/TestHelpers.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, it's allowed to instantiate the Container directly for isolation
Exclude tests from PHPStan static analysis

tests/**/*.php: Write Pest tests using it() syntax
In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container
Use the DI container only in integration tests when verifying multiple services together or DI configuration
DI container conventions for production do not apply to tests
Apply the AAA pattern (Arrange, Act, Assert) in every test
For exception tests, combine phases with a // ACT & ASSERT comment when the act triggers the assertion
Organize tests with describe() blocks and shared setup via beforeEach(); extract helpers/traits for DRY tests
Keep test files under 1.8x the size of the source they test, without sacrificing readability
Focus tests on core business logic; avoid testing the framework itself
Use dataset-driven testing with ->with([]) for multiple scenarios
Eliminate overlap: avoid multiple tests covering the same functionality
Consolidate assertions using chained expectations (e.g., expect($x)->toBe(1)->and($y)->toBe(2))
Mock only external dependencies; do not perform real I/O in unit tests
Avoid performance tests unless performance is the primary concern
Forbidden in tests: toBeInstanceOf(...), toBeArray(), generic not->toBeNull(), expect(true)->toBeTrue(), and sleep(...) (use time mocking)
Prefer meaningful assertions on behavior and values (e.g., checking specific config values, validator outcomes, and configured mock interactions)
Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds
Integration tests: allow real file operations and external processes; cover CLI commands and full workflows
Do not run or gate tests on PHPStan compliance; ignore PHPStan issues in tests and avoid excessive phpdoc just for types

Files:

  • tests/Fixtures/MockFilesystem.php
  • tests/Unit/TestHelpersTest.php
  • tests/TestHelpers.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-04T12:08:48.865Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : Add new input-gathering methods to ConsoleInputTrait and implement them using Laravel Prompts; methods should operate via $this->input
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Mock only external dependencies; do not perform real I/O in unit tests

Applied to files:

  • tests/Fixtures/MockFilesystem.php
🧬 Code graph analysis (2)
tests/Fixtures/MockFilesystem.php (1)
tests/Fixtures/MockSSHService.php (1)
  • __construct (29-32)
tests/TestHelpers.php (8)
tests/Fixtures/MockFilesystem.php (2)
  • MockFilesystem (28-137)
  • exists (58-88)
tests/Fixtures/MockPrompter.php (9)
  • MockPrompter (16-202)
  • text (41-54)
  • password (59-71)
  • confirm (76-88)
  • select (107-120)
  • multiselect (130-144)
  • suggest (151-166)
  • search (171-184)
  • pause (93-100)
tests/Fixtures/MockSSHService.php (1)
  • MockSSHService (27-87)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
app/Services/FilesystemService.php (2)
  • exists (41-44)
  • FilesystemService (27-106)
app/Services/InventoryService.php (1)
  • InventoryService (33-264)
app/Services/SSHService.php (1)
  • SSHService (40-327)
app/Services/PrompterService.php (8)
  • text (33-51)
  • password (56-72)
  • confirm (77-93)
  • select (110-128)
  • multiselect (138-158)
  • suggest (165-187)
  • search (192-210)
  • pause (98-103)

Comment on lines +21 to 30
'directory exists' => [true, 'content', 'config/app.yml', 'config', true, 'directory should exist'],
'directory with trailing slash' => [true, 'content', 'config/app.yml', 'config/', true, 'directory with trailing slash should exist'],
'existing file' => [true, 'content', 'inventory.yml', 'inventory.yml', true, 'existing file should exist'],
'non-existent file in existing dir' => [true, 'content', 'config/app.yml', 'config/missing.yml', false, 'non-existent file should not exist'],
'directory exists when file does not' => [false, '', 'config/app.yml', 'config', true, 'directory should exist'],
'non-existent file (no substring match)' => [false, '', 'inventory.yml', 'inventory.yml', false, 'non-existent file should not exist even if directory matches'],
'different path no substring match' => [false, '', 'config/app.yml', '/path/to/config/other.yml', false, 'non-existent file with directory substring should not exist'],
'direct path match' => [true, 'test', 'inventory.yml', 'inventory.yml', true, 'direct match should work'],
'path ending match' => [true, 'test', 'inventory.yml', '/path/to/inventory.yml', true, 'path ending match should work'],
'different path ending' => [true, 'test', 'inventory.yml', '/different/inventory.yml', true, 'different path ending should work'],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incorrect expectation encourages bogus suffix matches.
Expecting /different/inventory.yml to exist after seeding only inventory.yml diverges from Symfony's Filesystem (which requires the exact path) and hides regressions the new MockFilesystem should catch. Please align the expectation with real behavior.

-        'different path ending' => [true, 'test', 'inventory.yml', '/different/inventory.yml', true, 'different path ending should work'],
+        'different path ending' => [true, 'test', 'inventory.yml', '/different/inventory.yml', false, 'different full path should not match unless identical'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'directory exists' => [true, 'content', 'config/app.yml', 'config', true, 'directory should exist'],
'directory with trailing slash' => [true, 'content', 'config/app.yml', 'config/', true, 'directory with trailing slash should exist'],
'existing file' => [true, 'content', 'inventory.yml', 'inventory.yml', true, 'existing file should exist'],
'non-existent file in existing dir' => [true, 'content', 'config/app.yml', 'config/missing.yml', false, 'non-existent file should not exist'],
'directory exists when file does not' => [false, '', 'config/app.yml', 'config', true, 'directory should exist'],
'non-existent file (no substring match)' => [false, '', 'inventory.yml', 'inventory.yml', false, 'non-existent file should not exist even if directory matches'],
'different path no substring match' => [false, '', 'config/app.yml', '/path/to/config/other.yml', false, 'non-existent file with directory substring should not exist'],
'direct path match' => [true, 'test', 'inventory.yml', 'inventory.yml', true, 'direct match should work'],
'path ending match' => [true, 'test', 'inventory.yml', '/path/to/inventory.yml', true, 'path ending match should work'],
'different path ending' => [true, 'test', 'inventory.yml', '/different/inventory.yml', true, 'different path ending should work'],
'directory exists' => [true, 'content', 'config/app.yml', 'config', true, 'directory should exist'],
'directory with trailing slash' => [true, 'content', 'config/app.yml', 'config/', true, 'directory with trailing slash should exist'],
'existing file' => [true, 'content', 'inventory.yml', 'inventory.yml', true, 'existing file should exist'],
'non-existent file in existing dir' => [true, 'content', 'config/app.yml', 'config/missing.yml', false, 'non-existent file should not exist'],
'directory exists when file does not' => [false, '', 'config/app.yml', 'config', true, 'directory should exist'],
'non-existent file (no substring match)' => [false, '', 'inventory.yml', 'inventory.yml', false, 'non-existent file should not exist even if directory matches'],
'different path no substring match' => [false, '', 'config/app.yml', '/path/to/config/other.yml', false, 'non-existent file with directory substring should not exist'],
'direct path match' => [true, 'test', 'inventory.yml', 'inventory.yml', true, 'direct match should work'],
'path ending match' => [true, 'test', 'inventory.yml', '/path/to/inventory.yml', true, 'path ending match should work'],
'different path ending' => [true, 'test', 'inventory.yml', '/different/inventory.yml', false, 'different full path should not match unless identical'],
🤖 Prompt for AI Agents
In tests/Unit/TestHelpersTest.php around lines 21 to 30, the test case
"different path ending" incorrectly expects /different/inventory.yml to exist
when only inventory.yml was seeded; change the expected boolean from true to
false and update the assertion message to reflect that different path endings
should not match an exact file seed (aligning the mock with Symfony Filesystem
behavior).

@loadinglucian
loadinglucian merged commit 07b6c0d into main Oct 5, 2025
5 checks passed
@loadinglucian
loadinglucian deleted the test/extract-mock-fixtures branch October 5, 2025 18:48
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