Skip to content

fix(php): clear both quarantined test groups, and the dead code they were hiding - #13

Closed
Snider wants to merge 3 commits into
mainfrom
dev
Closed

fix(php): clear both quarantined test groups, and the dead code they were hiding#13
Snider wants to merge 3 commits into
mainfrom
dev

Conversation

@Snider

@Snider Snider commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Clears both quarantined PHP test groups. Every test now either runs or is gone with a receipt — no markTestSkipped, no ->skip(), no testsuite <exclude> left in the repo.

Suite

failed passed skipped total
before 184 1112 6 1302
after 156 1155 0 1311

Zero regressions — failing-test names were diffed before and after; no test that passed at baseline fails now. Total rises by 9 because those 9 could not previously be collected at all.

Adjudication

Group 1 — <exclude>php/tests/Feature/Agentic/Livewire (3 files, 9 tests) → badly linked

The stated reason was true but shallow. Root cause: php/Agentic/ held eight files whose namespaces resolve one directory higher. Core\Mod\Agentic\ maps to php/, so Core\Mod\Agentic\Livewire\HubComponent had to live at php/Livewire/HubComponent.php and instead sat at php/Agentic/Livewire/HubComponent.php. Composer never loaded any of them. All three components extend HubComponent, so BrainExplorer, CreditLedger and FleetOverview have never been reachable in a running application.

The tests hid it: LivewireTestCase::livewireComponent() require_once'd the component file by path, and the two Feature/Agentic/Services tests carried their own loadAgenticPhpClass() doing the same. Both workarounds are gone; the files moved to php/Livewire, php/Data and php/Services, where their declared namespaces have always pointed. Their __DIR__-relative Blade paths moved with them.

The three tests are now PHPUnit classes. Pest raises TestCaseAlreadyInUse for any second uses() binding on a file, by class name, so a subclass is rejected just the same — class-style tests are not matched by uses()->in() at all, which is why the thirteen siblings in Feature/Livewire have always run.

Feature/Agentic: 15 passed (was 9 uncollectable + 2 failing).

Group 2 — the six skips → both fixable

ContentServiceTest, 5× markTestSkipped('Help article prompt not found'). ContentService resolves paths as base_path() plus a config prefix defaulting to app/Mod/Agentic/Resources/* — a host application path. Under Testbench base_path() is the bare skeleton, so the prompt could never exist: a permanent skip on every machine and in CI. Three more tests in the file failed for the same reason. The prefixes are config-driven, so the suite now points them at a sandbox and writes its own fixtures.

ContentServiceTest: 10 passed (was 2 passed / 3 failed / 5 skipped).

BatchContentGenerationTest ->skip('Alias mocking requires process isolation'). It alias-mocked Mod\Content\Models\ContentTaska namespace that exists nowhere. The real one is Core\Mod\Content\, shipped by dappcore/php-content, which was not a dependency of this package at all. Twenty production files imported the bare Mod\Content\*, so BatchContentGeneration::handle() and the MCP content tools would fatal on "Class not found" the moment a worker picked them up.

dappcore/php-content joins require-dev, matching how dappcore/php-tenant is already carried. The test now runs against a real content_tasks table — only that table, because the package migration also recreates prompts with columns stricter than this package's own.

Nothing was deleted. Nothing was adjudicated obsolete.

Three production bugs the phantom classes were hiding

Mocks of a non-existent class accept anything, so these tests asserted nothing:

  1. Template variables have never been substituted. ProcessContentTask::interpolateVariables() built its placeholder as '{{{'.$key.'}}}' — three braces. Every template writes {{name}}, and Prompt::interpolate() builds the same placeholder correctly as "{{{$key}}}", which is $key between two brace pairs. The concatenated form was a mis-transcription of it, so user templates reached the provider verbatim.
  2. Entitlement denials recorded no reason. $result->message is an undefined property read — EntitlementResult keeps its text in a readonly $reason behind getMessage(), and has no __get.
  3. Feature/Jobs/ProcessContentTaskTest passed a ContentProcessingService as handle()'s second argument — a signature this job has never had.

Feature/Jobs + Unit/ProcessContentTask: 70 passed, 3 failed (was 43 passed, 27 failed). The 3 are pre-existing and reach a live Qdrant/Elasticsearch.

Not in scope

The 156 remaining failures are pre-existing and unrelated to either group; CI keeps continue-on-error, and its comment is corrected here (it claimed 993/303).

The largest remaining cluster (25) is Core\Mcp\Tools\Concerns\ValidatesDependencies and Core\Mcp\Dependencies\HasDependencies. Both are real and live in dappcore/mcp — deliberately not wired here, because that package claims the Core\Mcp\ PSR-4 root this repo also maps onto php/Mcp/, with CircuitBreaker, ToolRegistry and CircuitOpenException existing on both sides. That is a namespace collision needing a decision on which copy is canonical, not a blind install. Follow-up work.

Two further findings filed separately: the three Livewire components are registered nowhere (no Boot, no route, no menu — they load, but nothing reaches them), and BatchContentGenerationTest's "does not dispatch any ProcessContentTask when collection is empty" reimplements the branch inline rather than calling the job.

🤖 Generated with Claude Code
Co-Authored-By: Virgil virgil@lethean.io

Summary by CodeRabbit

  • New Features

    • Added workspace credit balance, deduction, refund and transaction history capabilities.
    • Added agent session creation, state updates and event streaming support.
    • Added structured fleet statistics and credit transaction data handling.
  • Bug Fixes

    • Improved content prompt placeholder interpolation and entitlement error messages.
    • Corrected view resolution for Brain Explorer, credit ledger and fleet overview screens.
    • Updated content processing to use the current application structure.
  • Tests

    • Expanded automated coverage for content processing, credits, sessions and fleet interfaces.
    • Enabled previously excluded Livewire feature tests.

Snider and others added 3 commits August 8, 2026 09:38
php/Agentic/ held eight files whose namespaces resolve one directory
higher: Core\Mod\Agentic\ maps to php/, so Core\Mod\Agentic\Livewire\
HubComponent had to live at php/Livewire/HubComponent.php and instead sat
at php/Agentic/Livewire/HubComponent.php. Composer never loaded any of
them. The three component classes extend HubComponent, so every one of
them was fatal on load, and BrainExplorer, CreditLedger and FleetOverview
have never been reachable in a running application.

The tests hid it. LivewireTestCase::livewireComponent() require_once'd the
component file by path, and the two service tests carried their own
loadAgenticPhpClass() doing the same, so the classes were pulled in by
hand and the missing autoloading never showed. Both workarounds go; the
files are moved to php/Livewire, php/Data and php/Services, which is where
their declared namespaces have always pointed.

Their view paths moved with them: the components resolve Blade through
__DIR__.'/../../resources/views/...', which was correct from php/Agentic/
Livewire and is one level too deep from php/Livewire.

The three Feature/Agentic/Livewire tests were excluded in phpunit.xml
rather than fixed. Pest rejects a second uses() binding on a file no
matter the inheritance, so a file-level uses(LivewireTestCase::class)
under a directory-wide uses(TestCase::class) always throws
TestCaseAlreadyInUse. Class-style tests are not matched by uses()->in() at
all, which is why the thirteen sibling files in Feature/Livewire have
always run — these three are now written the same way. BrainExplorerTest
also fakes HTTP, because forgetting a memory posts a delete to Qdrant and
was reaching localhost:6334 for real.

Feature/Agentic: 15 passed, was 9 uncollectable plus 2 failing.

Co-Authored-By: Virgil <virgil@lethean.io>
Twenty production files imported Mod\Content\* — ContentTask, ContentBrief,
AIGatewayService, GenerateContentJob and the rest. No such namespace exists.
The real one is Core\Mod\Content\, shipped by dappcore/php-content, which
was not a dependency of this package at all. So BatchContentGeneration::
handle() would fatal on "Class not found" the moment a worker picked it up,
and the MCP content tools with it.

dappcore/php-content joins require-dev, matching how dappcore/php-tenant is
already carried here: it is a sibling module a host application supplies.
Its only new transitive dependency is ezyang/htmlpurifier.

Wiring the real classes in exposed three things the phantom ones had been
hiding:

interpolateVariables() built its placeholder as '{{{'.$key.'}}}' — three
braces. Every template in the package writes {{name}}, and Prompt::
interpolate() builds the same placeholder correctly as "{{{$key}}}", which
is $key between two brace pairs, not three. The concatenated form was a
mis-transcription of it, so no variable has ever been substituted and user
templates reached the provider verbatim.

An entitlement denial recorded "Entitlement denied: " and nothing else.
EntitlementResult keeps its text in a readonly $reason behind getMessage()
and has no $message property and no __get, so $result->message was always
an undefined property read.

The tests were written against the phantoms and had to be corrected, not
merely re-pointed: mocks of a non-existent class accept anything, so they
asserted nothing. ContentTask and Prompt doubles are makePartial() so real
Eloquent attribute handling runs; EntitlementResult and UsageRecord are the
real classes, since can() and recordUsage() are typed and reject look-alikes;
recordUsage()'s expectation is positional because a named-argument
expectation leaves $user unmatched. ProcessContentTaskTest also passed a
ContentProcessingService as handle()'s second argument — a signature this
job has never had — and Unit/ProcessContentTaskTest asserted the
three-brace placeholder.

BatchContentGenerationTest's remaining skip goes too. It alias-mocked
ContentTask to fake the static query(), which replaces the class for the
whole PHP process and is why it needed isolation; it now runs against a
real content_tasks table created for that test. Only that table, because
php-content's migration also recreates prompts with columns stricter than
this package's own.

Feature/Jobs + Unit/ProcessContentTask: 70 passed, 3 failed, was 43 passed,
27 failed. The 3 are pre-existing and reach a live Qdrant/Elasticsearch.

Co-Authored-By: Virgil <virgil@lethean.io>
Five tests in ContentServiceTest did nothing but markTestSkipped('Help
article prompt not found'), on every machine and in CI. The prompt was
never going to be there: ContentService resolves its paths as base_path()
plus a relative prefix defaulting to app/Mod/Agentic/Resources/*, which
belongs to a host application, and under Testbench base_path() is the bare
skeleton. Three more tests in the same file failed rather than skipped, for
the same reason — they read a batch-001-link-getting-started fixture that
only a host app would have.

All three prefixes are config-driven, so the suite now points them at a
sandbox under base_path() and writes the prompt template and batch spec it
needs, cleaning up afterwards. The per-test finally blocks go with it,
since afterEach removes the whole tree.

ContentServiceTest: 10 passed, was 2 passed, 3 failed, 5 skipped.

The CI comment is brought back in line with the suite: 1311 tests, 1155
passing, 156 failing, nothing skipped or excluded. It named the missing
Core\Mcp dependency trait, so that is now described accurately —
ValidatesDependencies and HasDependencies belong to dappcore/mcp, which
claims the same Core\Mcp\ PSR-4 root this package also maps onto php/Mcp,
so adding it is a namespace collision to resolve, not a plain install.

Co-Authored-By: Virgil <virgil@lethean.io>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a1e9e4ea-1aaf-49aa-9afd-046e29f70c89

📥 Commits

Reviewing files that changed from the base of the PR and between 6f4e0b6 and 496cf36.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • .github/workflows/ci.yml
  • composer.json
  • php/Console/Commands/GenerateCommand.php
  • php/Data/CreditTransaction.php
  • php/Data/FleetStats.php
  • php/Jobs/BatchContentGeneration.php
  • php/Jobs/ProcessContentTask.php
  • php/Livewire/BrainExplorer.php
  • php/Livewire/CreditLedger.php
  • php/Livewire/FleetOverview.php
  • php/Livewire/HubComponent.php
  • php/Mcp/Resources/ContentResource.php
  • php/Mcp/Tools/Agent/Content/ContentBatchGenerate.php
  • php/Mcp/Tools/Agent/Content/ContentBriefCreate.php
  • php/Mcp/Tools/Agent/Content/ContentBriefGet.php
  • php/Mcp/Tools/Agent/Content/ContentBriefList.php
  • php/Mcp/Tools/Agent/Content/ContentFromPlan.php
  • php/Mcp/Tools/Agent/Content/ContentGenerate.php
  • php/Mcp/Tools/Agent/Content/ContentStatus.php
  • php/Mcp/Tools/Agent/Content/ContentUsageStats.php
  • php/Models/Prompt.php
  • php/Services/ContentService.php
  • php/Services/CreditService.php
  • php/Services/SessionService.php
  • php/tests/Feature/Agentic/Livewire/BrainExplorerTest.php
  • php/tests/Feature/Agentic/Livewire/CreditLedgerTest.php
  • php/tests/Feature/Agentic/Livewire/FleetOverviewTest.php
  • php/tests/Feature/Agentic/Services/CreditServiceTest.php
  • php/tests/Feature/Agentic/Services/SessionServiceTest.php
  • php/tests/Feature/ContentServiceTest.php
  • php/tests/Feature/Jobs/BatchContentGenerationTest.php
  • php/tests/Feature/Jobs/ProcessContentTaskTest.php
  • php/tests/Feature/Livewire/LivewireTestCase.php
  • php/tests/Pest.php
  • php/tests/Unit/ProcessContentTaskTest.php
  • phpunit.xml

📝 Walkthrough

Walkthrough

Changes

Content integration and processing

Layer / File(s) Summary
Content integration and processing
composer.json, php/Console/..., php/Jobs/..., php/Mcp/..., php/Services/ContentService.php, php/tests/Feature/ContentServiceTest.php, php/tests/Feature/Jobs/..., php/tests/Unit/ProcessContentTaskTest.php
Content classes now use the Core\Mod namespace. Content processing uses getMessage() and double-brace placeholders. Tests use current contracts, partial mocks, real table setup, and isolated content fixtures.

Credit data and ledger service

Layer / File(s) Summary
Credit data and ledger service
php/Data/CreditTransaction.php, php/Data/FleetStats.php, php/Services/CreditService.php, php/tests/Feature/Agentic/Services/CreditServiceTest.php
The change adds immutable credit and fleet data classes. CreditService reports balances, records deductions and refunds, retrieves ledgers, validates workspace IDs, and locks entries during balance updates.

Session lifecycle and SSE events

Layer / File(s) Summary
Session lifecycle and SSE events
php/Services/SessionService.php, php/tests/Feature/Agentic/Services/SessionServiceTest.php
SessionService creates sessions, resolves plans and workspaces, validates state transitions, resolves sessions, and emits JSON SSE frames.

Livewire coverage and test support

Layer / File(s) Summary
Livewire coverage and test support
php/Livewire/..., php/tests/Feature/Agentic/Livewire/..., php/tests/Feature/Livewire/LivewireTestCase.php, php/tests/Pest.php, .github/workflows/ci.yml
Livewire view paths and source inspection paths are updated. Agentic Livewire tests now use PHPUnit classes with shared authentication. The CI status comment records updated test totals and failure groups.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionService
  participant Workspace
  participant AgentSession
  Client->>SessionService: create session
  SessionService->>Workspace: resolve workspace and plan
  SessionService->>AgentSession: persist session
  SessionService-->>Client: session.created SSE frame
  Client->>SessionService: update session state
  SessionService->>AgentSession: persist validated state
  SessionService-->>Client: session.state.updated SSE frame
Loading
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


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.

@Snider

Snider commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #14, which carries the same three commits on a named branch (fix/quarantine-clearance). The dev branch is being retired — dev is used as a tag in this estate.

@Snider Snider closed this Aug 8, 2026
@Snider
Snider deleted the dev branch August 8, 2026 08:44
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