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
5 changes: 5 additions & 0 deletions src/PipeForge.Tests.Steps/IDisposablePipelineStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace PipeForge.Tests.Steps;

public interface IDisposablePipelineStep : IPipelineStep<SampleContext>, IDisposable
{
}
31 changes: 31 additions & 0 deletions src/PipeForge.Tests.Steps/IGenericPipelineStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace PipeForge.Tests.Steps;

public interface IGenericPipelineStep<T> : IPipelineStep<T> where T : class
{
}

public class GenericPipelineStep<T> : IGenericPipelineStep<T> where T : class
{
public string? Description => throw new NotImplementedException();

public bool MayShortCircuit => throw new NotImplementedException();

public string Name => throw new NotImplementedException();

public string? ShortCircuitCondition => throw new NotImplementedException();

public Task InvokeAsync(T context, PipelineDelegate<T> next, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}

[PipelineStep(0)]
public class ClosedGenericPipelineStep : GenericPipelineStep<SampleContext>, IGenericPipelineStep<SampleContext>
{
// This class is a closed generic implementation of IGenericPipelineStep<SampleContext>.
}

public class OpenGenericPipelineStep<T> : ClosedGenericPipelineStep
{
}
5 changes: 5 additions & 0 deletions src/PipeForge.Tests.Steps/INotImplementedRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace PipeForge.Tests.Steps;

public interface INotImplementedRunner : IPipelineRunner<SampleContext, ISampleContextStep>
{ }

11 changes: 11 additions & 0 deletions src/PipeForge.Tests.Steps/ISampleContextRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace PipeForge.Tests.Steps;

public interface ISampleContextRunner : IPipelineRunner<SampleContext, ISampleContextStep>
{ }

public class SampleContextRunner : PipelineRunner<SampleContext, ISampleContextStep>, ISampleContextRunner
{
public SampleContextRunner(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
12 changes: 12 additions & 0 deletions src/PipeForge.Tests.Steps/SampleContextStepB.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace PipeForge.Tests.Steps;

[PipelineStep(3)]
public class SampleContextStepB : SampleContextStep
{
public static readonly string StepName = "B";

public SampleContextStepB()
{
Name = StepName;
}
}
12 changes: 12 additions & 0 deletions src/PipeForge.Tests.Steps/SampleContextStepC.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace PipeForge.Tests.Steps;

[PipelineStep(2)]
public class SampleContextStepC : SampleContextStep
{
public static readonly string StepName = "C";

public SampleContextStepC()
{
Name = StepName;
}
}
34 changes: 34 additions & 0 deletions src/PipeForge.Tests.Steps/SampleContextStepF.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace PipeForge.Tests.Steps;

[PipelineStep(4, TestConstants.Filter1)]
public class SampleContextStepF1 : SampleContextStep
{
public static readonly string StepName = "F";

public SampleContextStepF1()
{
Name = StepName;
}

public override Task InvokeAsync(SampleContext context, PipelineDelegate<SampleContext> next, CancellationToken cancellationToken = default)
{
context.AddStep(Name);
return Task.CompletedTask;
}
}

[PipelineStep(4, TestConstants.Filter2)]
public class SampleContextStepF2 : SampleContextStep
{
public static readonly string StepName = "F";

public SampleContextStepF2()
{
Name = StepName;
}

public override Task InvokeAsync(SampleContext context, PipelineDelegate<SampleContext> next, CancellationToken cancellationToken = default)
{
throw new NotImplementedException("This step should not be executed in this test.");
}
}
12 changes: 12 additions & 0 deletions src/PipeForge.Tests.Steps/SampleContextStepM.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace PipeForge.Tests.Steps;

[PipelineStep(4, TestConstants.Filter1, TestConstants.Filter2)]
public class SampleContextStepM : SampleContextStep
{
public static readonly string StepName = "M";

public SampleContextStepM()
{
Name = StepName;
}
}
12 changes: 12 additions & 0 deletions src/PipeForge.Tests.Steps/SampleContextStepZ.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace PipeForge.Tests.Steps;

[PipelineStep(100)]
public class SampleContextStepZ : SampleContextStep
{
public static readonly string StepName = "Z";

public SampleContextStepZ()
{
Name = StepName;
}
}
7 changes: 7 additions & 0 deletions src/PipeForge.Tests.Steps/TestConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace PipeForge.Tests.Steps;

public static class TestConstants
{
public const string Filter1 = "Filter1";
public const string Filter2 = "Filter2";
}
31 changes: 31 additions & 0 deletions src/PipeForge.Tests/DelegatePipelineStepTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using PipeForge.Tests.Steps;

namespace PipeForge.Tests;

public class DelegatePipelineStepTests
{
[Fact]
public void Constructor_ThrowsException_WhenActionIsNull()
{
var ex = Should.Throw<ArgumentNullException>(() =>
{
_ = new DelegatePipelineStep<SampleContext>(null!);
});
}

[Fact]
public async Task Execute_CallsAction_WhenInvoked()
{
var wasCalled = false;
PipelineDelegate<SampleContext> next = (_, _) => Task.CompletedTask;

var step = new DelegatePipelineStep<SampleContext>(async (context, d, ct) =>
{
wasCalled = true;
await Task.CompletedTask;
});

await step.InvokeAsync(new SampleContext(), next, CancellationToken.None);
wasCalled.ShouldBeTrue();
}
}
177 changes: 177 additions & 0 deletions src/PipeForge.Tests/Extensions/AssemblyExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using System.Reflection;
using PipeForge.Extensions;
using PipeForge.Tests.Steps;

namespace PipeForge.Tests.Extensions;

public class AssemblyExtensionsTests
{
private static readonly Assembly[] _assemblies = [typeof(SampleContext).Assembly];

[Fact]
public void FindClosedImplementationsOf_ThrowsException_WhenTypeIsNotAnInterface()
{
var ex = Should.Throw<ArgumentException>(() =>
{
_assemblies.FindClosedImplementationsOf<string>();
});

ex.Message.ShouldStartWith(string.Format(PipeForge.Extensions.AssemblyExtensions.MessageNotAnInterface, typeof(string).FullName));
}

[Fact]
public void FindClosedImplementationsOf_ReturnsAllClosedImplementations_ForValidInterface()
{
var result = _assemblies.FindClosedImplementationsOf<IPipelineStep<SampleContext>>();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.ShouldContain(t => t == typeof(SampleContextStepA)),
() => result.ShouldNotContain(t => t == typeof(SampleContextStep)),
() => result.ShouldNotContain(t => t == typeof(ISampleContextStep)),
() => result.ShouldNotContain(t => t == typeof(IDisposablePipelineStep)),
() => result.ShouldNotContain(t => t == typeof(OpenGenericPipelineStep<>)),
() => result.ShouldNotContain(t => t == typeof(GenericPipelineStep<>)),
() => result.ShouldNotContain(t => t == typeof(IGenericPipelineStep<>))
);
}

[Fact]
public void FindClosedImplementationsOf_ReturnsAllClosedImplementations_ForValidSpecificInterface()
{
var result = _assemblies.FindClosedImplementationsOf<ISampleContextStep>();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.ShouldContain(t => t == typeof(SampleContextStepA)),
() => result.ShouldNotContain(t => t == typeof(SampleContextStep)),
() => result.ShouldNotContain(t => t == typeof(ISampleContextStep)),
() => result.ShouldNotContain(t => t == typeof(IDisposablePipelineStep)),
() => result.ShouldNotContain(t => t == typeof(OpenGenericPipelineStep<>)),
() => result.ShouldNotContain(t => t == typeof(ClosedGenericPipelineStep)),
() => result.ShouldNotContain(t => t == typeof(GenericPipelineStep<>)),
() => result.ShouldNotContain(t => t == typeof(IGenericPipelineStep<>))
);
}

[Fact]
public void GetDescriptorsFor_ReturnsEmpty_WhenNoStepsFound()
{
var result = _assemblies.GetDescriptorsFor<IPipelineStep<string>>(null);
result.ShouldBeEmpty();
}

[Fact]
public void GetDescriptorsFor_ReturnsIEnumerable_ForValidStepInterface()
{
var result = _assemblies.GetDescriptorsFor<IPipelineStep<SampleContext>>(null).ToList();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.Count().ShouldBe(5),
() => result[0].ImplementationType.ShouldBe(typeof(ClosedGenericPipelineStep)),
() => result[1].ImplementationType.ShouldBe(typeof(SampleContextStepA)),
() => result[2].ImplementationType.ShouldBe(typeof(SampleContextStepC)),
() => result[3].ImplementationType.ShouldBe(typeof(SampleContextStepB)),
() => result[4].ImplementationType.ShouldBe(typeof(SampleContextStepZ))
);
}

[Fact]
public void GetDescriptorsFor_ReturnsIEnumerable_ForValidCustomStepInterface_WithNoFilters()
{
var result = _assemblies.GetDescriptorsFor<ISampleContextStep>(null).ToList();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.Count().ShouldBe(4),
() => result[0].ImplementationType.ShouldBe(typeof(SampleContextStepA)),
() => result[1].ImplementationType.ShouldBe(typeof(SampleContextStepC)),
() => result[2].ImplementationType.ShouldBe(typeof(SampleContextStepB)),
() => result[3].ImplementationType.ShouldBe(typeof(SampleContextStepZ))
);
}

[Fact]
public void GetDescriptorsFor_ReturnsIEnumerable_ForValidCustomStepInterface_WithSingleFilter1()
{
var result = _assemblies.GetDescriptorsFor<ISampleContextStep>([TestConstants.Filter1]).ToList();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.Count().ShouldBe(6),
() => result[0].ImplementationType.ShouldBe(typeof(SampleContextStepA)),
() => result[1].ImplementationType.ShouldBe(typeof(SampleContextStepC)),
() => result[2].ImplementationType.ShouldBe(typeof(SampleContextStepB)),
() => result[3].ImplementationType.ShouldBe(typeof(SampleContextStepF1)),
() => result[4].ImplementationType.ShouldBe(typeof(SampleContextStepM)),
() => result[5].ImplementationType.ShouldBe(typeof(SampleContextStepZ))
);
}

[Fact]
public void GetDescriptorsFor_ReturnsIEnumerable_ForValidCustomStepInterface_WithSingleFilter2()
{
var result = _assemblies.GetDescriptorsFor<ISampleContextStep>([TestConstants.Filter2]).ToList();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.Count().ShouldBe(6),
() => result[0].ImplementationType.ShouldBe(typeof(SampleContextStepA)),
() => result[1].ImplementationType.ShouldBe(typeof(SampleContextStepC)),
() => result[2].ImplementationType.ShouldBe(typeof(SampleContextStepB)),
() => result[3].ImplementationType.ShouldBe(typeof(SampleContextStepF2)),
() => result[4].ImplementationType.ShouldBe(typeof(SampleContextStepM)),
() => result[5].ImplementationType.ShouldBe(typeof(SampleContextStepZ))
);
}

[Fact]
public void GetDescriptorsFor_ReturnsIEnumerable_ForValidCustomStepInterface_WithMultipleFilters()
{
var result = _assemblies.GetDescriptorsFor<ISampleContextStep>([TestConstants.Filter2, TestConstants.Filter1]).ToList();
result.ShouldNotBeEmpty();

result.ShouldSatisfyAllConditions
(
() => result.Count().ShouldBe(7),
() => result[0].ImplementationType.ShouldBe(typeof(SampleContextStepA)),
() => result[1].ImplementationType.ShouldBe(typeof(SampleContextStepC)),
() => result[2].ImplementationType.ShouldBe(typeof(SampleContextStepB)),
() => result[3].ImplementationType.ShouldBe(typeof(SampleContextStepF1)),
() => result[4].ImplementationType.ShouldBe(typeof(SampleContextStepF2)),
() => result[5].ImplementationType.ShouldBe(typeof(SampleContextStepM)),
() => result[6].ImplementationType.ShouldBe(typeof(SampleContextStepZ))
);
}

[Fact]
public void SafeGetTypes_ReturnsTypes_WhenDelegateSucceeds()
{
var expected = new[] { typeof(string), typeof(int) };
var result = PipeForge.Extensions.AssemblyExtensions.SafeGetTypes(() => expected);
result.ShouldBe(expected);
}

[Fact]
public void SafeGetTypes_ReturnsFilteredTypes_WhenReflectionTypeLoadExceptionThrown()
{
var types = new Type?[] { typeof(string), null, typeof(int) };
var ex = new ReflectionTypeLoadException(types, []);
var result = PipeForge.Extensions.AssemblyExtensions.SafeGetTypes(() => throw ex);
result.ShouldBe([typeof(string), typeof(int)]);
}

[Fact]
public void SafeGetTypes_ReturnsEmpty_WhenUnexpectedExceptionThrown()
{
var result = PipeForge.Extensions.AssemblyExtensions.SafeGetTypes(() => throw new InvalidOperationException());
result.ShouldBeEmpty();
}
}
Loading