Skip to content

Conversation

@ChiragAgg5k
Copy link
Member

@ChiragAgg5k ChiragAgg5k commented Oct 1, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Cloud storage folder detection improved so directories are reliably recognized (with or without trailing slash); when direct metadata lookup fails, a fallback listing check ensures parity with local storage.
  • Tests

    • Added automated tests covering directory existence for nested paths and mixed file/folder scenarios across storage adapters.

@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

Walkthrough

Expands S3 device exists() to treat directory-like paths by falling back to listing objects when getInfo throws; adds directory-existence tests for Local storage and S3 test base and introduces an abstract getAdapterType() requirement in the S3 test base.

Changes

Cohort / File(s) Summary
S3 device existence logic
src/Storage/Device/S3.php
Modifies exists() to return true when getInfo succeeds; if getInfo throws, normalizes the path into a prefix (ensuring a trailing slash for directory checks), lists objects with a limit of 1, and returns true if any objects are found, otherwise false.
Local storage directory tests
tests/Storage/Device/LocalTest.php
Adds testDirectoryExists() which asserts non-existence for missing paths, creates nested directories, verifies existence for each level (with and without trailing slash), creates a file inside the nested path, and asserts cleanup removes the paths.
S3 test base updates
tests/Storage/S3Base.php
Adds abstract protected function getAdapterType(): string; and public function testDirectoryExists() to require adapter-specific behavior and to validate directory-existence semantics across S3 adapters.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller as Caller
  participant S3A as S3 Device Adapter
  participant S3 as S3 Service

  Caller->>S3A: exists(path)
  alt getInfo succeeds
    S3A->>S3: getInfo(path)
    S3-->>S3A: metadata
    S3A-->>Caller: true
  else getInfo throws
    S3A->>S3: listObjects(prefix=normalized path or path + '/', limit=1)
    S3-->>S3A: objectList (KeyCount)
    alt KeyCount > 0
      S3A-->>Caller: true
    else KeyCount == 0
      S3A-->>Caller: false
    end
  end

  note over S3A: Normalizes path and ensures trailing slash for directory-like checks
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paws on cloud and tree,
I sniff the keys where folders be.
If info stumbles, I list the trail,
I peek the prefix, follow the tail.
A rabbit hops — existence found! 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary change by indicating that the exists method is modified to check for directories, accurately reflecting the core adjustments made to both S3 and local storage adapters in this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-dir-exists

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71a4fc3 and 77e0cf2.

📒 Files selected for processing (1)
  • src/Storage/Device/S3.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Storage/Device/S3.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: E2E Test (LocalTest)
  • GitHub Check: E2E Test (DOSpacesTest)
  • GitHub Check: E2E Test (WasabiTest)
  • GitHub Check: E2E Test (LinodeTest)
  • GitHub Check: E2E Test (BackblazeTest)
  • GitHub Check: E2E Test (S3Test)

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/Storage/Device/S3.php (1)

619-644: Trim trailing slash on root when building the S3 prefix.
Replace

$prefix = $this->getRoot().'/'.ltrim($path, '/');

with

$root   = rtrim($this->getRoot(), '/');
$prefix = $root . '/' . ltrim($path, '/');

to avoid double-slashes in $prefix ([src/Storage/Device/S3.php#631-634]).

🧹 Nitpick comments (1)
tests/Storage/S3Base.php (1)

147-166: Consider documenting S3's virtual directory behavior in the test.

The test correctly validates S3's object-based storage model where directories are virtual (implied by object key prefixes). Key observations:

  1. Line 149: Tests existing 'testing/' directory (populated in setUp)
  2. Line 153: Creates nested structure by writing a file - no explicit directory creation needed in S3
  3. Lines 155-157: Verifies "directories" exist (paths without trailing slash) - differs from LocalTest which tests both with/without trailing slash
  4. Lines 163-165: After deleting the only file, expects directories to not exist - correct for S3's virtual directory model

This differs from LocalTest.testDirectoryExists() which:

  • Explicitly creates directories using createDirectory()
  • Tests paths both with AND without trailing slashes
  • Explicitly deletes with delete($testDir, true) (recursive)

The S3 test correctly reflects S3's behavior, but a comment explaining why directories don't need explicit creation/deletion would help future maintainers understand the distinction.

Consider adding a comment to clarify S3's virtual directory behavior:

 public function testDirectoryExists()
 {
+    // In S3, directories are virtual and implied by object key prefixes
+    // They exist if any objects have that prefix
     $this->assertEquals(true, $this->object->exists($this->object->getPath('testing/')));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71102ea and c6d999b.

📒 Files selected for processing (3)
  • src/Storage/Device/S3.php (2 hunks)
  • tests/Storage/Device/LocalTest.php (1 hunks)
  • tests/Storage/S3Base.php (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/Storage/Device/LocalTest.php (2)
src/Storage/Device.php (5)
  • exists (209-209)
  • getPath (85-85)
  • createDirectory (249-249)
  • write (159-159)
  • delete (192-192)
src/Storage/Device/Local.php (5)
  • exists (388-391)
  • getPath (63-66)
  • createDirectory (440-449)
  • write (288-297)
  • delete (338-353)
tests/Storage/S3Base.php (8)
tests/Storage/Device/BackblazeTest.php (1)
  • getAdapterType (25-28)
tests/Storage/Device/LinodeTest.php (1)
  • getAdapterType (25-28)
tests/Storage/Device/S3Test.php (1)
  • getAdapterType (28-31)
tests/Storage/Device/WasabiTest.php (1)
  • getAdapterType (25-28)
tests/Storage/Device/DOSpacesTest.php (1)
  • getAdapterType (25-28)
tests/Storage/Device/LocalTest.php (1)
  • testDirectoryExists (87-115)
src/Storage/Device/S3.php (4)
  • exists (624-645)
  • getPath (230-233)
  • write (503-515)
  • delete (527-538)
src/Storage/Device.php (4)
  • exists (209-209)
  • getPath (85-85)
  • write (159-159)
  • delete (192-192)
src/Storage/Device/S3.php (2)
src/Storage/Device.php (1)
  • getRoot (74-74)
src/Storage/Device/Local.php (1)
  • getRoot (53-56)
🔇 Additional comments (3)
tests/Storage/Device/LocalTest.php (1)

87-115: LGTM! Comprehensive directory existence test coverage.

The test thoroughly validates directory existence behavior including:

  • Non-existent paths (with/without trailing slash)
  • Directory creation and existence verification
  • Nested directory structures
  • File creation within nested structure
  • Recursive deletion and cleanup verification

The test structure is clear and aligns well with the Local storage implementation which uses PHP's file_exists() that naturally handles both files and directories.

src/Storage/Device/S3.php (1)

619-623: Good docblock update.

The updated docblock correctly reflects that the method now handles both files and directories, not just files as before.

tests/Storage/S3Base.php (1)

23-26: LGTM! Abstract method enforces adapter type contract.

The new abstract method getAdapterType() properly enforces that all S3-based adapter test classes provide their type. This is used in the testType() method and ensures consistency across all S3 adapter implementations.

@abnegate abnegate merged commit 5562a61 into main Oct 2, 2025
9 checks passed
@abnegate abnegate deleted the fix-dir-exists branch October 2, 2025 06:46
ChiragAgg5k added a commit that referenced this pull request Dec 2, 2025
This reverts commit 5562a61, reversing
changes made to 71102ea.
ChiragAgg5k added a commit that referenced this pull request Dec 2, 2025
This reverts commit 5562a61, reversing
changes made to 71102ea.
ChiragAgg5k added a commit that referenced this pull request Dec 2, 2025
This reverts commit 5562a61, reversing
changes made to 71102ea.
loks0n added a commit that referenced this pull request Dec 2, 2025
Revert "Merge pull request #138 from utopia-php/fix-dir-exists"
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.

3 participants