Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

<Target Name="ApplyFileVersion" AfterTargets="MinVer">
<PropertyGroup>
<BUILD_BUILDNUMBER Condition="'$(BUILD_BUILDNUMBER)' == ''">00000</BUILD_BUILDNUMBER>
<FileVersion>$(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BUILD_BUILDNUMBER)</FileVersion>
<GITHUB_RUN_NUMBER Condition="'$(GITHUB_RUN_NUMBER)' == ''">0</GITHUB_RUN_NUMBER>
<FileVersion>$(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(GITHUB_RUN_NUMBER)</FileVersion>
</PropertyGroup>
</Target>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Cuemon.Extensions.DependencyInjection" Version="9.0.0-preview.9" />
<PackageReference Include="Cuemon.IO" Version="9.0.0-preview.9" />
<PackageReference Include="Cuemon.Core" Version="9.0.0-preview.9" PrivateAssets="compile" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features;
using Cuemon.IO;
using System;
using System.IO;
using System.Text;
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features;
using Cuemon;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;

Expand All @@ -20,10 +23,23 @@ public FakeHttpContextAccessor()
var fc = new FeatureCollection();
fc.Set<IHttpResponseFeature>(new FakeHttpResponseFeature());
fc.Set<IHttpRequestFeature>(new FakeHttpRequestFeature());
fc.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(StreamFactory.Create(writer => writer.Write("Hello awesome developers!"))));
fc.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(MakeGreeting("Hello awesome developers!")));
HttpContext = new DefaultHttpContext(fc);
}

private Stream MakeGreeting(string greeting)
{
return Patterns.SafeInvoke(() => new MemoryStream(), ms =>
{
var sw = new StreamWriter(ms, Encoding.UTF8);
sw.Write(greeting);
sw.Flush();
ms.Flush();
ms.Position = 0;
return ms;
}, ex => throw new InvalidOperationException("There is an error in the Stream being written.", ex));
}

/// <summary>
/// Gets or sets the HTTP context.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http;
using Cuemon.Extensions.DependencyInjection;
using System;
using System.ComponentModel;
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
{
Expand All @@ -18,18 +20,27 @@ public static class ServiceCollectionExtensions
/// <returns>A reference to <paramref name="services"/> after the operation has completed.</returns>
public static IServiceCollection AddFakeHttpContextAccessor(this IServiceCollection services, ServiceLifetime lifetime)
{
services.TryAdd<IHttpContextAccessor, FakeHttpContextAccessor>(provider =>
switch (lifetime)
{
var contextAccessor = new FakeHttpContextAccessor
{
HttpContext =
{
RequestServices = provider
}
};
return contextAccessor;
}, lifetime);
case ServiceLifetime.Transient:
services.TryAddTransient(FakeHttpContextAccessorFactory);
break;
case ServiceLifetime.Scoped:
services.TryAddScoped(FakeHttpContextAccessorFactory);
break;
case ServiceLifetime.Singleton:
services.TryAddSingleton(FakeHttpContextAccessorFactory);
break;
default:
throw new InvalidEnumArgumentException(nameof(lifetime), (int)lifetime, typeof(ServiceLifetime));
}
return services;
}

private static IHttpContextAccessor FakeHttpContextAccessorFactory(IServiceProvider provider)
{
var contextAccessor = new FakeHttpContextAccessor { HttpContext = { RequestServices = provider } };
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify Object Initialization for Better Readability

The nested object initializer on line 42 can be refactored to improve readability. Separating the initialization steps makes the code clearer and easier to maintain.

Apply this diff to simplify the initialization:

-var contextAccessor = new FakeHttpContextAccessor { HttpContext = { RequestServices = provider } };
+var contextAccessor = new FakeHttpContextAccessor();
+contextAccessor.HttpContext.RequestServices = provider;

This change enhances clarity by explicitly setting the RequestServices property after creating the FakeHttpContextAccessor instance.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var contextAccessor = new FakeHttpContextAccessor { HttpContext = { RequestServices = provider } };
var contextAccessor = new FakeHttpContextAccessor();
contextAccessor.HttpContext.RequestServices = provider;

return contextAccessor;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Cuemon.Core" Version="9.0.0-preview.9" PrivateAssets="compile" />
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Cuemon.Core appears to be unused and may be removed.

The Cuemon.Core package is referenced in multiple project files, but no usage was found in the codebase. If Cuemon.Core is not essential for your project, consider removing these references to clean up dependencies.

🔗 Analysis chain

Verify the necessity and stability of the new Cuemon.Core package.

The addition of the Cuemon.Core package (version 9.0.0-preview.9) with PrivateAssets="compile" is noted. While this aligns with the PR objectives for dependency updates, please consider the following:

  1. Ensure that this pre-release version (9.0.0-preview.9) is stable enough for your current development phase.
  2. Verify that the PrivateAssets="compile" attribute is intentional and aligns with your project's architecture.
  3. Document any new functionality or changes that this package introduces to the project.

To ensure this package is used correctly throughout the project, run the following script:

This script will help identify where Cuemon.Core is being used and if it's referenced in other project files, which might be affected by the PrivateAssets attribute.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of Cuemon.Core in the project

# Test: Search for Cuemon.Core usage
echo "Searching for Cuemon.Core usage:"
rg --type csharp "Cuemon\.Core" -g "!*.csproj"

# Test: Check if Cuemon.Core is referenced in other project files
echo "Checking for Cuemon.Core references in other .csproj files:"
rg --type xml "Cuemon\.Core" -g "*.csproj" -g "!src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj"

Length of output: 907

<PackageReference Include="xunit.extensibility.core" Version="2.9.2" />
</ItemGroup>

Expand Down
8 changes: 8 additions & 0 deletions src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.

using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "This is an implementation of the IDisposable interface tailored to avoid wrong implementations.", Scope = "type", Target = "~T:Codebelt.Extensions.Xunit.Hosting.HostFixture")]
44 changes: 40 additions & 4 deletions src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ namespace Codebelt.Extensions.Xunit.Hosting
/// <summary>
/// Provides a default implementation of the <see cref="IHostFixture"/> interface.
/// </summary>
/// <seealso cref="Disposable" />
/// <seealso cref="IHostFixture" />
public class HostFixture : Disposable, IHostFixture
public class HostFixture : IDisposable, IHostFixture
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Incomplete Disposal Implementation for Unmanaged Resources in HostFixture

It appears that while HostFixture implements both OnDisposeManagedResources and OnDisposeUnmanagedResources, the corresponding disposal logic for unmanaged resources is missing due to the absence of UnmanagedDisposable.cs. This could result in incomplete disposal of resources and potential memory leaks.

  • Ensure that OnDisposeUnmanagedResources is properly implemented.
  • If unmanaged resources are no longer needed, remove the OnDisposeUnmanagedResources method to prevent confusion.
🔗 Analysis chain

Confirm the implications of changing inheritance from Disposable to implementing IDisposable.

By changing HostFixture from inheriting Disposable to directly implementing IDisposable, ensure that all disposal logic previously handled by Disposable is now correctly implemented in HostFixture. Verify that no crucial functionality from Disposable is missing.

Run the following script to compare the members of Disposable and HostFixture:


Sure, let's gather more information to ensure all necessary disposal methods are correctly implemented.


Sure, let's further investigate the inheritance hierarchy to ensure all disposal logic is correctly implemented.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if all relevant disposal methods from `Disposable` are implemented in `HostFixture`.

# Test: Find methods in the `Disposable` class and compare with methods in `HostFixture`.
# Expected: All necessary disposal methods from `Disposable` are re-implemented in `HostFixture`.

# Locate the definition of the `Disposable` class
DISPOSABLE_FILE=$(fd 'Disposable.cs' --type f)

echo "Methods in Disposable class ($DISPOSABLE_FILE):"
rg --type cs 'protected\s+(virtual|abstract|override)?\s+void\s+\w+\s*\(' "$DISPOSABLE_FILE" -A 3

echo "Methods in HostFixture class (src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs):"
rg --type cs 'protected\s+(virtual|override)?\s+void\s+\w+\s*\(' src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs -A 3

Length of output: 1436


Script:

#!/bin/bash
# Description: Verify the contents of `ManagedDisposable.cs` to ensure it includes all required disposal methods.

# Locate the definition of the `ManagedDisposable` class
MANAGED_DISPOSABLE_FILE=$(fd 'ManagedDisposable.cs' --type f)

echo "Contents of ManagedDisposable class ($MANAGED_DISPOSABLE_FILE):"
cat "$MANAGED_DISPOSABLE_FILE"

Length of output: 952


Script:

#!/bin/bash
# Description: Identify the base classes of `HostFixture` to understand the inheritance chain.

# Locate and display the base classes of HostFixture
rg --type cs 'public\s+class\s+HostFixture\s*:\s*(.+)' src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs

Length of output: 166

{
/// <summary>
/// Initializes a new instance of the <see cref="HostFixture"/> class.
Expand Down Expand Up @@ -127,15 +126,52 @@ public virtual void ConfigureHost(Test hostTest)
#endif

/// <summary>
/// Called when this object is being disposed by either <see cref="M:Cuemon.Disposable.Dispose" /> or <see cref="M:Cuemon.Disposable.Dispose(System.Boolean)" /> having <c>disposing</c> set to <c>true</c> and <see cref="P:Cuemon.Disposable.Disposed" /> is <c>false</c>.
/// Gets a value indicating whether this <see cref="HostFixture"/> object is disposed.
/// </summary>
protected override void OnDisposeManagedResources()
/// <value><c>true</c> if this <see cref="HostFixture"/> object is disposed; otherwise, <c>false</c>.</value>
public bool Disposed { get; private set; }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure thread-safe access to the Disposed property.

If HostFixture instances may be accessed from multiple threads, the Disposed property should be accessed in a thread-safe manner to prevent race conditions.

Consider using a locking mechanism or marking Disposed as volatile.


/// <summary>
/// Called when this object is being disposed by either <see cref="Dispose()" /> or <see cref="Dispose(bool)" /> having <c>disposing</c> set to <c>true</c> and <see cref="Disposed" /> is <c>false</c>.
/// </summary>
protected virtual void OnDisposeManagedResources()
{
if (ServiceProvider is ServiceProvider sp)
{
sp.Dispose();
}
Host?.Dispose();
}

/// <summary>
/// Called when this object is being disposed by either <see cref="Dispose()"/> or <see cref="Dispose(bool)"/> and <see cref="Disposed"/> is <c>false</c>.
/// </summary>
protected virtual void OnDisposeUnmanagedResources()
{
}

/// <summary>
/// Releases all resources used by the <see cref="HostFixture"/> object.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Releases the unmanaged resources used by the <see cref="HostFixture"/> object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected void Dispose(bool disposing)
{
Comment on lines +166 to +167
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make Dispose(bool disposing) method protected virtual to support inheritance.

To follow the standard dispose pattern and allow derived classes to override the disposal logic, Dispose(bool disposing) should be protected virtual.

Apply this diff:

-protected void Dispose(bool disposing)
+protected virtual void Dispose(bool disposing)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected void Dispose(bool disposing)
{
protected virtual void Dispose(bool disposing)
{

if (Disposed) { return; }
if (disposing)
{
OnDisposeManagedResources();
}
OnDisposeUnmanagedResources();
Disposed = true;
}
Comment on lines +168 to +175
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add synchronization to the Dispose(bool disposing) method for thread safety.

The Dispose(bool disposing) method is not thread-safe. Simultaneous calls to Dispose from multiple threads could lead to inconsistent state or resource leaks. Add synchronization to ensure that disposal is handled safely.

Apply this diff to add locking:

protected virtual void Dispose(bool disposing)
{
+    lock (this)
+    {
        if (Disposed) { return; }
        if (disposing)
        {
            OnDisposeManagedResources();
        }
        OnDisposeUnmanagedResources();
        Disposed = true;
+    }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Disposed) { return; }
if (disposing)
{
OnDisposeManagedResources();
}
OnDisposeUnmanagedResources();
Disposed = true;
}
protected virtual void Dispose(bool disposing)
{
lock (this)
{
if (Disposed) { return; }
if (disposing)
{
OnDisposeManagedResources();
}
OnDisposeUnmanagedResources();
Disposed = true;
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<ProjectGuid>0d0bdf91-e7c7-4cb4-a39d-e1a5374c5602</ProjectGuid>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -10,7 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Cuemon.Core" Version="9.0.0-preview.9" />
<PackageReference Include="Cuemon.Core" Version="9.0.0-preview.9" PrivateAssets="compile" />
<PackageReference Include="xunit.assert" Version="2.9.2" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Codebelt.Extensions.Xunit/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected Test(ITestOutputHelper output = null, Type callerType = null)
/// <summary>
/// Gets a value indicating whether <see cref="TestOutput"/> has a reference to an implementation of <see cref="ITestOutputHelper"/>.
/// </summary>
/// <value><c>true</c> if this instance has has a reference to an implementation of <see cref="ITestOutputHelper"/>; otherwise, <c>false</c>.</value>
/// <value><c>true</c> if this instance has a reference to an implementation of <see cref="ITestOutputHelper"/>; otherwise, <c>false</c>.</value>
protected bool HasTestOutput => TestOutput != null;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Cuemon.AspNetCore" Version="9.0.0-preview.9" />
<PackageReference Include="Cuemon.Extensions.AspNetCore" Version="9.0.0-preview.9" />
<PackageReference Include="Cuemon.Extensions.IO" Version="9.0.0-preview.9" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.ComponentModel;
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;

namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore
{
public class ServiceCollectionExtensionsTest : Test
{
public ServiceCollectionExtensionsTest(ITestOutputHelper output) : base(output)
{
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void AddFakeHttpContextAccessor_ShouldAddService(ServiceLifetime lifetime)
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddFakeHttpContextAccessor(lifetime);
var serviceProvider = services.BuildServiceProvider();
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();

// Assert
Assert.NotNull(httpContextAccessor);
Assert.IsType<FakeHttpContextAccessor>(httpContextAccessor);
}

[Fact]
public void AddFakeHttpContextAccessor_ShouldThrowInvalidEnumArgumentException_ForInvalidLifetime()
{
// Arrange
var services = new ServiceCollection();
var invalidLifetime = (ServiceLifetime)999;

// Act & Assert
Assert.Throws<InvalidEnumArgumentException>(() => services.AddFakeHttpContextAccessor(invalidLifetime));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Cuemon.Core" Version="9.0.0-preview.9" />
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Add PrivateAssets="compile" to Cuemon.Core Package Reference

The Cuemon.Core package (version 9.0.0-preview.9) is actively used within the test project, as indicated by the using Cuemon.Messaging; statements. To align with the PR objectives and ensure proper isolation, please add the PrivateAssets="compile" attribute to the <PackageReference>:

<PackageReference Include="Cuemon.Core" Version="9.0.0-preview.9" PrivateAssets="compile" />

This change will prevent the package from flowing to projects that consume this test project, maintaining encapsulation of dependencies.

🔗 Analysis chain

Verify compatibility and consider adding PrivateAssets="compile"

The addition of the Cuemon.Core package (version 9.0.0-preview.9) aligns with the PR objectives for updating package references. However, there are a few points to consider:

  1. Ensure that this preview version is compatible with other packages and the project's target framework.
  2. The PR objectives mention adding PrivateAssets="compile" to the Cuemon.Core package reference, but this attribute is not present in the current change. Consider adding it if it's still applicable.
  3. As this is a test project, using a preview version is generally acceptable, but be aware of potential instability or changes in future releases.

To verify the compatibility and usage of this package, you can run the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for Cuemon.Core usage and verify target framework compatibility

# Check for Cuemon.Core usage in the test project
echo "Checking for Cuemon.Core usage:"
rg --type csharp "using Cuemon" test/Codebelt.Extensions.Xunit.Hosting.Tests

# Verify target framework in the project file
echo "Verifying target framework:"
rg "<TargetFramework>" test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj

# Check other package versions for consistency
echo "Checking other package versions:"
rg "<PackageReference Include=" test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj

Length of output: 1060

<PackageReference Include="Xunit.Priority" Version="1.1.6" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ public UnmanagedDisposable()
Dispose(false);
}


protected override void OnDisposeManagedResources()
{

Expand Down