Skip to content

Conversation

@mitchelsellers
Copy link
Contributor

@mitchelsellers mitchelsellers commented Dec 6, 2025

Summary by CodeRabbit

  • Tests
    • Added comprehensive unit tests for directory operations with isolated temp directories and cleanup.
    • Added extensive file operation tests covering text/byte I/O, streams, move/copy/replace, attributes and timestamps.
    • Enhanced time provider tests with parsing, epoch and timezone conversion checks.
    • Added URL slug generator tests covering valid inputs, edge cases and invalid-input handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@mitchelsellers mitchelsellers added the documentation Improvements or additions to documentation label Dec 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds comprehensive unit tests: new test suites for DirectoryProvider, FileProvider, and UrlSlugGenerator; updates TimeProviderTests with parsing, epoch/timezone tests and timing assertion adjustments. All changes are test-only; no production code modified.

Changes

Cohort / File(s) Summary
Directory provider tests
src/NetCore.Utilities.Tests/DirectoryProviderTests.cs
Adds DirectoryProviderTests (public, IDisposable) with many [Fact] tests covering create/delete (including recursive), enumerate directories/files/entries, existence checks, move, directory roots/parents, logical drives, current directory, and timestamp getters/setters; includes temp-root setup and robust Dispose cleanup.
File provider tests
src/NetCore.Utilities.Tests/FileProviderTests.cs
Adds FileProviderTests (public, IDisposable) exercising text/lines I/O, encoding variants, append/create/open stream scenarios, byte read/write round-trips, copy/move/replace semantics, existence/deletion, attributes, and timestamps (local/UTC). Per-test temp directory management and Dispose cleanup included.
URL slug generator tests
src/NetCore.Utilities.Tests/UrlSlugGeneratorTests.cs
Adds UrlSlugGeneratorTests with parameterized cases for slug generation (spaces, special chars, caps, numbers), invalid-input (ArgumentException) tests, collapse-multiple-dashes and trim-leading/trailing-dashes assertions.
Time provider test updates
src/NetCore.Utilities.Tests/TimeProviderTests.cs
Replaces millisecond assertions with duration-based checks (TotalSeconds within 1s), removes scaffolding comments, adds Parse/TryParse/TryParseExact tests, epoch-second and ConvertTimeTo/FromUtc tests including unknown-timezone exception cases; minor test adjustments to use UTC where appropriate.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Pay attention to temp directory lifecycle and Dispose implementations in:
    • src/NetCore.Utilities.Tests/DirectoryProviderTests.cs
    • src/NetCore.Utilities.Tests/FileProviderTests.cs
  • Review timezone-related assertions and use of UTC/local in:
    • src/NetCore.Utilities.Tests/TimeProviderTests.cs
  • Validate parameterized test inputs and expected outputs in:
    • src/NetCore.Utilities.Tests/UrlSlugGeneratorTests.cs

Possibly related PRs

Poem

🐇 I dug a test hole, tidy and neat,

Files and folders lined up in a row,
Timestamps ticked true, slugs hopped to their beat,
Cleanup at sunset—no stray crumbs to show,
Hooray for tests! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Improved Unit Test Coverage for Project' is overly broad and generic. While it relates to the changeset, it does not convey the specific focus of the tests added (DirectoryProvider, FileProvider, TimeProvider, UrlSlugGenerator). Consider a more specific title that highlights the key test suites added, such as 'Add comprehensive unit tests for DirectoryProvider, FileProvider, TimeProvider, and UrlSlugGenerator' or 'Add test coverage for core utility providers'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 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 c5d5e75 and aa11800.

📒 Files selected for processing (3)
  • src/NetCore.Utilities.Tests/DirectoryProviderTests.cs (1 hunks)
  • src/NetCore.Utilities.Tests/FileProviderTests.cs (1 hunks)
  • src/NetCore.Utilities.Tests/UrlSlugGeneratorTests.cs (1 hunks)

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

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 6, 2025

@mitchelsellers mitchelsellers merged commit 67c3512 into develop Dec 6, 2025
4 checks passed
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: 1

🧹 Nitpick comments (4)
src/NetCore.Utilities.Tests/TimeProviderTests.cs (1)

149-156: Consider making UtcNowSecondsSinceEpoch test tolerant instead of strict

Both SecondsSinceEpoch(_timeProvider.UtcNow) and _timeProvider.UtcNowSecondsSinceEpoch() depend on wall‑clock time; under heavy load they could drift by a second and sporadically fail. Allowing a ±1 second window would keep the intent while avoiding rare flakes.

Example adjustment:

-        public void UtcNowSecondsSinceEpoch_ShouldMatchSecondsSinceEpochOfUtcNow()
-        {
-            var utcNow = _timeProvider.UtcNow;
-            var expected = _timeProvider.SecondsSinceEpoch(utcNow);
-            var actual = _timeProvider.UtcNowSecondsSinceEpoch();
-            Assert.Equal(expected, actual);
-        }
+        public void UtcNowSecondsSinceEpoch_ShouldBeWithinOneSecondOfSecondsSinceEpochOfUtcNow()
+        {
+            var expected = _timeProvider.SecondsSinceEpoch(_timeProvider.UtcNow);
+            var actual = _timeProvider.UtcNowSecondsSinceEpoch();
+
+            var delta = Math.Abs((long)expected - (long)actual);
+            Assert.InRange(delta, 0, 1);
+        }
src/NetCore.Utilities.Tests/FileProviderTests.cs (1)

239-263: Optional: strengthen Replace tests by asserting backup contents

Right now the Replace tests ensure that the destination contains the source content and that the backup file exists. To fully pin wrapper behavior to File.Replace, you might also assert that the backup contains the original destination content.

For example, in Replace_ShouldReplaceFileContents:

-        _fileProvider.Replace(src, dest, backup);
-        Assert.Equal("Source", _fileProvider.ReadAllText(dest));
-        Assert.True(_fileProvider.Exists(backup));
+        _fileProvider.Replace(src, dest, backup);
+        Assert.Equal("Source", _fileProvider.ReadAllText(dest));
+        Assert.True(_fileProvider.Exists(backup));
+        Assert.Equal("Destination", _fileProvider.ReadAllText(backup));

Same idea could be applied to the Replace_WithIgnoreMetadataErrors variant.

src/NetCore.Utilities.Tests/DirectoryProviderTests.cs (2)

188-219: Optional: timestamp tests could better isolate which property is being set

SetAndGetLastAccessTime_ShouldWork, SetAndGetLastWriteTime_ShouldWork, and SetAndGetCreationTime_ShouldWork currently assert only that the year of the retrieved timestamp matches the year you wrote. That’s fine as a smoke test, but similar to the FileProvider case, it won’t catch a future regression where setters are accidentally wired to the wrong underlying Directory APIs.

If you want stricter coverage, consider patterns where you:

  • Baseline one timestamp to year A and another to year B.
  • Invoke a single setter.
  • Assert that only the intended timestamp’s year changes.

That would make these tests robust against mis‑wiring while still tolerating filesystem granularity.


221-232: GetLogicalDrives_ShouldReturnDrives may be brittle across platforms

The test currently asserts that:

  • GetLogicalDrives() is non‑empty.
  • Its length exactly matches DriveInfo.GetDrives().Length.
  • Every DriveInfo.Name is contained in the returned array.

On Windows this likely holds if DirectoryProvider.GetLogicalDrives delegates to Directory.GetLogicalDrives, but on Linux/macOS the relationship between DriveInfo.GetDrives() and any logical‑drive list can differ, which could make this test fail even when the wrapper is correctly forwarding.

You might want to relax the assertion to something like “every drive returned by the provider exists in the DriveInfo set” or simply assert non‑emptiness and that returned names are a subset of DriveInfo names.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84586df and c5d5e75.

📒 Files selected for processing (4)
  • src/NetCore.Utilities.Tests/DirectoryProviderTests.cs (1 hunks)
  • src/NetCore.Utilities.Tests/FileProviderTests.cs (1 hunks)
  • src/NetCore.Utilities.Tests/TimeProviderTests.cs (3 hunks)
  • src/NetCore.Utilities.Tests/UrlSlugGeneratorTests.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/NetCore.Utilities.Tests/FileProviderTests.cs (1)
src/NetCore.Utilities/FileProvider.cs (1)
  • FileProvider (401-717)
src/NetCore.Utilities.Tests/TimeProviderTests.cs (1)
src/NetCore.Utilities/TimeProvider.cs (12)
  • TryParse (85-85)
  • TryParse (105-105)
  • TryParse (208-208)
  • TryParse (211-211)
  • TryParseExact (123-124)
  • TryParseExact (142-143)
  • TryParseExact (214-215)
  • TryParseExact (218-219)
  • SecondsSinceEpoch (152-152)
  • SecondsSinceEpoch (222-227)
  • UtcNowSecondsSinceEpoch (160-160)
  • UtcNowSecondsSinceEpoch (230-233)
src/NetCore.Utilities.Tests/UrlSlugGeneratorTests.cs (1)
src/NetCore.Utilities/UrlSlugGenerator.cs (1)
  • UrlSlugGenerator (22-39)
⏰ 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). (1)
  • GitHub Check: Analyze Code Quality
🔇 Additional comments (11)
src/NetCore.Utilities.Tests/UrlSlugGeneratorTests.cs (1)

6-62: UrlSlugGenerator tests cover key behaviors and align with implementation

The test suite nicely exercises normal, punctuation-heavy, edge, and invalid inputs, plus the dash-collapsing and trimming behavior. The expectations match the current UrlSlugGenerator implementation and should give good regression coverage with minimal brittleness.

src/NetCore.Utilities.Tests/TimeProviderTests.cs (4)

19-22: Using a 1‑second tolerance for Now/UtcNow is a good, low‑flakiness pattern

Comparing TotalSeconds within a small tolerance instead of asserting exact equality gives the right balance between verifying the wrapper and avoiding clock‑scheduling flakes.

Also applies to: 36-40


62-90: Parse / TryParse / TryParseExact tests nicely lock wrapper behavior to DateTime

These tests consistently use DateTime.Parse / DateTime.TryParse / DateTime.TryParseExact as oracles (with/without provider and styles), which is exactly what you want to ensure the wrapper stays a thin delegation layer over the BCL behavior.

Also applies to: 92-112, 114-138


140-147: SecondsSinceEpoch constant matches the underlying implementation

The hard‑coded value 1546345815 for 2019-01-01 12:30:15Z lines up with the SecondsSinceEpoch implementation (epoch at 1970‑01‑01T00:00:00Z and returning TotalSeconds as ulong), so this is a good regression guard for that helper.


158-192: Timezone conversion tests cover both success and failure paths well

The new ConvertTimeFromUtc/ConvertTimeToUtc tests validate both unknown‑timezone failure (TimeZoneNotFoundException) and correct mapping through TimeZoneInfo.Local, which should catch most wrapper mistakes around timezone ID usage and direction of conversion.

src/NetCore.Utilities.Tests/FileProviderTests.cs (3)

17-39: Per‑test temp directory setup/teardown is clean and test‑friendly

Creating a unique temp root per test and cleaning it up in Dispose is a solid pattern that keeps the filesystem tests isolated and avoids cross‑test interference.


41-215: Broad file IO and stream coverage is well‑structured

The tests around WriteAllText/ReadAllText, line‑based IO, append operations, bytes round‑trip, and the various Open*/Create* overloads do a good job of pinning the FileProvider to System.IO.File semantics, including encoding‑specific paths and stream capabilities.

Also applies to: 239-263, 341-395


341-395: ReadLines and Create/Open overload tests nicely cover enumeration and stream behavior

The ReadLines (with and without encoding) plus Create/Open overload tests give good confidence that the provider’s enumerable and stream APIs behave as expected (readability/writability, correct data).

src/NetCore.Utilities.Tests/DirectoryProviderTests.cs (3)

16-32: Temp root management via IDisposable keeps directory tests isolated

Creating a per‑test _testRoot under the system temp path and cleaning it up in Dispose is a good pattern for avoiding cross‑test interference and leftover state on the filesystem.


34-186: Directory creation/deletion, enumeration, and move tests give good coverage

The tests around CreateDirectory, Delete (including recursive), directory/file enumerations (with/without search patterns), existence checks, and Move all map directly to the underlying System.IO.Directory/File behavior and should quickly surface wrapper regressions.


234-241: Parent directory test correctly ties GetParent to the temp root

GetParent_ShouldReturnParentDirectoryInfo is a nice sanity check that GetParent returns _testRoot for a child under _testRoot, which should quickly catch mapping mistakes in the DirectoryProvider.GetParent implementation.

Comment on lines +265 to +339
[Fact]
public void GetAndSetAttributes_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "Attributes");
_fileProvider.SetAttributes(path, FileAttributes.ReadOnly);
var attrs = _fileProvider.GetAttributes(path);
Assert.True(attrs.HasFlag(FileAttributes.ReadOnly));
}

[Fact]
public void GetAndSetCreationTime_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "CreationTime");
var now = DateTime.Now;
_fileProvider.SetCreationTime(path, now);
var actual = _fileProvider.GetCreationTime(path);
Assert.Equal(now.Year, actual.Year);
}

[Fact]
public void GetAndSetCreationTimeUtc_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "CreationTimeUtc");
var utcNow = DateTime.UtcNow;
_fileProvider.SetCreationTimeUtc(path, utcNow);
var actual = _fileProvider.GetCreationTimeUtc(path);
Assert.Equal(utcNow.Year, actual.Year);
}

[Fact]
public void GetAndSetLastAccessTime_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "LastAccessTime");
var now = DateTime.Now;
_fileProvider.SetLastAccessTime(path, now);
var actual = _fileProvider.GetLastAccessTime(path);
Assert.Equal(now.Year, actual.Year);
}

[Fact]
public void GetAndSetLastAccessTimeUtc_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "LastAccessTimeUtc");
var utcNow = DateTime.UtcNow;
_fileProvider.SetLastAccessTimeUtc(path, utcNow);
var actual = _fileProvider.GetLastAccessTimeUtc(path);
Assert.Equal(utcNow.Year, actual.Year);
}

[Fact]
public void GetAndSetLastWriteTime_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "LastWriteTime");
var now = DateTime.Now;
_fileProvider.SetLastWriteTime(path, now);
var actual = _fileProvider.GetLastWriteTime(path);
Assert.Equal(now.Year, actual.Year);
}

[Fact]
public void GetAndSetLastWriteTimeUtc_ShouldWork()
{
var path = GetTestFilePath();
_fileProvider.WriteAllText(path, "LastWriteTimeUtc");
var utcNow = DateTime.UtcNow;
_fileProvider.SetLastWriteTimeUtc(path, utcNow);
var actual = _fileProvider.GetLastWriteTimeUtc(path);
Assert.Equal(utcNow.Year, actual.Year);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the FileProvider.cs file
find . -name "FileProvider.cs" -type f

Repository: IowaComputerGurus/netcore.utilities

Length of output: 118


🏁 Script executed:

# List the overall structure to understand the repository
git ls-files | head -20

Repository: IowaComputerGurus/netcore.utilities

Length of output: 758


🏁 Script executed:

# Read the FileProvider.cs file to check the implementations
cat -n ./src/NetCore.Utilities/FileProvider.cs

Repository: IowaComputerGurus/netcore.utilities

Length of output: 37975


🏁 Script executed:

# Read the test file to verify test structure
sed -n '265,339p' ./src/NetCore.Utilities.Tests/FileProviderTests.cs

Repository: IowaComputerGurus/netcore.utilities

Length of output: 3106


🏁 Script executed:

# Read more lines to see all the tests mentioned
sed -n '296,350p' ./src/NetCore.Utilities.Tests/FileProviderTests.cs

Repository: IowaComputerGurus/netcore.utilities

Length of output: 2126


🏁 Script executed:

# Get the remaining test
sed -n '340,360p' ./src/NetCore.Utilities.Tests/FileProviderTests.cs

Repository: IowaComputerGurus/netcore.utilities

Length of output: 892


SetLastAccessTimeUtc and SetLastWriteTime have mapping bugs that current tests fail to catch

The FileProvider implementation contains two bugs that the current tests are too weak to detect:

  • Line 660: SetLastAccessTimeUtc calls File.SetCreationTimeUtc instead of File.SetLastAccessTimeUtc
  • Line 666: SetLastWriteTime calls File.SetLastAccessTime instead of File.SetLastWriteTime

The current tests only assert now.Year == actual.Year, which pass despite the wrong methods being called because the filesystem timestamps often coincide in year values by chance. To catch these mis-mappings, tests should:

  1. Set different timestamps to clearly different years (e.g., creation time to 2000, then access/write time to 2010)
  2. Assert that the intended timestamp changes to the new value
  3. Assert that unrelated timestamps remain at their baseline value

For example, GetAndSetLastWriteTime_ShouldWork should set access time to an earlier year, then write time to a later year, and verify both values independently. This would cause tests to fail if SetLastWriteTime mistakenly calls SetLastAccessTime.

🤖 Prompt for AI Agents
In src/NetCore.Utilities.Tests/FileProviderTests.cs around lines 265-339, the
tests are too weak to catch mapping bugs (SetLastAccessTimeUtc calls
SetCreationTimeUtc and SetLastWriteTime calls SetLastAccessTime); update the
tests to set clearly different timestamps (e.g., creation=2000,
access/write=2010), then set the target timestamp and assert the target changed
to the expected year and that the unrelated timestamps remain at their baseline
values so wrong method calls will fail; also fix the FileProvider implementation
mappings (ensure SetLastAccessTimeUtc calls File.SetLastAccessTimeUtc and
SetLastWriteTime calls File.SetLastWriteTime) if you prefer to correct code
rather than only strengthen tests.

@mitchelsellers mitchelsellers deleted the feature/improve-code-coverage branch December 6, 2025 06:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants