Skip to content

Commit 8240ee9

Browse files
committed
feat: add Moq package and enhance tests for DependencyGraphGenerator and DataExporter
1 parent 7bf6bb7 commit 8240ee9

7 files changed

Lines changed: 182 additions & 45 deletions

File tree

CodeLineCounter.Tests/CodeLineCounter.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PrivateAssets>all</PrivateAssets>
1616
</PackageReference>
1717
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
18+
<PackageReference Include="Moq" Version="4.20.72" />
1819
<PackageReference Include="xunit" Version="2.9.2" />
1920
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
2021
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

CodeLineCounter.Tests/DataExporterTests.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CodeLineCounter.Models;
22
using CodeLineCounter.Utils;
3+
using Moq;
34

45
namespace CodeLineCounter.Tests
56
{
@@ -212,7 +213,7 @@ public void get_file_duplications_count_uses_current_dir_for_null_solution_path(
212213

213214
// Successfully exports dependencies to specified format (CSV/JSON) and creates DOT file
214215
[Fact]
215-
public void export_dependencies_creates_files_in_correct_formats()
216+
public async Task export_dependencies_creates_files_in_correct_formats()
216217
{
217218
// Arrange
218219
var dependencies = new List<DependencyRelation>
@@ -224,7 +225,7 @@ public void export_dependencies_creates_files_in_correct_formats()
224225
var format = CoreUtils.ExportFormat.JSON;
225226

226227
// Act
227-
DataExporter.ExportDependencies(testFilePath, dependencies, format);
228+
await DataExporter.ExportDependencies(testFilePath, dependencies, format);
228229

229230
// Assert
230231
string expectedJsonPath = CoreUtils.GetExportFileNameWithExtension(testFilePath, format);
@@ -240,7 +241,7 @@ public void export_dependencies_creates_files_in_correct_formats()
240241
File.Delete(expectedJsonPath);
241242
}
242243

243-
if (File.Exists(expectedDotPath))
244+
if (File.Exists(expectedDotPath))
244245
{
245246
File.Delete(expectedDotPath);
246247
}
@@ -249,12 +250,49 @@ public void export_dependencies_creates_files_in_correct_formats()
249250
{
250251
throw new IOException($"Error deleting files: {ex.Message}", ex);
251252
}
252-
catch (UnauthorizedAccessException ex)
253+
catch (UnauthorizedAccessException ex)
253254
{
254255
throw new UnauthorizedAccessException($"Access denied while deleting files: {ex.Message}", ex);
255256
}
256257
}
257258

259+
// Throws ArgumentException when file path is null
260+
[Fact]
261+
public void export_collection_throws_when_filepath_null()
262+
{
263+
// Arrange
264+
string? filePath = null;
265+
var testData = new List<TestClass> { new TestClass { Id = 1, Name = "Test" } };
266+
var format = CoreUtils.ExportFormat.CSV;
267+
268+
// Act & Assert
269+
var exception = Assert.Throws<ArgumentException>(() =>
270+
DataExporter.ExportCollection(filePath, testData, format));
271+
Assert.Equal("File path cannot be null or empty (Parameter 'filePath')", exception.Message);
272+
}
273+
274+
// ExportCollection throws IOException when there is an IO error during export
275+
// [Fact]
276+
// public void export_collection_throws_ioexception_on_io_error()
277+
// {
278+
// // Arrange
279+
// var filePath = "invalid_path/test.csv";
280+
// var testData = new List<TestClass> { new TestClass() };
281+
// var format = CoreUtils.ExportFormat.CSV;
282+
283+
// // Mock the export strategy to throw an IOException
284+
// var mockExportStrategy = new Mock<IExportStrategy>();
285+
// mockExportStrategy
286+
// .Setup(strategy => strategy.Export(It.IsAny<string>(), It.IsAny<IEnumerable<TestClass>>()))
287+
// .Throws(new IOException("IO error"));
288+
289+
// DataExporter.ExportStrategies[format] = mockExportStrategy.Object;
290+
291+
// // Act & Assert
292+
// Assert.Throws<IOException>(() =>
293+
// DataExporter.ExportCollection(filePath, testData, format));
294+
// }
295+
258296
protected virtual void Dispose(bool disposing)
259297
{
260298
if (!_disposed)

CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using CodeLineCounter.Models;
22
using CodeLineCounter.Services;
3+
using DotNetGraph.Core;
4+
using DotNetGraph.Extensions;
35

46
namespace CodeLineCounter.Tests
57
{
@@ -18,7 +20,9 @@ public async Task generate_graph_with_valid_dependencies_creates_dot_file()
1820
string outputPath = Path.Combine(Path.GetTempPath(), "test_graph.dot");
1921

2022
// Act
21-
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
23+
24+
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
25+
await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph);
2226

2327
// Assert
2428
Assert.True(File.Exists(outputPath));
@@ -39,7 +43,8 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
3943
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");
4044

4145
// Act
42-
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
46+
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
47+
await DependencyGraphGenerator.CompileGraphAndWriteToFile(outputPath, graph);
4348

4449
// Assert
4550
Assert.True(File.Exists(outputPath));
@@ -50,6 +55,34 @@ public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
5055
File.Delete(outputPath);
5156
}
5257

58+
[Fact]
59+
public void create_node_sets_correct_fillcolor_and_style_incoming_greater()
60+
{
61+
var vertexInfo = new Dictionary<string, (int incoming, int outgoing)>
62+
{
63+
{ "TestVertex", (3, 2) }
64+
};
65+
66+
DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex");
67+
68+
Assert.Equal(DotColor.MediumSeaGreen.ToHexString(), node.FillColor.Value);
69+
Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value);
70+
}
71+
72+
[Fact]
73+
public void create_node_sets_correct_fillcolor_and_style_incoming_lower()
74+
{
75+
var vertexInfo = new Dictionary<string, (int incoming, int outgoing)>
76+
{
77+
{ "TestVertex", (3, 4) }
78+
};
79+
80+
DotNode node = DependencyGraphGenerator.CreateNode(vertexInfo, "TestVertex");
81+
82+
Assert.Equal(DotColor.Salmon.ToHexString(), node.FillColor.Value);
83+
Assert.Equal(DotNodeStyle.Filled.FlagsToString(), node.Style.Value);
84+
}
85+
5386

5487
}
5588
}

CodeLineCounter.Tests/FileUtilsTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,29 @@ public void GetBasePath_Should_Return_NonEmptyString()
3131
Assert.False(string.IsNullOrEmpty(basePath), "Base path should not be null or empty.");
3232
Assert.True(Directory.Exists(basePath), "Base path should be a valid directory.");
3333
}
34+
35+
[Fact]
36+
public void get_solution_files_throws_exception_for_nonexistent_directory()
37+
{
38+
// Arrange
39+
var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
40+
41+
// Act & Assert
42+
Assert.Throws<UnauthorizedAccessException>(() => FileUtils.GetSolutionFiles(nonExistentPath));
43+
}
44+
45+
// Throws UnauthorizedAccessException when solution file does not exist
46+
[Fact]
47+
public void get_project_files_throws_when_file_not_exists()
48+
{
49+
// Arrange
50+
var nonExistentPath = "nonexistent.sln";
51+
52+
// Act & Assert
53+
var exception = Assert.Throws<UnauthorizedAccessException>(() =>
54+
FileUtils.GetProjectFiles(nonExistentPath));
55+
56+
Assert.Contains(nonExistentPath, exception.Message);
57+
}
3458
}
3559
}

CodeLineCounter.Tests/SolutionAnalyzerTests.cs

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,40 @@ namespace CodeLineCounter.Tests.Services
77
public class SolutionAnalyzerTest
88
{
99

10+
[Fact]
11+
public void analyze_and_export_solution_succeeds_with_valid_inputs()
12+
{
13+
// Arrange
14+
var basePath = FileUtils.GetBasePath();
15+
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
16+
solutionPath = Path.Combine(solutionPath, "CodeLineCounter.sln");
17+
var sw = new StringWriter();
18+
Console.SetOut(sw);
19+
var verbose = false;
20+
var format = CoreUtils.ExportFormat.JSON;
21+
22+
// Act & Assert
23+
var exception = Record.Exception(() =>
24+
SolutionAnalyzer.AnalyzeAndExportSolution(solutionPath, verbose, format));
25+
26+
Assert.Null(exception);
27+
}
28+
29+
[Fact]
30+
public void analyze_and_export_solution_throws_on_invalid_path()
31+
{
32+
// Arrange
33+
var invalidPath = "";
34+
var verbose = false;
35+
var format = CoreUtils.ExportFormat.JSON;
36+
37+
// Act & Assert
38+
var exception = Assert.Throws<UnauthorizedAccessException>(() =>
39+
SolutionAnalyzer.AnalyzeAndExportSolution(invalidPath, verbose, format));
40+
41+
Assert.Contains("Access to the path '' is denied.", exception.Message);
42+
}
43+
1044
[Fact]
1145
public void PerformAnalysis_ShouldReturnCorrectAnalysisResult()
1246
{
@@ -106,7 +140,7 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
106140
SolutionAnalyzer.OutputDetailedMetrics(metrics, projectTotals);
107141

108142
// Assert
109-
var expectedOutput =
143+
var expectedOutput =
110144
$"Project Project1 (/path/to/project1) - Namespace Namespace1 in file File1.cs (/path/to/project1/File1.cs) has 100 lines of code and a cyclomatic complexity of 10.{Environment.NewLine}" +
111145
$"Project Project2 (/path/to/project2) - Namespace Namespace2 in file File2.cs (/path/to/project2/File2.cs) has 200 lines of code and a cyclomatic complexity of 20.{Environment.NewLine}" +
112146
$"Project Project1 has 100 total lines of code.{Environment.NewLine}" +
@@ -116,39 +150,39 @@ public void OutputDetailedMetrics_ShouldPrintMetricsAndProjectTotals()
116150
}
117151
}
118152

119-
// Export metrics, duplications and dependencies data in parallel for valid input
120-
[Fact]
121-
public void export_results_with_valid_input_exports_all_files()
122-
{
123-
// Arrange
124-
var result = new AnalysisResult
125-
{
126-
SolutionFileName = "TestSolution",
127-
Metrics = new List<NamespaceMetrics>(),
128-
ProjectTotals = new Dictionary<string, int >(),
129-
TotalLines = 1000,
130-
DuplicationMap = new List<DuplicationCode>(),
131-
DependencyList = new List<DependencyRelation>()
132-
};
133-
134-
var basePath = FileUtils.GetBasePath();
135-
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
136-
137-
solutionPath = Path.Combine(solutionPath, "TestSolution.sln");
138-
var format = CoreUtils.ExportFormat.CSV;
139-
140-
// Act
141-
using (var sw = new StringWriter())
142-
{
143-
Console.SetOut(sw);
144-
SolutionAnalyzer.ExportResults(result, solutionPath, format);
145-
}
153+
// Export metrics, duplications and dependencies data in parallel for valid input
154+
[Fact]
155+
public void export_results_with_valid_input_exports_all_files()
156+
{
157+
// Arrange
158+
var result = new AnalysisResult
159+
{
160+
SolutionFileName = "TestSolution",
161+
Metrics = new List<NamespaceMetrics>(),
162+
ProjectTotals = new Dictionary<string, int>(),
163+
TotalLines = 1000,
164+
DuplicationMap = new List<DuplicationCode>(),
165+
DependencyList = new List<DependencyRelation>()
166+
};
167+
168+
var basePath = FileUtils.GetBasePath();
169+
var solutionPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", ".."));
170+
171+
solutionPath = Path.Combine(solutionPath, "TestSolution.sln");
172+
var format = CoreUtils.ExportFormat.CSV;
146173

147-
// Assert
148-
Assert.True(File.Exists("TestSolution-CodeMetrics.csv"));
149-
Assert.True(File.Exists("TestSolution-CodeDuplications.csv"));
150-
Assert.True(File.Exists("TestSolution-CodeDependencies.csv"));
151-
}
174+
// Act
175+
using (var sw = new StringWriter())
176+
{
177+
Console.SetOut(sw);
178+
SolutionAnalyzer.ExportResults(result, solutionPath, format);
179+
}
180+
181+
// Assert
182+
Assert.True(File.Exists("TestSolution-CodeMetrics.csv"));
183+
Assert.True(File.Exists("TestSolution-CodeDuplications.csv"));
184+
Assert.True(File.Exists("TestSolution-CodeDependencies.csv"));
185+
}
152186

153187
}
154188
}

CodeLineCounter/Services/DependencyGraphGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace CodeLineCounter.Services
1010
{
1111
public static class DependencyGraphGenerator
1212
{
13-
public static async Task GenerateGraph(List<DependencyRelation> dependencies, string outputPath, string? filterNamespace = null, string? filterAssembly = null)
13+
public static DotGraph GenerateGraphOnly(List<DependencyRelation> dependencies, string? filterNamespace = null, string? filterAssembly = null)
1414
{
1515
var filteredDependencies = dependencies;
1616
filteredDependencies = FilterNamespaceFromDependencies(dependencies, filterNamespace, filteredDependencies);
@@ -51,7 +51,7 @@ public static async Task GenerateGraph(List<DependencyRelation> dependencies, st
5151
graph.Elements.Add(edge);
5252
}
5353

54-
await CompileGraphAndWriteToFile(outputPath, graph);
54+
return graph;
5555
}
5656

5757
private static DotEdge CreateEdge(DependencyRelation dep)
@@ -80,7 +80,7 @@ private static DotSubgraph CreateCluster(KeyValuePair<string, List<string>> nsGr
8080
return cluster;
8181
}
8282

83-
private static DotNode CreateNode(Dictionary<string, (int incoming, int outgoing)> vertexInfo, string vertex)
83+
public static DotNode CreateNode(Dictionary<string, (int incoming, int outgoing)> vertexInfo, string vertex)
8484
{
8585
var info = vertexInfo[vertex];
8686
var node = new DotNode();
@@ -142,7 +142,7 @@ private static void GroupByNamespace(Dictionary<string, (int incoming, int outgo
142142
}
143143
}
144144

145-
private static async Task CompileGraphAndWriteToFile(string outputPath, DotGraph graph)
145+
public static async Task CompileGraphAndWriteToFile(string outputPath, DotGraph graph)
146146
{
147147
await using var writer = new StringWriter();
148148
var options = new CompilationOptions

CodeLineCounter/Utils/DataExporter.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CodeLineCounter.Models;
22
using CodeLineCounter.Services;
3+
using DotNetGraph.Core;
34

45

56
namespace CodeLineCounter.Utils
@@ -13,14 +14,19 @@ public static class DataExporter
1314
{ CoreUtils.ExportFormat.JSON, new JsonExportStrategy() }
1415
};
1516

17+
public static Dictionary<CoreUtils.ExportFormat, IExportStrategy> ExportStrategies
18+
{
19+
get { return _exportStrategies; }
20+
}
21+
1622
public static void Export<T>(string filePath, T data, CoreUtils.ExportFormat format) where T : class
1723
{
1824
ArgumentNullException.ThrowIfNull(data);
1925

2026
ExportCollection(filePath, [data], format);
2127
}
2228

23-
public static void ExportCollection<T>(string filePath, IEnumerable<T> data, CoreUtils.ExportFormat format) where T : class
29+
public static void ExportCollection<T>(string? filePath, IEnumerable<T> data, CoreUtils.ExportFormat format) where T : class
2430
{
2531
if (string.IsNullOrEmpty(filePath))
2632
throw new ArgumentException("File path cannot be null or empty", nameof(filePath));
@@ -48,7 +54,8 @@ public static async Task ExportDependencies(string filePath, List<DependencyRela
4854
string outputFilePath = CoreUtils.GetExportFileNameWithExtension(filePath, format);
4955
ExportCollection(outputFilePath, dependencies, format);
5056

51-
await DependencyGraphGenerator.GenerateGraph(dependencies, Path.ChangeExtension(outputFilePath, ".dot"));
57+
DotGraph graph = DependencyGraphGenerator.GenerateGraphOnly(dependencies);
58+
await DependencyGraphGenerator.CompileGraphAndWriteToFile(Path.ChangeExtension(outputFilePath, ".dot"), graph);
5259
}
5360

5461
public static void ExportMetrics(string filePath, List<NamespaceMetrics> metrics,

0 commit comments

Comments
 (0)