Skip to content

Feature/warn if dex is enabled in production#14

Merged
olajideolamide merged 13 commits into
mainfrom
feature/warn-if-dex-is-enabled-in-production
May 22, 2026
Merged

Feature/warn if dex is enabled in production#14
olajideolamide merged 13 commits into
mainfrom
feature/warn-if-dex-is-enabled-in-production

Conversation

@olajideolamide
Copy link
Copy Markdown
Owner

Summary

Warn if dex is in production
Jump to issue if issue id is included in URL

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

Warning

Rate limit exceeded

@olajideolamide has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 37 minutes and 20 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55a2fb60-5254-4d69-b1c9-21723ba972a1

📥 Commits

Reviewing files that changed from the base of the PR and between f5b9d13 and c679aea.

📒 Files selected for processing (5)
  • .github/workflows/phpunit.yml
  • tests/Adapters/CiCacheStoreTest.php
  • tests/Journeys/RepeatedIssueJourneyTest.php
  • tests/Support/Factories/IssueFactory.php
  • tests/Telemetry/RequestLifecycleServiceTest.php

Walkthrough

This PR centralizes multi-database CI/test configuration, refactors request/response adapters to use type-aware helpers, introduces a test support layer (DexTestCase/DexDatabaseTestCase) with factories, and adds extensive database-backed unit, feature, lifecycle, repository, and journey tests.

Changes

Monolithic PR — full review checkpoint

Layer / File(s) Summary
All changes (single checkpoint)
all modified and added files (see PR)
All changed ranges from CI workflow and config, PHPStan baselines/config, adapter refactors (request/response/router), test bootstrap and test infra (DexTestCase, DexDatabaseTestCase, Registrar), test factories, and the large suite of new and updated tests are included in this checkpoint. The change set spans CI jobs for SQLite/MySQL/MariaDB, composer scripts, phpstan updates, request handling refactors, view tweaks (deep-linking), and many database-backed tests.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A fluffy bundle of tests now hops through SQLite, MySQL, and MariaDB,
While adapters shed their method_exists checks and dance with types instead,
Deep links guide issues to dialogs, and journeys trace from create to resolved—
The refactored warren is stronger, the coverage complete, hop hop! 🌟

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/warn-if-dex-is-enabled-in-production

Copy link
Copy Markdown

@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: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Config/Registrar.php (1)

12-12: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add strict typing declaration.

This file is missing the declare(strict_types=1); declaration. As per coding guidelines, use strict typing where the file/project conventions allow it. Other source files in the codebase (e.g., CiRequestPathProvider.php) consistently use strict typing.

🔧 Proposed fix to add strict typing
 <?php
 
+declare(strict_types=1);
+
 /**
  * This file is part of Dex.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Config/Registrar.php` at line 12, This file is missing a strict types
declaration; add declare(strict_types=1); immediately after the opening <?php
tag in the file containing namespace Dex\Config; (the Registrar file) so the
Registrar class and any functions in the Dex\Config namespace are compiled with
strict typing consistent with the project convention.
src/Views/dex/_js.php (1)

219-243: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run deep-link open after row render, and only consume when matching issue is already open.

Current ordering/guard can skip proper row targeting and prematurely consume the deep-link state.

Suggested fix
     function dexIssuesRenderRows() {
         const tbody = document.getElementById('issuesTbody');
         const issues = dexIssuesState.data.issues || [];

         if (dexIssuesState.loading) {
             tbody.innerHTML = '<tr class="empty-row"><td colspan="8">Loading issues...</td></tr>';
             return;
         }
-
-        dexIssuesTryOpenDeepLinkedIssue();

         if (!issues.length) {
             tbody.innerHTML = '<tr class="empty-row"><td colspan="8">No issues match your filters.</td></tr>';
+            dexIssuesTryOpenDeepLinkedIssue();
             return;
         }

         tbody.innerHTML = issues.map(dexIssuesBuildRow).join('');
+        dexIssuesTryOpenDeepLinkedIssue();
     }

     function dexIssuesTryOpenDeepLinkedIssue() {
         if (dexIssuesState.loading || dexIssuesDeepLinkState.opened || !dexIssuesDeepLinkState.issueId) {
             return;
         }

-        if (dexIssueDialogState.issueId) {
+        if (Number(dexIssueDialogState.issueId) === Number(dexIssuesDeepLinkState.issueId)) {
             dexIssuesDeepLinkState.opened = true;
             return;
         }

         const tbody = document.getElementById('issuesTbody');
         const row = tbody?.querySelector(`tr[data-id="${dexIssuesDeepLinkState.issueId}"]`) || null;
         dexIssuesDeepLinkState.opened = true;
         dexIssuesOpenDialog(dexIssuesDeepLinkState.issueId, null, row);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Views/dex/_js.php` around lines 219 - 243, The deep-link logic currently
runs/consumes before rows are rendered and prematurely sets
dexIssuesDeepLinkState.opened; move the call to
dexIssuesTryOpenDeepLinkedIssue() so it runs after tbody.innerHTML =
issues.map(dexIssuesBuildRow).join(''), and change
dexIssuesTryOpenDeepLinkedIssue() to only mark dexIssuesDeepLinkState.opened =
true when the matching row/dialog is actually found or opened: check if
dexIssueDialogState.issueId === dexIssuesDeepLinkState.issueId first (if so, set
opened=true and return), otherwise locate the row element and only if row exists
call dexIssuesOpenDialog(dexIssuesDeepLinkState.issueId, null, row) and then set
opened=true; do not set opened=true unconditionally before confirming a match.
🧹 Nitpick comments (6)
tests/Support/Factories/RequestFactory.php (1)

61-61: 💤 Low value

Simplify boolean conversion.

The nested casting (int) (((int) (...)) > 0) can be simplified to (int) (($summary['exception_count'] ?? 0) > 0) since the comparison already produces a boolean.

♻️ Proposed simplification
-            'has_exception'         => (int) (((int) ($summary['exception_count'] ?? 0)) > 0),
+            'has_exception'         => (int) (($summary['exception_count'] ?? 0) > 0),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Support/Factories/RequestFactory.php` at line 61, The expression used
to set the 'has_exception' value in RequestFactory is overly nested: replace the
nested casts around $summary['exception_count'] with a direct boolean comparison
and single cast so use (int)(($summary['exception_count'] ?? 0) > 0) for the
'has_exception' array entry in the RequestFactory code to simplify and clarify
intent.
tests/Feature/IssueStatusActionFeatureTest.php (1)

82-101: ⚡ Quick win

Consider extracting route setup to shared helper.

If multiple feature tests need identical Dex route configuration, extract setupDexRoutes() to a shared trait or base class method to reduce duplication and ensure consistency across test suites.

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

In `@tests/Feature/IssueStatusActionFeatureTest.php` around lines 82 - 101,
Extract the setupDexRoutes() helper into a shared test utility (either a base
test class or trait) so other feature tests can reuse it: move the method that
configures $filterConfig, sets aliases via self::FILTER_ALIAS ->
DexUiFilter::class, injects mocks with
Factories::injectMock('config','Filters',...) and
Factories::injectMock('filters','filters',...), and registers the route group
using self::PREFIX with the same route definitions and
Services::injectMock('routes', $routes) into the new shared trait/base (e.g.,
DexRoutesTrait or TestCase::setupDexRoutes), update tests to call that shared
method and remove duplicated copies to ensure consistent route setup across
suites.
tests/Support/Config/Registrar.php (1)

56-63: ⚡ Quick win

Directory creation side-effect in config method.

Creating the database directory during configuration violates the principle of config methods being pure. Consider moving this initialization to a test setup method (e.g., in DexDatabaseTestCase::setUp()) or a dedicated bootstrap function.

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

In `@tests/Support/Config/Registrar.php` around lines 56 - 63, The Registrar
config method currently creates the database directory (uses $dbPath and
WRITEPATH and calls mkdir) which introduces a side-effect in configuration;
remove the directory creation from the Registrar config implementation and
instead add directory creation logic to test setup (e.g., implement in
DexDatabaseTestCase::setUp() or a bootstrap helper) so tests ensure the
directory exists before configuration runs; keep $dbPath/WRITEPATH usage in the
config for the path value but move the mkdir/is_dir checks into the test setup
or bootstrap function.
tests/Feature/IssuesListFeatureTest.php (1)

87-116: ⚡ Quick win

Consider extracting duplicate test setup helpers to base class.

The methods setupDexRoutes() and allowLocalIp() are duplicated in both IssuesListFeatureTest and IssueShowFeatureTest. Since both tests extend DexDatabaseTestCase, consider moving these helpers to the base class to reduce duplication and improve maintainability.

♻️ Suggested refactor

Move setupDexRoutes() and allowLocalIp() to tests/Support/DexDatabaseTestCase.php:

// In DexDatabaseTestCase.php
protected function setupDexRoutes(string $filterAlias = 'dexui', string $prefix = 'dex'): void
{
    $filterConfig = config('Filters');
    $filterConfig->aliases[$filterAlias] = DexUiFilter::class;
    Factories::injectMock('config', 'Filters', $filterConfig);
    Factories::injectMock('filters', 'filters', $filterConfig);

    $routes = service('routes');
    $routes->group($prefix, [
        'namespace' => 'Dex\\Controllers',
        'filter'    => $filterAlias,
    ], static function ($routes): void {
        $routes->get('', 'Issues::index');
        $routes->get('issues/data', 'Issues::data');
        $routes->get('issues/(:num)/dialog', 'Issues::dialog/$1');
        $routes->post('issues/(:num)/resolve', 'Issues::resolve/$1');
        $routes->post('issues/(:num)/ignore', 'Issues::ignore/$1');
    });

    Services::injectMock('routes', $routes);
}

protected function allowLocalIp(): void
{
    $this->setDexEnv('DEX_ENABLED', '1');
    $this->setDexEnv('DEX_UI_ENABLED', '1');
    $this->setDexEnv('DEX_UI_ALLOWLIST', '127.0.0.1,::1');
    $this->setDexEnv('DEX_UI_STEALTH_DENY', '0');
    service('superglobals')->setServer('REMOTE_ADDR', '127.0.0.1');
}

Then update the feature tests to call parent::setupDexRoutes() and parent::allowLocalIp().

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

In `@tests/Feature/IssuesListFeatureTest.php` around lines 87 - 116, The
duplicated helper methods setupDexRoutes() and allowLocalIp() should be moved
from IssuesListFeatureTest and IssueShowFeatureTest into the shared test base
DexDatabaseTestCase to avoid duplication; create protected methods with the same
names in DexDatabaseTestCase (keeping the existing logic that configures
Filters, routes grouping and superglobals), remove the duplicates from the
feature test classes, and update those tests to call the inherited
setupDexRoutes() and allowLocalIp() (or
parent::setupDexRoutes()/parent::allowLocalIp() if you prefer explicit calls).
src/Adapters/CiHttpContextProvider.php (2)

88-92: ⚡ Quick win

Remove unused foreach value binding to satisfy static analysis cleanly.

$_header is never used; iterate over keys directly to avoid PHPMD noise.

Proposed fix
-            foreach ($this->requestHeaders($req) as $name => $_header) {
+            foreach (array_keys($this->requestHeaders($req)) as $name) {
                 if ($maxHeaders > 0 && $i >= $maxHeaders) {
                     break;
                 }
                 $line = (string) $req->getHeaderLine((string) $name);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Adapters/CiHttpContextProvider.php` around lines 88 - 92, The foreach
binds an unused value ($_header) causing PHPMD noise; update the loop in
CiHttpContextProvider to iterate keys only by replacing the current foreach
($this->requestHeaders($req) as $name => $_header) with an iteration over the
keys (e.g., foreach (array_keys($this->requestHeaders($req)) as $name)) so the
code still uses $req->getHeaderLine((string) $name) while removing the unused
$_header binding.

108-136: ⚡ Quick win

Add PHPDoc for newly introduced helper methods.

requestHeaders() and resolveRequest() are new functions and should be documented per project standard.

As per coding guidelines, "Add PHPDoc for new functions; comment only complex, non-obvious logic".

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

In `@src/Adapters/CiHttpContextProvider.php` around lines 108 - 136, Add PHPDoc
blocks for the two new methods: above requestHeaders(object $request) document a
short description, annotate the parameter as `@param` object $request (note it
accepts IncomingRequest|CLIRequest|any object with headers()/getHeaders()), and
annotate `@return` array (empty array on failure) plus a brief line describing the
header-extraction logic and fallback behavior; above resolveRequest() add a
short description, annotate `@return` object|null (or the specific request type
union if preferred) and mention that it uses service('request') and catches
Throwable returning null. Keep the comments concise and only describe the
non-obvious behavior (fallbacks and exception swallowing).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/phpunit.yml:
- Around line 53-60: Add an explicit least-privilege permissions block for the
GITHUB_TOKEN to the workflow and jobs; update the phpunit-mysql job (and the
other phpunit job referenced around lines 112-120) to include a permissions key
granting only what the CI needs (for example at minimum "contents: read" and any
other specific scopes like "packages: read" or "actions: read" if those jobs
push or download packages), or add a top-level permissions: { contents: read }
to the workflow if all jobs share the same minimal scope; ensure you add the
permissions block near the job definition for phpunit-mysql (job name/ID
"phpunit-mysql") and likewise for the other phpunit job so the default broad
token scopes are not used.
- Around line 89-95: The cache step "Cache Composer dependencies" uses
actions/cache@v4 for path: vendor and current key expression but runs on
pull_request and can write caches; modify the cache steps (the ones using
actions/cache@v4 with path: vendor and key: ${{ runner.os }}-php-${{ matrix.php
}}-composer-${{ hashFiles('**/composer.lock') }}) to only run for
non-pull_request events by adding an if condition (e.g. if: github.event_name !=
'pull_request') so PRs cannot prime or save the cache; apply the same change to
the other two actions/cache@v4 steps in the workflow.
- Around line 77-83: Update the workflow to pin reusable actions and harden
checkout credentials: replace mutable tags like actions/checkout@v4,
shivammathur/setup-php@v2 and actions/cache@v4 with immutable references (SHA or
full ref) for each occurrence and add persist-credentials: false to the
actions/checkout step; also add a minimal permissions: block at the workflow or
job level (e.g., read-only permissions for contents and other needed scopes) to
limit token scope. Apply these same changes to the corresponding steps in the
phpunit, phpunit-sqlite, phpunit-mysql, and phpunit-mariadb jobs (the
actions/checkout, Setup PHP, and actions/cache steps).

In `@composer.json`:
- Around line 46-47: PHPStan 2.0 fails because the configured CodeIgniter
PHPStan extension file vendor/codeigniter/phpstan-codeigniter/extension.neon is
missing/incompatible; either upgrade or remove the package reference in
composer.json (codeigniter/phpstan-codeigniter) to a PHPStan-2-compatible
release, or stop including its extension in your config; specifically update
composer.json entries for "codeigniter/phpstan-codeigniter" / "phpstan/phpstan"
and modify your phpstan.neon/phpstan.neon.dist to remove or guard the include of
vendor/codeigniter/phpstan-codeigniter/extension.neon (use a
conditional/existence check or delete the include) so the `composer phpstan` run
no longer fails.

In `@src/Adapters/CiHttpContextProvider.php`:
- Around line 53-58: In CiHttpContextProvider's request/context extraction
blocks (the try { $out['url'] = (string) $uri; $out['query'] = (string)
$uri->getQuery(); } catch (Throwable) { /* ignore */ }) and the similar catch
blocks at the other locations, do not swallow exceptions silently; instead catch
Throwable $e and call the class logger (e.g., $this->logger->error) with a clear
message and contextual data (include $uri, any request identifiers, and the
partial $out array), then either set the safe fallbacks
($out['url']=null/$out['query']='') or rethrow the exception depending on caller
expectations; update all occurrences (the blocks around $uri usage at the shown
spots) to follow this pattern so exceptions are logged with context.

In `@src/Adapters/CiRouterInfoProvider.php`:
- Around line 64-66: The catch blocks in CiRouterInfoProvider that currently
read "catch (Throwable) { // ignore }" must not silently swallow exceptions;
replace each empty catch with logging the Throwable and contextual data before
continuing the fallback path. In the CiRouterInfoProvider class (the methods
where you catch Throwable), call the existing logger (e.g.
$this->logger->warning or ->error) and include the exception message/stack and
relevant identifiers (route name, CI identifier, method like
getRouterInfo/resolveRouterInfo) so the log provides context, then continue with
the fallback behavior as before.

In `@src/Config/Registrar.php`:
- Line 25: Registrar currently only registers the 'dexui' filter alias
(DexUiFilter::class) while docs still reference 'dex-ui'; either restore
backwards-compatibility by adding a 'dex-ui' => DexUiFilter::class entry
alongside 'dexui' in the Registrar (and add a comment indicating it is
deprecated) or update the documentation to use the new 'dexui' alias and add a
clear breaking-change note; update the file referenced in the comment
(docs/content/security/protecting-the-dashboard.md) to match whichever approach
you choose so routes/tests and docs stay consistent.

In `@src/Support/RequestSnapshot.php`:
- Around line 54-62: The try/catch around extracting $uri, $url, $query, $host,
$scheme in RequestSnapshot (the block accessing $req->getUri()) currently
swallows Throwable; change it to catch (Throwable $e) and log the exception with
context (include $req or the partial values like $url/$host if available)
instead of ignoring it—use the class's logger or a standard logger to emit the
error and any useful context so failures building the URI snapshot are recorded
for production diagnosis.

In `@tests/Adapters/CiCacheStoreTest.php`:
- Around line 13-18: The teardown currently resets the cache singleton before
invoking the parent's teardown which may rely on the cache; change the order in
the tearDown() method so parent::tearDown() is called first and then
Services::resetSingle('cache') is called afterwards to avoid breaking parent
cleanup logic.

In `@tests/Journeys/RepeatedIssueJourneyTest.php`:
- Line 70: In RepeatedIssueJourneyTest (the test that sets last_seen to
'2025-01-02 10:00:00'), remove the ineffective sleep(0) call—it's a no-op in PHP
and unnecessary because last_seen is explicitly set; simply delete the line
containing sleep(0) so the test relies only on the explicit last_seen assignment
in the test method.

In `@tests/Lifecycle/LifecycleJsonTest.php`:
- Around line 150-153: The test currently passes a valid JSON object string
('{}') to RequestFactory::normal, so update the fixture to use a truly
non-object/malformed lifecycle payload (for example a JSON string or primitive
like '"invalid"', 'null', or '42') when setting 'lifecycle_json' so the
non-object path is exercised; change the call to RequestFactory::normal that
sets 'lifecycle_json' (the $row setup in LifecycleJsonTest) to one of these
non-object values and keep the rest of the test assertions unchanged.

In `@tests/Lifecycle/LifecycleStorageTest.php`:
- Around line 86-91: The test currently filters span items into $spans and then
asserts durations, but it can pass vacuously if $spans is empty; update the test
(the array_filter result $spans in LifecycleStorageTest) to first assert that
$spans is not empty (e.g. use $this->assertNotEmpty($spans) or
$this->assertGreaterThan(0, count($spans))) before iterating, then keep the
existing checks on each $span['duration_ms'] to ensure the test fails when no
spans exist and still validates durations when they do.

In `@tests/Support/Config/Registrar.php`:
- Line 108: The current assignment for the 'port' config in Registrar.php casts
getenv('DB_PORT') to int without validation; update the logic that sets the
'port' array entry (in the Registrar class/config builder) to first check that
getenv('DB_PORT') is a valid numeric integer (e.g., is_numeric or filter_var
with FILTER_VALIDATE_INT) and only cast when valid, otherwise fall back to the
default 3306 (or throw a clear configuration error); ensure you reference the
same 'port' => ... expression so you don't change other config keys.

In `@tests/Support/Factories/IssueFactory.php`:
- Around line 54-57: The regressed() factory returns status 'regression' which
mismatches the method name; update the regressed(array $overrides = []) method
to return array_merge(self::base(), ['status' => 'regressed'], $overrides) so
the status value matches the regressed() helper (refer to regressed() and
base()).

In `@tests/Telemetry/RequestLifecycleServiceTest.php`:
- Line 46: The test in RequestLifecycleServiceTest weakened the assertion from a
bounded timing check to just assertIsNumeric($span['end_ms']), which can mask
duration regressions; replace that assertion with a tolerant upper-bound check
(e.g., $this->assertLessThanOrEqual(160, $span['end_ms'])) to restore validation
that end_ms does not exceed the expected request duration while allowing small
timing variance; update the assertion in the test method that inspects
$span['end_ms'] (the line currently using assertIsNumeric($span['end_ms'])) to
use assertLessThanOrEqual with the suggested tolerance.

---

Outside diff comments:
In `@src/Config/Registrar.php`:
- Line 12: This file is missing a strict types declaration; add
declare(strict_types=1); immediately after the opening <?php tag in the file
containing namespace Dex\Config; (the Registrar file) so the Registrar class and
any functions in the Dex\Config namespace are compiled with strict typing
consistent with the project convention.

In `@src/Views/dex/_js.php`:
- Around line 219-243: The deep-link logic currently runs/consumes before rows
are rendered and prematurely sets dexIssuesDeepLinkState.opened; move the call
to dexIssuesTryOpenDeepLinkedIssue() so it runs after tbody.innerHTML =
issues.map(dexIssuesBuildRow).join(''), and change
dexIssuesTryOpenDeepLinkedIssue() to only mark dexIssuesDeepLinkState.opened =
true when the matching row/dialog is actually found or opened: check if
dexIssueDialogState.issueId === dexIssuesDeepLinkState.issueId first (if so, set
opened=true and return), otherwise locate the row element and only if row exists
call dexIssuesOpenDialog(dexIssuesDeepLinkState.issueId, null, row) and then set
opened=true; do not set opened=true unconditionally before confirming a match.

---

Nitpick comments:
In `@src/Adapters/CiHttpContextProvider.php`:
- Around line 88-92: The foreach binds an unused value ($_header) causing PHPMD
noise; update the loop in CiHttpContextProvider to iterate keys only by
replacing the current foreach ($this->requestHeaders($req) as $name => $_header)
with an iteration over the keys (e.g., foreach
(array_keys($this->requestHeaders($req)) as $name)) so the code still uses
$req->getHeaderLine((string) $name) while removing the unused $_header binding.
- Around line 108-136: Add PHPDoc blocks for the two new methods: above
requestHeaders(object $request) document a short description, annotate the
parameter as `@param` object $request (note it accepts
IncomingRequest|CLIRequest|any object with headers()/getHeaders()), and annotate
`@return` array (empty array on failure) plus a brief line describing the
header-extraction logic and fallback behavior; above resolveRequest() add a
short description, annotate `@return` object|null (or the specific request type
union if preferred) and mention that it uses service('request') and catches
Throwable returning null. Keep the comments concise and only describe the
non-obvious behavior (fallbacks and exception swallowing).

In `@tests/Feature/IssuesListFeatureTest.php`:
- Around line 87-116: The duplicated helper methods setupDexRoutes() and
allowLocalIp() should be moved from IssuesListFeatureTest and
IssueShowFeatureTest into the shared test base DexDatabaseTestCase to avoid
duplication; create protected methods with the same names in DexDatabaseTestCase
(keeping the existing logic that configures Filters, routes grouping and
superglobals), remove the duplicates from the feature test classes, and update
those tests to call the inherited setupDexRoutes() and allowLocalIp() (or
parent::setupDexRoutes()/parent::allowLocalIp() if you prefer explicit calls).

In `@tests/Feature/IssueStatusActionFeatureTest.php`:
- Around line 82-101: Extract the setupDexRoutes() helper into a shared test
utility (either a base test class or trait) so other feature tests can reuse it:
move the method that configures $filterConfig, sets aliases via
self::FILTER_ALIAS -> DexUiFilter::class, injects mocks with
Factories::injectMock('config','Filters',...) and
Factories::injectMock('filters','filters',...), and registers the route group
using self::PREFIX with the same route definitions and
Services::injectMock('routes', $routes) into the new shared trait/base (e.g.,
DexRoutesTrait or TestCase::setupDexRoutes), update tests to call that shared
method and remove duplicated copies to ensure consistent route setup across
suites.

In `@tests/Support/Config/Registrar.php`:
- Around line 56-63: The Registrar config method currently creates the database
directory (uses $dbPath and WRITEPATH and calls mkdir) which introduces a
side-effect in configuration; remove the directory creation from the Registrar
config implementation and instead add directory creation logic to test setup
(e.g., implement in DexDatabaseTestCase::setUp() or a bootstrap helper) so tests
ensure the directory exists before configuration runs; keep $dbPath/WRITEPATH
usage in the config for the path value but move the mkdir/is_dir checks into the
test setup or bootstrap function.

In `@tests/Support/Factories/RequestFactory.php`:
- Line 61: The expression used to set the 'has_exception' value in
RequestFactory is overly nested: replace the nested casts around
$summary['exception_count'] with a direct boolean comparison and single cast so
use (int)(($summary['exception_count'] ?? 0) > 0) for the 'has_exception' array
entry in the RequestFactory code to simplify and clarify intent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 91f314b6-7b50-4844-9bd2-2ed035c27f57

📥 Commits

Reviewing files that changed from the base of the PR and between 37855d9 and 7ba3351.

📒 Files selected for processing (61)
  • .github/workflows/phpunit.yml
  • .gitignore
  • README.md
  • composer.json
  • phpstan-baseline.neon
  • phpstan-baseline.neon.cleaned
  • phpstan-baseline.neon.new
  • phpstan.neon.dist
  • phpunit.xml.dist
  • src/Adapters/CiHttpContextProvider.php
  • src/Adapters/CiRequestMetaFactory.php
  • src/Adapters/CiRequestPathProvider.php
  • src/Adapters/CiResponseApplier.php
  • src/Adapters/CiRouterInfoProvider.php
  • src/Config/Registrar.php
  • src/Config/Routes.php
  • src/Controllers/Issues.php
  • src/Dex.php
  • src/Filters/DexUiFilter.php
  • src/Repositories/IssueRepository.php
  • src/Support/ConfigResolver.php
  • src/Support/RequestSnapshot.php
  • src/Views/dex/_js.php
  • src/Views/dex/issues_dialog_shell.php
  • src/Views/dex/issues_list.php
  • src/Views/dex/layout.php
  • tests/Adapters/CiCacheStoreTest.php
  • tests/Adapters/CiHttpContextProviderTest.php
  • tests/Adapters/CiRequestPathProviderTest.php
  • tests/Adapters/CiRouterInfoProviderTest.php
  • tests/Database/MigrationTest.php
  • tests/Feature/DashboardAccessTest.php
  • tests/Feature/IssueShowFeatureTest.php
  • tests/Feature/IssueStatusActionFeatureTest.php
  • tests/Feature/IssuesListFeatureTest.php
  • tests/Filters/DexUiFilterFeatureTest.php
  • tests/Filters/DexUiFilterTest.php
  • tests/Journeys/IgnoredIssueJourneyTest.php
  • tests/Journeys/NewIssueJourneyTest.php
  • tests/Journeys/RepeatedIssueJourneyTest.php
  • tests/Journeys/ResolvedRegressionJourneyTest.php
  • tests/Lifecycle/LifecycleJsonTest.php
  • tests/Lifecycle/LifecycleMetricsTest.php
  • tests/Lifecycle/LifecycleStorageTest.php
  • tests/Repositories/IssueReadRepositoryTest.php
  • tests/Repositories/IssueRepositoryTest.php
  • tests/Repositories/OccurrenceReadRepositoryTest.php
  • tests/Repositories/OccurrenceRepositoryTest.php
  • tests/Repositories/RequestReadRepositoryTest.php
  • tests/Repositories/RequestRepositoryTest.php
  • tests/Support/CachedConfigProviderTest.php
  • tests/Support/Config/Registrar.php
  • tests/Support/ConfigResolverTest.php
  • tests/Support/DexDatabaseTestCase.php
  • tests/Support/DexTestCase.php
  • tests/Support/Factories/IssueFactory.php
  • tests/Support/Factories/LifecycleFactory.php
  • tests/Support/Factories/OccurrenceFactory.php
  • tests/Support/Factories/RequestFactory.php
  • tests/Telemetry/RequestLifecycleServiceTest.php
  • tests/bootstrap.php
💤 Files with no reviewable changes (3)
  • src/Views/dex/issues_dialog_shell.php
  • src/Adapters/CiResponseApplier.php
  • src/Views/dex/layout.php

Comment thread .github/workflows/phpunit.yml
Comment thread .github/workflows/phpunit.yml Outdated
Comment thread .github/workflows/phpunit.yml
Comment thread composer.json
Comment thread src/Adapters/CiHttpContextProvider.php
Comment thread tests/Lifecycle/LifecycleJsonTest.php
Comment thread tests/Lifecycle/LifecycleStorageTest.php
Comment thread tests/Support/Config/Registrar.php
Comment thread tests/Support/Factories/IssueFactory.php
Comment thread tests/Telemetry/RequestLifecycleServiceTest.php
Copy link
Copy Markdown

@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: 1

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

Inline comments:
In @.github/workflows/phpunit.yml:
- Around line 9-11: The workflow-wide permissions currently grant actions: write
for all runs; narrow them by changing the top-level permissions block
(permissions:) to contents: read and actions: read, then remove or downgrade any
job-level actions: write in the phpunit-sqlite, phpunit-mysql, and
phpunit-mariadb jobs; re-grant actions: write only where cache saving occurs
(the step using actions/cache@v4) and only for non-PR runs (e.g., add an if:
github.event_name != 'pull_request' on that save step or on the job that saves
cache) so GITHUB_TOKEN write scope is limited to the cache-save path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbbb2622-0e2f-4ed7-865e-76d8c0bc6563

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba3351 and f5b9d13.

📒 Files selected for processing (1)
  • .github/workflows/phpunit.yml

Comment thread .github/workflows/phpunit.yml
@olajideolamide
Copy link
Copy Markdown
Owner Author

@coderabbitai resolve

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

✅ Actions performed

Comments resolved and changes approved.

@olajideolamide olajideolamide merged commit 7cdd4c2 into main May 22, 2026
15 checks passed
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.

2 participants