Skip to content

Commit 3439578

Browse files
committed
Refactor and add unit tests for Csv plugin
Upgraded solution to Visual Studio 18 and reorganized projects under `src` and `tests` folders. Added a new unit test project (`FlowSynx.Plugins.Csv.UnitTests`) and configured `InternalsVisibleTo` for testing. Refactored `CsvPlugin` and operations (`Filter`, `Map`, `Read`) into dedicated namespaces. Fixed a bug in `FilterOperation` where unfiltered records were used in CSV output. Added comprehensive unit tests for plugin metadata, operations, helpers, and extensions. Introduced tests for filtering, mapping, and reading CSV data. Ensured better test coverage with fake implementations and structured data handling. Improved maintainability and modularity by restructuring code and adding tests for core functionality. #20
1 parent 4b7b4a3 commit 3439578

20 files changed

Lines changed: 426 additions & 15 deletions

Plugins.Csv.sln

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.12.35728.132 d17.12
3+
# Visual Studio Version 18
4+
VisualStudioVersion = 18.0.11222.15
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowSynx.Plugins.Csv", "src\FlowSynx.Plugins.Csv.csproj", "{ECD2DE21-BABA-4282-BD92-716CB6754967}"
77
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{090AF7A6-591F-434D-980A-C5CEF59FC206}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AB101817-0D53-4EED-8CD4-9150EA5CF692}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowSynx.Pluigns.Csv.UnitTests", "tests\FlowSynx.Pluigns.Csv.UnitTests.csproj", "{535535BE-029E-4E1A-96F2-D14DE17A08E8}"
13+
EndProject
814
Global
915
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1016
Debug|Any CPU = Debug|Any CPU
@@ -15,8 +21,19 @@ Global
1521
{ECD2DE21-BABA-4282-BD92-716CB6754967}.Debug|Any CPU.Build.0 = Debug|Any CPU
1622
{ECD2DE21-BABA-4282-BD92-716CB6754967}.Release|Any CPU.ActiveCfg = Release|Any CPU
1723
{ECD2DE21-BABA-4282-BD92-716CB6754967}.Release|Any CPU.Build.0 = Release|Any CPU
24+
{535535BE-029E-4E1A-96F2-D14DE17A08E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{535535BE-029E-4E1A-96F2-D14DE17A08E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{535535BE-029E-4E1A-96F2-D14DE17A08E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{535535BE-029E-4E1A-96F2-D14DE17A08E8}.Release|Any CPU.Build.0 = Release|Any CPU
1828
EndGlobalSection
1929
GlobalSection(SolutionProperties) = preSolution
2030
HideSolutionNode = FALSE
2131
EndGlobalSection
32+
GlobalSection(NestedProjects) = preSolution
33+
{ECD2DE21-BABA-4282-BD92-716CB6754967} = {090AF7A6-591F-434D-980A-C5CEF59FC206}
34+
{535535BE-029E-4E1A-96F2-D14DE17A08E8} = {AB101817-0D53-4EED-8CD4-9150EA5CF692}
35+
EndGlobalSection
36+
GlobalSection(ExtensibilityGlobals) = postSolution
37+
SolutionGuid = {E475AAEF-3830-486C-A5B3-E92B6EBBE1C6}
38+
EndGlobalSection
2239
EndGlobal

src/CsvPlugin.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
using FlowSynx.PluginCore;
33
using FlowSynx.PluginCore.Extensions;
44
using FlowSynx.Plugins.Csv.Services;
5-
using FlowSynx.Plugins.Csv.Operations;
6-
using FlowSynx.Plugins.Csv.Parameters;
5+
using FlowSynx.Plugins.Csv.Operations.Read;
6+
using FlowSynx.Plugins.Csv.Operations.Map;
7+
using FlowSynx.Plugins.Csv.Operations.Filter;
78

89
namespace FlowSynx.Plugins.Csv;
910

src/FlowSynx.Plugins.Csv.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
<PackageReference Include="FlowSynx.PluginCore" Version="1.4.0" />
1717
</ItemGroup>
1818

19+
<ItemGroup>
20+
<InternalsVisibleTo Include="FlowSynx.Pluigns.Csv.UnitTests" />
21+
</ItemGroup>
22+
1923
<ItemGroup>
2024
<Compile Update="Resources.Designer.cs">
2125
<DesignTime>True</DesignTime>
@@ -40,4 +44,9 @@
4044
</None>
4145
</ItemGroup>
4246

47+
<ItemGroup>
48+
<Folder Include="Parameters\" />
49+
<Folder Include="Properties\" />
50+
</ItemGroup>
51+
4352
</Project>
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
using FlowSynx.PluginCore;
44
using FlowSynx.Plugins.Csv.Helpers;
55
using FlowSynx.Plugins.Csv.Models;
6-
using FlowSynx.Plugins.Csv.Parameters;
76
using FlowSynx.Plugins.Csv.Services;
87
using System.Dynamic;
98
using System.Globalization;
109
using System.Text.Json;
1110

12-
namespace FlowSynx.Plugins.Csv.Operations;
11+
namespace FlowSynx.Plugins.Csv.Operations.Filter;
1312

1413
internal class FilterOperation : IPluginOperation<FilterParameters, PluginContext>
1514
{
@@ -28,7 +27,9 @@ internal class FilterOperation : IPluginOperation<FilterParameters, PluginContex
2827
if (!isJson)
2928
throw new ArgumentException("Invalid filter structure.");
3029

31-
var rootGroup = JsonSerializer.Deserialize<FilterGroup>(parameters.Filters?.ToString()!);
30+
// Use case-insensitive property name matching so JSON like {"logic":..., "filters":...} binds correctly
31+
var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
32+
var rootGroup = JsonSerializer.Deserialize<FilterGroup>(parameters.Filters?.ToString()!, jsonOptions);
3233

3334
if (rootGroup is null)
3435
throw new ArgumentException("Invalid filter structure.");
@@ -60,7 +61,7 @@ internal class FilterOperation : IPluginOperation<FilterParameters, PluginContex
6061
}).ToList();
6162

6263
var result = records.Where(row => EvaluateFilterGroup(row, rootGroup)).ToList();
63-
var csvString = await helper.ToCsvStringAsync(records, parameters.Delimiter, parameters.IgnoreBlankLines, parameters.HasHeader);
64+
var csvString = await helper.ToCsvStringAsync(result, parameters.Delimiter, parameters.IgnoreBlankLines, parameters.HasHeader);
6465

6566

6667

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using FlowSynx.PluginCore;
22

3-
namespace FlowSynx.Plugins.Csv.Parameters;
3+
namespace FlowSynx.Plugins.Csv.Operations.Filter;
44

55
public class FilterParameters
66
{
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
using CsvHelper.Configuration;
33
using FlowSynx.PluginCore;
44
using FlowSynx.Plugins.Csv.Helpers;
5-
using FlowSynx.Plugins.Csv.Parameters;
65
using FlowSynx.Plugins.Csv.Services;
76
using System.Dynamic;
87
using System.Globalization;
98

10-
namespace FlowSynx.Plugins.Csv.Operations;
9+
namespace FlowSynx.Plugins.Csv.Operations.Map;
1110

1211
internal class MapOperation : IPluginOperation<MapParameters, PluginContext>
1312
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using FlowSynx.PluginCore;
22

3-
namespace FlowSynx.Plugins.Csv.Parameters;
3+
namespace FlowSynx.Plugins.Csv.Operations.Map;
44

55
public class MapParameters
66
{
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
using CsvHelper.Configuration;
33
using FlowSynx.PluginCore;
44
using FlowSynx.Plugins.Csv.Helpers;
5-
using FlowSynx.Plugins.Csv.Parameters;
65
using FlowSynx.Plugins.Csv.Services;
76
using System.Dynamic;
87
using System.Globalization;
98

10-
namespace FlowSynx.Plugins.Csv.Operations;
9+
namespace FlowSynx.Plugins.Csv.Operations.Read;
1110

1211
internal class ReadOperation : IPluginOperation<ReadParameters, PluginContext>
1312
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using FlowSynx.PluginCore;
22

3-
namespace FlowSynx.Plugins.Csv.Parameters;
3+
namespace FlowSynx.Plugins.Csv.Operations.Read;
44

55
public class ReadParameters
66
{

tests/CsvPluginTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using FlowSynx.PluginCore;
2+
using FlowSynx.Plugins.Csv;
3+
using FlowSynx.Plugins.Csv.Services;
4+
using Xunit;
5+
6+
namespace FlowSynx.Pluign.Csv.UnitTests;
7+
8+
public class CsvPluginTests
9+
{
10+
private class FakeGuidProvider : IGuidProvider
11+
{
12+
public Guid NewGuid() => Guid.Parse("00000000-0000-0000-0000-000000000123");
13+
}
14+
15+
private class FakeReflectionGuard : IReflectionGuard
16+
{
17+
public bool IsCalledViaReflection() => false;
18+
}
19+
20+
private class DummyLogger : IPluginLogger
21+
{
22+
public void Log(PluginLoggerLevel level, string message) { }
23+
}
24+
25+
[Fact]
26+
public void Metadata_HasExpectedValues()
27+
{
28+
var plugin = new CsvPlugin();
29+
var md = plugin.Metadata;
30+
Assert.Equal("Csv", md.Name);
31+
Assert.Equal("FlowSynx", md.CompanyName);
32+
Assert.Equal("flowsynx.png", md.Icon);
33+
Assert.Equal("README.md", md.ReadMe);
34+
Assert.Equal(1, md.Version.Major);
35+
}
36+
37+
[Fact]
38+
public async Task InitializeAsync_SetsSpecifications()
39+
{
40+
var plugin = new CsvPlugin(new FakeGuidProvider(), new FakeReflectionGuard());
41+
await plugin.InitializeAsync(new DummyLogger(), new Dictionary<string, object?>());
42+
Assert.NotNull(plugin.Specifications);
43+
}
44+
45+
[Fact]
46+
public async Task ExecuteAsync_BeforeInit_Throws()
47+
{
48+
var plugin = new CsvPlugin(new FakeGuidProvider(), new FakeReflectionGuard());
49+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
50+
plugin.ExecuteAsync("read", new PluginParameters(), CancellationToken.None));
51+
Assert.Contains("not initialized", ex.Message);
52+
}
53+
54+
[Fact]
55+
public async Task ExecuteAsync_UnsupportedOperation_Throws()
56+
{
57+
var plugin = new CsvPlugin(new FakeGuidProvider(), new FakeReflectionGuard());
58+
await plugin.InitializeAsync(new DummyLogger(), null);
59+
var ex = await Assert.ThrowsAsync<NotSupportedException>(() =>
60+
plugin.ExecuteAsync("unknown", new PluginParameters(), CancellationToken.None));
61+
Assert.Contains("not supported", ex.Message);
62+
}
63+
}

0 commit comments

Comments
 (0)