Skip to content

Commit a2bf826

Browse files
Increase code coverage for PathHelpers and Validation (#54)
* Initial plan * Add tests for PathHelpers and Validation coverage Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> * Fix tests and improve code coverage Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> * Address code review feedback - remove unused using and refactor tests Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
1 parent 13b3998 commit a2bf826

File tree

2 files changed

+337
-0
lines changed

2 files changed

+337
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) DEMA Consulting
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all
11+
// copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
// SOFTWARE.
20+
21+
namespace DemaConsulting.BuildMark.Tests;
22+
23+
/// <summary>
24+
/// Tests for the PathHelpers class.
25+
/// </summary>
26+
[TestClass]
27+
public class PathHelpersTests
28+
{
29+
/// <summary>
30+
/// Test that SafePathCombine correctly combines valid paths.
31+
/// </summary>
32+
[TestMethod]
33+
public void PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly()
34+
{
35+
// Arrange
36+
var basePath = "/home/user/project";
37+
var relativePath = "subfolder/file.txt";
38+
39+
// Act
40+
var result = PathHelpers.SafePathCombine(basePath, relativePath);
41+
42+
// Assert
43+
Assert.AreEqual(Path.Combine(basePath, relativePath), result);
44+
}
45+
46+
/// <summary>
47+
/// Test that SafePathCombine throws ArgumentException for path traversal with double dots.
48+
/// </summary>
49+
[TestMethod]
50+
public void PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException()
51+
{
52+
// Arrange
53+
var basePath = "/home/user/project";
54+
var relativePath = "../etc/passwd";
55+
56+
// Act & Assert
57+
var exception = Assert.Throws<ArgumentException>(() =>
58+
PathHelpers.SafePathCombine(basePath, relativePath));
59+
Assert.Contains("Invalid path component", exception.Message);
60+
}
61+
62+
/// <summary>
63+
/// Test that SafePathCombine throws ArgumentException for path with double dots in middle.
64+
/// </summary>
65+
[TestMethod]
66+
public void PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException()
67+
{
68+
// Arrange
69+
var basePath = "/home/user/project";
70+
var relativePath = "subfolder/../../../etc/passwd";
71+
72+
// Act & Assert
73+
var exception = Assert.Throws<ArgumentException>(() =>
74+
PathHelpers.SafePathCombine(basePath, relativePath));
75+
Assert.Contains("Invalid path component", exception.Message);
76+
}
77+
78+
/// <summary>
79+
/// Test that SafePathCombine throws ArgumentException for absolute paths.
80+
/// </summary>
81+
[TestMethod]
82+
public void PathHelpers_SafePathCombine_AbsolutePath_ThrowsArgumentException()
83+
{
84+
// Test Unix absolute path
85+
var unixBasePath = "/home/user/project";
86+
var unixRelativePath = "/etc/passwd";
87+
var unixException = Assert.Throws<ArgumentException>(() =>
88+
PathHelpers.SafePathCombine(unixBasePath, unixRelativePath));
89+
Assert.Contains("Invalid path component", unixException.Message);
90+
91+
// Test Windows absolute path (only on Windows since Windows paths may not be rooted on Unix)
92+
if (OperatingSystem.IsWindows())
93+
{
94+
var windowsBasePath = "C:\\Users\\project";
95+
var windowsRelativePath = "C:\\Windows\\System32\\file.txt";
96+
var windowsException = Assert.Throws<ArgumentException>(() =>
97+
PathHelpers.SafePathCombine(windowsBasePath, windowsRelativePath));
98+
Assert.Contains("Invalid path component", windowsException.Message);
99+
}
100+
}
101+
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Copyright (c) DEMA Consulting
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all
11+
// copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
// SOFTWARE.
20+
21+
using DemaConsulting.BuildMark.RepoConnectors;
22+
23+
namespace DemaConsulting.BuildMark.Tests;
24+
25+
/// <summary>
26+
/// Tests for the Validation class.
27+
/// </summary>
28+
[TestClass]
29+
public class ValidationTests
30+
{
31+
/// <summary>
32+
/// Test that Validation.Run writes TRX results file when specified.
33+
/// </summary>
34+
[TestMethod]
35+
public void Validation_Run_WithTrxResultsFile_WritesTrxFile()
36+
{
37+
// Arrange
38+
var tempDir = Path.Combine(Path.GetTempPath(), $"buildmark_test_{Guid.NewGuid()}");
39+
Directory.CreateDirectory(tempDir);
40+
41+
try
42+
{
43+
var trxFile = Path.Combine(tempDir, "results.trx");
44+
var args = new[] { "--validate", "--results", trxFile };
45+
46+
StringWriter? outputWriter = null;
47+
StringWriter? errorWriter = null;
48+
49+
try
50+
{
51+
// Capture console output
52+
outputWriter = new StringWriter();
53+
errorWriter = new StringWriter();
54+
Console.SetOut(outputWriter);
55+
Console.SetError(errorWriter);
56+
57+
// Act
58+
using var context = Context.Create(args, () => new MockRepoConnector());
59+
Validation.Run(context);
60+
61+
// Assert - Verify TRX file was created
62+
Assert.IsTrue(File.Exists(trxFile), "TRX file should be created");
63+
64+
// Verify TRX file contains expected content
65+
var trxContent = File.ReadAllText(trxFile);
66+
Assert.Contains("TestRun", trxContent);
67+
Assert.Contains("BuildMark Self-Validation", trxContent);
68+
}
69+
finally
70+
{
71+
// Restore console output
72+
var standardOutput = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
73+
Console.SetOut(standardOutput);
74+
var standardError = new StreamWriter(Console.OpenStandardError()) { AutoFlush = true };
75+
Console.SetError(standardError);
76+
77+
outputWriter?.Dispose();
78+
errorWriter?.Dispose();
79+
}
80+
}
81+
finally
82+
{
83+
// Cleanup
84+
if (Directory.Exists(tempDir))
85+
{
86+
Directory.Delete(tempDir, true);
87+
}
88+
}
89+
}
90+
91+
/// <summary>
92+
/// Test that Validation.Run writes JUnit XML results file when specified.
93+
/// </summary>
94+
[TestMethod]
95+
public void Validation_Run_WithXmlResultsFile_WritesJUnitFile()
96+
{
97+
// Arrange
98+
var tempDir = Path.Combine(Path.GetTempPath(), $"buildmark_test_{Guid.NewGuid()}");
99+
Directory.CreateDirectory(tempDir);
100+
101+
try
102+
{
103+
var xmlFile = Path.Combine(tempDir, "results.xml");
104+
var args = new[] { "--validate", "--results", xmlFile };
105+
106+
StringWriter? outputWriter = null;
107+
StringWriter? errorWriter = null;
108+
109+
try
110+
{
111+
// Capture console output
112+
outputWriter = new StringWriter();
113+
errorWriter = new StringWriter();
114+
Console.SetOut(outputWriter);
115+
Console.SetError(errorWriter);
116+
117+
// Act
118+
using var context = Context.Create(args, () => new MockRepoConnector());
119+
Validation.Run(context);
120+
121+
// Assert - Verify XML file was created
122+
Assert.IsTrue(File.Exists(xmlFile), "XML file should be created");
123+
124+
// Verify XML file contains expected content
125+
var xmlContent = File.ReadAllText(xmlFile);
126+
Assert.Contains("testsuites", xmlContent);
127+
Assert.Contains("BuildMark Self-Validation", xmlContent);
128+
}
129+
finally
130+
{
131+
// Restore console output
132+
var standardOutput = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
133+
Console.SetOut(standardOutput);
134+
var standardError = new StreamWriter(Console.OpenStandardError()) { AutoFlush = true };
135+
Console.SetError(standardError);
136+
137+
outputWriter?.Dispose();
138+
errorWriter?.Dispose();
139+
}
140+
}
141+
finally
142+
{
143+
// Cleanup
144+
if (Directory.Exists(tempDir))
145+
{
146+
Directory.Delete(tempDir, true);
147+
}
148+
}
149+
}
150+
151+
/// <summary>
152+
/// Test that Validation.Run handles unsupported results file extension.
153+
/// </summary>
154+
[TestMethod]
155+
public void Validation_Run_WithUnsupportedResultsFileExtension_ShowsError()
156+
{
157+
// Arrange
158+
var tempDir = Path.Combine(Path.GetTempPath(), $"buildmark_test_{Guid.NewGuid()}");
159+
Directory.CreateDirectory(tempDir);
160+
161+
try
162+
{
163+
var unsupportedFile = Path.Combine(tempDir, "results.json");
164+
var args = new[] { "--validate", "--results", unsupportedFile };
165+
166+
StringWriter? outputWriter = null;
167+
168+
try
169+
{
170+
// Capture console output
171+
outputWriter = new StringWriter();
172+
Console.SetOut(outputWriter);
173+
174+
// Act
175+
using var context = Context.Create(args, () => new MockRepoConnector());
176+
Validation.Run(context);
177+
178+
// Assert - Verify error message in output (WriteError writes to Console.WriteLine)
179+
var output = outputWriter.ToString();
180+
Assert.Contains("Unsupported results file format", output);
181+
}
182+
finally
183+
{
184+
// Restore console output
185+
var standardOutput = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
186+
Console.SetOut(standardOutput);
187+
188+
outputWriter?.Dispose();
189+
}
190+
}
191+
finally
192+
{
193+
// Cleanup
194+
if (Directory.Exists(tempDir))
195+
{
196+
Directory.Delete(tempDir, true);
197+
}
198+
}
199+
}
200+
201+
/// <summary>
202+
/// Test that Validation.Run handles write failure for results file.
203+
/// </summary>
204+
[TestMethod]
205+
public void Validation_Run_WithInvalidResultsFilePath_ShowsError()
206+
{
207+
// Arrange
208+
var invalidPath = Path.Combine("/invalid_path_that_does_not_exist_12345678", "results.trx");
209+
var args = new[] { "--validate", "--results", invalidPath };
210+
211+
StringWriter? outputWriter = null;
212+
213+
try
214+
{
215+
// Capture console output
216+
outputWriter = new StringWriter();
217+
Console.SetOut(outputWriter);
218+
219+
// Act
220+
using var context = Context.Create(args, () => new MockRepoConnector());
221+
Validation.Run(context);
222+
223+
// Assert - Verify error message in output (WriteError writes to Console.WriteLine)
224+
var output = outputWriter.ToString();
225+
Assert.Contains("Failed to write results file", output);
226+
}
227+
finally
228+
{
229+
// Restore console output
230+
var standardOutput = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
231+
Console.SetOut(standardOutput);
232+
233+
outputWriter?.Dispose();
234+
}
235+
}
236+
}

0 commit comments

Comments
 (0)