Skip to content

[MINOR BC] [4.x] Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths - #1473

Open
lukinovec wants to merge 12 commits into
masterfrom
scope-cache-fix
Open

[MINOR BC] [4.x] Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths#1473
lukinovec wants to merge 12 commits into
masterfrom
scope-cache-fix

Conversation

@lukinovec

@lukinovec lukinovec commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This problem is specific to file-driver stores that are listed in tenancy.cache.stores, while tenancy.filesystem.scope_cache is set to true.

Ran into this while checking whether we could drop the separate 'parallel' cache store from our boilerplate's testing setup and instead just give the 'file' store a per-process path (framework/cache/data_<parallel testing token>), so each test process gets its own cache directory. Turns out scopeCache() discards configured paths entirely, so that has no effect (see below).

Using a different directory for the file cache store by setting cache.stores.file.path (either using config([...]), or directly in config/cache.php -- doesn't matter) has no effect -- FilesystemTenancyBootstrapper::scopeCache() ignores path/lock_path entirely and rewrites both to a hardcoded <storage>/framework/cache/data path on every tenancy()->initialize()/tenancy()->end():

// In `FilesystemTenancyBootstrapper::scopeCache()` (called both in `bootstrap()` and in `revert()`)
foreach ($stores as $name) {
    $path = $storagePath . '/framework/cache/data';
    $this->app['config']["cache.stores.{$name}.path"] = $path;
    $this->app['config']["cache.stores.{$name}.lock_path"] = $path;
    ...
}

Specific issues with hardcoding the path like this:

  • a store with a configured (non-default) path gets scoped to use the default one (what I described above)
  • lock_path is always overwritten with path, so a store with a separate lock directory loses that separation
  • revert() runs the same code, so it doesn't restore what the store was configured with before tenancy initialized -- it just re-applies the same hardcoded default. Central cache ends up using the wrong path after ending tenancy.

The fix

In bootstrap(), scopeCache() captures the original configured path and scopes those instead of using a hardcoded default.

  • If the configured path is under the central storage path, that central part gets swapped for the tenant's storage path, keeping everything after it the same (e.g. storage/framework/cache/data becomes storage/tenant1/framework/cache/data).
  • If the path isn't storage_path()-based, there's nothing to swap, so the tenant's suffix just gets appended to the end of the path instead.

On revert(), scopeCache(false) puts the captured paths back into cache.stores.{$name}.path/lock_path and into the resolved store instance, so central cache uses the path it was configured with again.

The paths are captured just once. If a bootstrap() fails after scopeCache() (e.g. when scopeSessions() can't create its directory), revert() never runs and the config is left with the scoped paths, so capturing a second time would lose the central ones. scopeCache() also skips stores it has no captured path for, so adding a store to tenancy.cache.stores while tenancy is initialized doesn't make tenancy()->end() throw. Same for removing stores from tenancy.cache.stores in tenant context (see the 'scopeCache ignores changes to tenancy.cache.stores made in tenant context' test)

lock_path stays null when a store doesn't configure it, rather than us making it default to the scoped path -- FileStore already falls back to path for locks in that case, so we can just respect the store's original config.

Also added tests that cover each of the issues above (+ a test for handling paths that aren't storage_path()-based).

POSSIBLE MINOR BC: Someone with 'path' => '/var/cache/foobar' currently gets tenant cache in storage/tenant1/framework/cache/data. After this fix, they get /var/cache/foobar/tenant1, so whatever is already cached in the old directory is orphaned.

Possible further improvement

Rather than tenantCachePath() hardcoding how a store's path gets scoped, that could go through config, the same way diskRoot() resolves root_override templates. Something like:

// config/tenancy.php
'cache' => [
    'path_override' => [
        'file' => '%configured_path%/%suffix%',
    ],
],

with %configured_path%, %suffix%, %storage_path% and %original_storage_path% placeholders. Stores without an entry would keep the behavior described above (no template involved).

What that would let people do. Here's the default file store from config/cache.php:

'file' => [
    'driver' => 'file',
    'path' => storage_path('framework/cache/data'),
],

The changes in this PR scope that path to storage/tenant1/framework/cache/data, so each tenant's cache directory sits next to their files:

storage/
├── framework/cache/data/      // central cache
├── tenant1/
│   ├── app/
│   └── framework/cache/data/
└── tenant2/
    ├── app/
    └── framework/cache/data/

With 'file' => '%configured_path%/%suffix%', the cache directory would stay where it's configured and each tenant would get a subdirectory in it:

storage/
├── framework/cache/data/
│   ├── tenant1/
│   └── tenant2/
├── tenant1/
│   └── app/
└── tenant2/
    └── app/

All tenant cache directories in one place, so anything that only concerns cache (skipping it in backups, deleting old files) is one path instead of one per tenant. And it makes mounting possible -- you can't mount tmpfs on storage/tenant3/framework/cache/data before tenant3 exists, but you can mount it once on storage/framework/cache/data and every tenant's cache ends up inside the mount.

But you can already get the same thing without a new config key. If the store's path is outside storage_path(), the changes in this PR append the suffix to it automatically:

'path' => '/var/cache/myapp',   // in tenant1's context, this would become '/var/cache/myapp/tenant1'

So path_override would only really add one thing -- the same structure while the cache stays inside storage/. That does matter to someone whose deploy scripts, .gitignore and volume mounts all assume storage/, but that's about it.

So I don't think we should add it. AFAIK, nobody has ever asked for a configurable cache path (I searched the issues and PRs and couldn't find anything that would suggest that this is something people want). There are also two problems with the idea itself:

  • path and lock_path would go through the same template, so '%storage_path%/framework/cache/data' brings back the first two bugs from the list above -- the configured path gets thrown away, and both directories end up in the same place. We'd have to require the template to contain %configured_path%.
  • The placeholders wouldn't match root_override, where the tenant part is %tenant% (the tenant key). Here it'd be %suffix% (suffix_base + key), so two names for nearly the same thing in one class. I think that'd be confusing.

(Note that until recently I thought this would be the fix for the suffix_storage_path problem below, but I don't think that anymore -- that one should be fixed by default, not by a config key.)

More issues found while looking into this

All of this is pre-existing and untouched by this PR, so I'd deal with these in separate PRs.

suffix_storage_path => false isn't respected by scopeCache() or scopeSessions(). With the setting off, in the tenant context, right after tenancy()->initialize() and before writing anything:

// respects suffix_storage_path
storage_path()    .../storage

// doesn't -- and the directory is already created
session.files     .../storage/tenant<key>/framework/sessions 

Cache only points the store at the suffixed path and lets FileStore create it on first write. scopeSessions() is worse because it calls mkdir() on every tenancy init, with no suffix_storage_path check anywhere, whether or not a session is ever written.

Also, nothing cleans those directories up. DeleteTenantStorage returns early when suffix_storage_path === false (see the DeleteTenantStorage job). The early return there is correct -- without it the next guard would catch the case anyway, since storage_path() in the tenant context is the central path there, and deleting that would be very bad of course. But it means the cache/session directories under storage/tenant<key>/ can't be reached by any cleanup at the moment (so one such directory for every tenant that ever existed). Laravel doesn't do anything with these file sessions either, since it only clears whatever session.files currently points at.

Turning scope_cache/scope_sessions off wouldn't be a feasible "solution" because for the file driver, path scoping is the only isolation there is (FileStore::getPrefix() returns a hardcoded ''), so all tenants would share one cache directory. The settings are also about different things -- suffix_storage_path about whether storage_path() itself moves, scope_cache/scope_sessions about whether tenant cache and sessions stay separate. A central storage_path() with isolated cache sounds like a sensible combination (and I see that's what people actually want -- #196). And since suffix_storage_path, scope_cache and scope_sessions are all enabled by default in the config, someone who only sets suffix_storage_path to false (which is what the config comment tells you to do on S3) runs into this without ever touching cache or session scoping.

There's a separate scopeSessions() bug that has nothing to do with suffix_storage_path though. It never reads session.files, it hardcodes <storage>/framework/sessions on both bootstrap and revert. So a configured session path gets discarded when tenancy initializes, and it doesn't come back when tenancy ends -- the same two problems this PR fixes for cache. With session.files set to /tmp/foo-sessions:

In tenant context:    session.files = .../storage/tenant<key>/framework/sessions
After ending tenancy: session.files = .../storage/framework/sessions

So after ending tenancy, sessions don't go back to the configured /tmp/foo-sessions. They use <storage>/framework/sessions, which was never configured anywhere. This one would probably be the easiest to fix.

I think we could fix this by scoping cache and sessions inside the configured path when suffix_storage_path is false, so storage/framework/cache/data/tenant1 instead of storage/tenant1/framework/cache/data. Tenants stay separated, storage_path() stays central like the user asked for, and there's no storage/tenant<key>/ directory for cleanup to miss.

Note that suffix_storage_path isn't documented anywhere in the v4 docs, it's only documented by the docblock in the config file. The cache/session scoping sections describe the storage/tenant{id}/framework/... structure as the behavior, with no mention that anything changes it.

  • Delete the notes about regression in the tests after reviewing the PR fully (EDIT: deleted, leaving this unticked until @stancl reviews this PR, or specifically, this commit: aabba92)
  • Decide whether we should add the path_override thing

Summary by CodeRabbit

Bug Fixes

  • Improved isolation for file-based caches across tenant contexts.
  • Preserved central cache entries when switching between tenant and central contexts.
  • Kept cache and lock directories independently configured and scoped.
  • Added reliable handling for custom cache locations and optional lock directories.
  • Restored original cache settings when tenant context is reverted.
  • Prevented unsupported, missing, or dynamically added cache stores from being incorrectly scoped.

Tests

  • Expanded coverage for tenant isolation, custom directories, lock paths, disabled caching, missing stores, and recovery scenarios.

The tests cover the current (mostly incorrect) scopeCache() behavior (= hardcoding the /framework/cache/data path regardless of what was configured).

The 'file cache stores are separated per tenant' is not a regression test -- it covers the default path, which already worked correctly, there were just no tests for it. The rest are regression tests (see the "NOTE ABOUT REGRESSION" comments -- these are temporary, added them just so that it's clear what's currently wrong or broken) that should be fixed by the FS bootstrapper fix in the next commit.
scopeCache() rewrote path and lock_path for every file-driver store to a hardcoded '<storage>/framework/cache/data' path, completely ignoring the store's config. Now, scopeCache() remembers each store's original path and lock_path, scopes these paths for the tenant, and restores them to the stored originals on revert.

The store's lock_path was always overwritten by the same hardcoded path. But lock_path is configurable too, AND it's actually optional (unlike path). If it's not configured at all (= it's null or just unset), Laravel automatically falls back to the store's path. So in that case, leave lock_path null instead of assigning the path to it. This is not a *huge* change, assigning path to lock_path would essentially achieve the same thing, BUT if someone explicitly sets lock_path to null in the config, we should just respect that and let Laravel fall back to the path instead of setting the lock_path ourselves.

Also, on revert(), the same hardcoded path was used in scopeCache(). So if someone used a custom file driver-based store, cached something in central context, initialized and ended tenancy, the central cache got corrupt (see the 'central cache is not lost when tenancy ends' test).
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf318525-9eb7-4787-9d1f-39a19c305f0e

📥 Commits

Reviewing files that changed from the base of the PR and between 8244e56 and 7b12d51.

📒 Files selected for processing (1)
  • src/Bootstrappers/FilesystemTenancyBootstrapper.php
💤 Files with no reviewable changes (1)
  • src/Bootstrappers/FilesystemTenancyBootstrapper.php

📝 Walkthrough

Walkthrough

File cache stores now retain original paths, apply tenant-specific path and lock_path values independently, update FileStore instances, and restore paths after tenancy ends. Tests cover isolation, custom paths, disabled scoping, dynamic stores, missing stores, and external paths.

Changes

Filesystem cache scoping

Layer / File(s) Summary
Per-store cache path scoping
src/Bootstrappers/FilesystemTenancyBootstrapper.php
Captures original paths, scopes central and external paths per tenant, updates FileStore instances, and restores the original values.
Cache isolation and restoration coverage
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php
Tests tenant isolation, central cache persistence, separate cache and lock paths, disabled scoping, dynamic stores, missing stores, and external paths.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Tenancy
  participant FilesystemTenancyBootstrapper
  participant CacheConfig
  participant FileStore
  Tenancy->>FilesystemTenancyBootstrapper: initialize tenant
  FilesystemTenancyBootstrapper->>CacheConfig: scope store path and lock_path
  FilesystemTenancyBootstrapper->>FileStore: apply scoped paths
  Tenancy->>FilesystemTenancyBootstrapper: revert tenant
  FilesystemTenancyBootstrapper->>CacheConfig: restore original paths
Loading

Possibly related PRs

  • archtechx/tenancy#1381: Modifies FilesystemTenancyBootstrapper.php to manage tenant-scoped filesystem paths.

Poem

A rabbit maps each cache lane,
Tenant paths stay separate and plain.
Lock paths follow their store,
Central paths return once more.
Each cache keeps its proper place.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the fix for configured cache paths discarded by FilesystemTenancyBootstrapper::scopeCache().
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scope-cache-fix

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.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.74%. Comparing base (e0990a4) to head (7b12d51).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1473      +/-   ##
============================================
+ Coverage     86.65%   86.74%   +0.09%     
- Complexity     1220     1228       +8     
============================================
  Files           186      186              
  Lines          3589     3599      +10     
============================================
+ Hits           3110     3122      +12     
+ Misses          479      477       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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

🤖 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 `@src/Bootstrappers/FilesystemTenancyBootstrapper.php`:
- Around line 220-244: Validate the original cache path in the cache-scoping
flow before passing it to scopeCachePath(); when a file-driver store omits path,
fail with a clear configuration error or skip the store consistently during
bootstrap and revert. Preserve the existing optional lock_path handling and
ensure scopeCachePath() is never called with null.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 31b4c918-c01e-4d38-bdde-b69db2e32054

📥 Commits

Reviewing files that changed from the base of the PR and between 553f57a and 483a3ec.

📒 Files selected for processing (2)
  • src/Bootstrappers/FilesystemTenancyBootstrapper.php
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php

Comment thread src/Bootstrappers/FilesystemTenancyBootstrapper.php Outdated
scopeCache() didn't feel right since it 1) stored thee original paths, 2) actually scoped things. Separate the concerns so that scopeCache() just does that -- scopes cache.
@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

Making it protected could be a minor bc, and it'd be inconsistent with scopeSessions (which is public).
@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@lukinovec lukinovec changed the title Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths [MINOR BC] Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths Aug 3, 2026
@lukinovec lukinovec changed the title [MINOR BC] Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths [MINOR BC] [4.x] Fix FilesystemTenancyBootstrapper::scopeCache() discarding configured cache paths Aug 3, 2026
@lukinovec
lukinovec marked this pull request as ready for review August 3, 2026 15:08

@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.

Caution

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

⚠️ Outside diff range comments (1)
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php (1)

507-508: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use a unique temporary directory.

This test recursively deletes the fixed /tmp/tenancy-cache-test directory. Another local process or parallel test run can use that directory. The test can delete unrelated data and can conflict with another run.

Generate the path from sys_get_temp_dir() with a random suffix.

Proposed fix
-    $path = '/tmp/tenancy-cache-test';
+    $path = sys_get_temp_dir() . '/tenancy-cache-test-' . bin2hex(random_bytes(8));
🤖 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/Bootstrappers/FilesystemTenancyBootstrapperTest.php` around lines 507 -
508, Update the temporary path setup in the affected test to derive the
directory from sys_get_temp_dir() and append a unique random suffix, then
continue passing that generated path to File::deleteDirectory. Ensure each test
run targets only its own temporary directory instead of the fixed
tenancy-cache-test path.
🤖 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.

Outside diff comments:
In `@tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php`:
- Around line 507-508: Update the temporary path setup in the affected test to
derive the directory from sys_get_temp_dir() and append a unique random suffix,
then continue passing that generated path to File::deleteDirectory. Ensure each
test run targets only its own temporary directory instead of the fixed
tenancy-cache-test path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 02a24bdd-540d-4eeb-b1ee-43c71eb6dcc5

📥 Commits

Reviewing files that changed from the base of the PR and between aabba92 and 6b62798.

📒 Files selected for processing (1)
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php

@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

Caution

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

⚠️ Outside diff range comments (1)
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php (1)

471-500: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that an absent lock_path remains null.

The current assertions pass if scopeCache() replaces an absent lock_path with path. Assert that cache.stores.foo_file.lock_path is null after initialization and after tenancy()->end().

Proposed test assertions
     tenancy()->initialize(Tenant::create());

+    expect(config('cache.stores.foo_file.lock_path'))->toBeNull();
+
     expect(Cache::store('foo_file')->put('key', 'tenant'))->toBeTrue();
     expect(Cache::store('foo_file')->lock('foo')->get())->toBeTrue();

     tenancy()->end();

+    expect(config('cache.stores.foo_file.lock_path'))->toBeNull();
+
     expect(Cache::store('foo_file')->put('key', 'central'))->toBeTrue();
🤖 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/Bootstrappers/FilesystemTenancyBootstrapperTest.php` around lines 471 -
500, Update the test around scopeCache() to assert that
cache.stores.foo_file.lock_path remains null after tenancy()->initialize() and
again after tenancy()->end(). Keep the existing cache put and lock behavior
assertions unchanged.
🤖 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 `@src/Bootstrappers/FilesystemTenancyBootstrapper.php`:
- Around line 196-213: In src/Bootstrappers/FilesystemTenancyBootstrapper.php at
the bootstrap-time loop (lines 196-213), record the names of file stores that
are successfully scoped into a new instance property (for example, a scoped
stores list). During revert, update the scopeCache(false) method to iterate over
this captured snapshot of scoped stores instead of reading from the current
tenancy.cache.stores configuration list, ensuring that stores removed
mid-request are still reverted. In
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php at lines 578-618, add
a test case that scopes a file store during bootstrap, then removes it from
tenancy.cache.stores before calling tenancy()->end(), and asserts that the
store's path, lock_path, and resolved FileStore instance are restored to their
central-context values on revert.

---

Outside diff comments:
In `@tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php`:
- Around line 471-500: Update the test around scopeCache() to assert that
cache.stores.foo_file.lock_path remains null after tenancy()->initialize() and
again after tenancy()->end(). Keep the existing cache put and lock behavior
assertions unchanged.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: f5703c71-3aab-4cb7-850a-0f98033cc0f6

📥 Commits

Reviewing files that changed from the base of the PR and between 6b62798 and 0765bfc.

📒 Files selected for processing (2)
  • src/Bootstrappers/FilesystemTenancyBootstrapper.php
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php

Comment thread src/Bootstrappers/FilesystemTenancyBootstrapper.php Outdated
Note: 'the original cache paths are only stored on the first bootstrap' test got removed  -- it tested that the "Unable to create tenant session directory" exception gets thrown, and that's not in scope of the current PR.

@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.

Caution

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

⚠️ Outside diff range comments (3)
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php (3)

525-540: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that lock_path remains null.

The cache and lock operations also pass if the bootstrapper replaces an absent lock_path with the scoped cache path. Assert that cache.stores.foo_file.lock_path is null during tenancy and after tenancy()->end().

Based on upstream contract: scopeCache() preserves an absent lock_path as null.

🤖 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/Bootstrappers/FilesystemTenancyBootstrapperTest.php` around lines 525 -
540, Extend the test around the foo_file store configuration to assert that
cache.stores.foo_file.lock_path remains null both after tenancy initialization
and after tenancy()->end(). Keep the existing cache and lock operation
assertions unchanged, verifying scopeCache() preserves the absent lock_path
rather than replacing it with the scoped cache path.

493-505: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Release each acquired file lock.

Both tests call get() on non-expiring locks and do not release them. The cleanup only deletes the central configured directory. It does not remove the scoped tenant lock directory. A reused tenant suffix can then make a later lock acquisition fail.

  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php#L493-L505: retain the tenant and central lock instances, then call release() before cleanup.
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php#L534-L540: retain the tenant and central fallback lock instances, then call release() before cleanup.
🤖 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/Bootstrappers/FilesystemTenancyBootstrapperTest.php` around lines 493 -
505, Release every acquired non-expiring file lock before cleanup: in
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php lines 493-505, retain
the tenant and central lock instances and call release() on both; apply the same
change to the tenant and central fallback locks at lines 534-540.

550-551: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use an isolated temporary directory.

Line 550 uses a fixed shared directory. File::deleteDirectory() deletes all its contents before and after the test. Parallel test workers can delete each other’s active cache data. Local runs can also delete unrelated data at this path. Generate a unique child directory under the system temporary directory for this fixture.

Also applies to: 586-586

🤖 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/Bootstrappers/FilesystemTenancyBootstrapperTest.php` around lines 550 -
551, Replace the fixed shared directory path `/tmp/tenancy-cache-test` with a
dynamically generated unique temporary directory. Generate a unique child
directory under the system temporary directory for the $path variable
assignment, then pass this unique path to File::deleteDirectory(). Apply the
same fix to both occurrences at lines 550 and 586 to prevent parallel test
workers and local runs from deleting each other's or unrelated cache data.
🤖 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.

Outside diff comments:
In `@tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php`:
- Around line 525-540: Extend the test around the foo_file store configuration
to assert that cache.stores.foo_file.lock_path remains null both after tenancy
initialization and after tenancy()->end(). Keep the existing cache and lock
operation assertions unchanged, verifying scopeCache() preserves the absent
lock_path rather than replacing it with the scoped cache path.
- Around line 493-505: Release every acquired non-expiring file lock before
cleanup: in tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php lines
493-505, retain the tenant and central lock instances and call release() on
both; apply the same change to the tenant and central fallback locks at lines
534-540.
- Around line 550-551: Replace the fixed shared directory path
`/tmp/tenancy-cache-test` with a dynamically generated unique temporary
directory. Generate a unique child directory under the system temporary
directory for the $path variable assignment, then pass this unique path to
File::deleteDirectory(). Apply the same fix to both occurrences at lines 550 and
586 to prevent parallel test workers and local runs from deleting each other's
or unrelated cache data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2106cfba-9413-4eb9-8649-04256ab95fb9

📥 Commits

Reviewing files that changed from the base of the PR and between 0765bfc and 597e48e.

📒 Files selected for processing (1)
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php

Also add a separate test ('scopeCache ignores changes to tenancy.cache.stores made in tenant context' ) -- the 'central cache is not lost when tenancy ends' covered the skipping mechanism partially, but having a separate test for the tenancy.cache.stores mid-tenant context changes is definitely cleaner and makes more sense.

@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.

Caution

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

⚠️ Outside diff range comments (2)
src/Bootstrappers/FilesystemTenancyBootstrapper.php (1)

17-18: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear the cache-path snapshot after each revert.

$originalCachePaths and $originalCacheLockPaths remain populated after revert(). If central code changes a scoped store path or lock_path after tenancy ends, Line 211 skips the new values. The next bootstrap scopes and restores the stale first values.

Clear both maps after the revert loop. Add a regression test that changes both paths after tenancy()->end(), then initializes another tenant and verifies the new paths are scoped and restored.

Proposed fix
             $store->setDirectory($path);
             $store->setLockDirectory($lockPath);
         }
+
+        if ($suffix === false) {
+            $this->originalCachePaths = [];
+            $this->originalCacheLockPaths = [];
+        }
     }

Also applies to: 211-214

🤖 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/Bootstrappers/FilesystemTenancyBootstrapper.php` around lines 17 - 18,
Update FilesystemTenancyBootstrapper::revert() to clear both originalCachePaths
and originalCacheLockPaths after completing the revert loop, so each subsequent
bootstrap snapshots current path values. Add a regression test covering changes
to both path and lock_path after tenancy()->end(), then verify the next tenant
scopes those new paths and restores them afterward.
tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php (1)

579-580: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Use a unique cross-platform temporary directory.

/tmp/tenancy-cache-test is shared by test workers. The cleanup can delete artifacts from another run or local data with the same path. The fixed /tmp path also prevents this test from running on platforms without /tmp.

Proposed fix
-    $path = '/tmp/tenancy-cache-test';
+    $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'tenancy-cache-' . bin2hex(random_bytes(8));

Also applies to: 615-615

🤖 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/Bootstrappers/FilesystemTenancyBootstrapperTest.php` around lines 579 -
580, Replace the hard-coded /tmp/tenancy-cache-test path in the affected test
setup and cleanup blocks with a unique, cross-platform temporary directory
generated through the project’s existing temporary-directory utility, and reuse
that generated path throughout each test run. Apply the same change to the
additional occurrence noted in the comment.
🤖 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.

Outside diff comments:
In `@src/Bootstrappers/FilesystemTenancyBootstrapper.php`:
- Around line 17-18: Update FilesystemTenancyBootstrapper::revert() to clear
both originalCachePaths and originalCacheLockPaths after completing the revert
loop, so each subsequent bootstrap snapshots current path values. Add a
regression test covering changes to both path and lock_path after
tenancy()->end(), then verify the next tenant scopes those new paths and
restores them afterward.

In `@tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php`:
- Around line 579-580: Replace the hard-coded /tmp/tenancy-cache-test path in
the affected test setup and cleanup blocks with a unique, cross-platform
temporary directory generated through the project’s existing temporary-directory
utility, and reuse that generated path throughout each test run. Apply the same
change to the additional occurrence noted in the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 514d9e23-ec4f-4f08-9d0e-5dbda57b548d

📥 Commits

Reviewing files that changed from the base of the PR and between 0765bfc and 8244e56.

📒 Files selected for processing (2)
  • src/Bootstrappers/FilesystemTenancyBootstrapper.php
  • tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php

The check could only pass with a `null` path, which is just bad configuration. So no reason to keep this.
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