Skip to content

Commit 5b0ddac

Browse files
Apply NuGetCacheTool PR#8 improvements to TemplateDotNetTool (#48)
* Initial plan * feat: apply NuGetCacheTool PR#8 improvements to template Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> * refactor: split compound TMPL-REQ-013 into stderr and exit-code requirements Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> * fix: restore Context_WriteLine_Silent_DoesNotWriteToConsole test for TMPL-REQ-004 Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> * refactor: rename CLI requirements to TMPL-CMD-xxx, Platform to TMPL-PLT-xxx; fix REQ order and guide wording Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> * refactor: renumber TMPL-CMD-013/014/015 to 008/009/010 and TMPL-PLT-008-012 to 001-005 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 e629bc8 commit 5b0ddac

File tree

8 files changed

+226
-19
lines changed

8 files changed

+226
-19
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ implementation demonstrating best practices for DEMA Consulting .NET CLI tools.
1414

1515
## Tech Stack
1616

17-
- C# 12, .NET 8.0/9.0/10.0, dotnet CLI, NuGet
17+
- C# (latest), .NET 8.0/9.0/10.0, dotnet CLI, NuGet
1818

1919
## Key Files
2020

docs/guide/guide.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ Save validation results to a file:
5454
templatetool --validate --results results.trx
5555
```
5656

57+
The results file format is determined by the file extension: `.trx` for TRX (MSTest) format,
58+
or `.xml` for JUnit format.
59+
5760
## Silent Mode
5861

5962
Suppress console output:

requirements.yaml

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ sections:
3232
sections:
3333
- title: Command-Line Interface
3434
requirements:
35-
- id: TMPL-REQ-001
35+
- id: TMPL-CMD-001
3636
title: The tool shall implement a Context class for command-line argument handling.
3737
justification: |
3838
Provides a standardized approach to command-line argument parsing and output
@@ -46,7 +46,7 @@ sections:
4646
- Context_Create_ResultsFlag_SetsResultsFile
4747
- Context_Create_LogFlag_OpensLogFile
4848

49-
- id: TMPL-REQ-002
49+
- id: TMPL-CMD-002
5050
title: The tool shall support -v and --version flags to display version information.
5151
justification: |
5252
Users need to quickly identify the version of the tool they are using for
@@ -55,9 +55,10 @@ sections:
5555
- Context_Create_VersionFlag_SetsVersionTrue
5656
- Context_Create_ShortVersionFlag_SetsVersionTrue
5757
- Program_Run_WithVersionFlag_DisplaysVersionOnly
58+
- Program_Version_ReturnsNonEmptyString
5859
- IntegrationTest_VersionFlag_OutputsVersion
5960

60-
- id: TMPL-REQ-003
61+
- id: TMPL-CMD-003
6162
title: The tool shall support -?, -h, and --help flags to display usage information.
6263
justification: |
6364
Users need access to command-line usage documentation without requiring
@@ -69,7 +70,7 @@ sections:
6970
- Program_Run_WithHelpFlag_DisplaysUsageInformation
7071
- IntegrationTest_HelpFlag_OutputsUsageInformation
7172

72-
- id: TMPL-REQ-004
73+
- id: TMPL-CMD-004
7374
title: The tool shall support --silent flag to suppress console output.
7475
justification: |
7576
Enables automated scripts and CI/CD pipelines to run the tool without
@@ -79,7 +80,7 @@ sections:
7980
- Context_WriteLine_Silent_DoesNotWriteToConsole
8081
- IntegrationTest_SilentFlag_SuppressesOutput
8182

82-
- id: TMPL-REQ-005
83+
- id: TMPL-CMD-005
8384
title: The tool shall support --validate flag to run self-validation tests.
8485
justification: |
8586
Provides a built-in mechanism to verify the tool is functioning correctly
@@ -89,25 +90,55 @@ sections:
8990
- Program_Run_WithValidateFlag_RunsValidation
9091
- IntegrationTest_ValidateFlag_RunsValidation
9192

92-
- id: TMPL-REQ-006
93+
- id: TMPL-CMD-006
9394
title: The tool shall support --results flag to write validation results in TRX or JUnit format.
9495
justification: |
9596
Enables integration with CI/CD systems that expect standard test result formats.
9697
tests:
9798
- Context_Create_ResultsFlag_SetsResultsFile
9899
- IntegrationTest_ValidateWithResults_GeneratesTrxFile
100+
- IntegrationTest_ValidateWithResults_GeneratesJUnitFile
99101

100-
- id: TMPL-REQ-007
102+
- id: TMPL-CMD-007
101103
title: The tool shall support --log flag to write output to a log file.
102104
justification: |
103105
Provides persistent logging for debugging and audit trails.
104106
tests:
105107
- Context_Create_LogFlag_OpensLogFile
106108
- IntegrationTest_LogFlag_WritesOutputToFile
107109

110+
- id: TMPL-CMD-008
111+
title: The tool shall write error messages to stderr.
112+
justification: |
113+
Error messages must be written to stderr so they remain visible to the user
114+
without polluting stdout, which consumers may pipe or redirect for data capture.
115+
tests:
116+
- Context_WriteError_NotSilent_WritesToConsole
117+
- IntegrationTest_UnknownArgument_ReturnsError
118+
119+
- id: TMPL-CMD-009
120+
title: The tool shall reject unknown or malformed command-line arguments with a descriptive error.
121+
justification: |
122+
Providing clear feedback for invalid arguments helps users quickly correct
123+
mistakes and prevents silent misconfiguration.
124+
tests:
125+
- Context_Create_UnknownArgument_ThrowsArgumentException
126+
- Context_Create_LogFlag_WithoutValue_ThrowsArgumentException
127+
- Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException
128+
- IntegrationTest_UnknownArgument_ReturnsError
129+
130+
- id: TMPL-CMD-010
131+
title: The tool shall return a non-zero exit code on failure.
132+
justification: |
133+
Callers (scripts, CI/CD pipelines) must be able to detect failure conditions
134+
programmatically via the process exit code.
135+
tests:
136+
- Context_WriteError_SetsErrorExitCode
137+
- IntegrationTest_UnknownArgument_ReturnsError
138+
108139
- title: Platform Support
109140
requirements:
110-
- id: TMPL-REQ-008
141+
- id: TMPL-PLT-001
111142
title: The tool shall build and run on Windows platforms.
112143
justification: |
113144
DEMA Consulting tools must support Windows as a major development platform.
@@ -116,7 +147,7 @@ sections:
116147
- "windows@TemplateTool_VersionDisplay"
117148
- "windows@TemplateTool_HelpDisplay"
118149

119-
- id: TMPL-REQ-009
150+
- id: TMPL-PLT-002
120151
title: The tool shall build and run on Linux platforms.
121152
justification: |
122153
DEMA Consulting tools must support Linux for CI/CD and containerized environments.
@@ -125,23 +156,23 @@ sections:
125156
- "ubuntu@TemplateTool_VersionDisplay"
126157
- "ubuntu@TemplateTool_HelpDisplay"
127158

128-
- id: TMPL-REQ-010
159+
- id: TMPL-PLT-003
129160
title: The tool shall support .NET 8 runtime.
130161
justification: |
131162
.NET 8 is an LTS release providing long-term stability for enterprise users.
132163
tests:
133164
- "dotnet8.x@TemplateTool_VersionDisplay"
134165
- "dotnet8.x@TemplateTool_HelpDisplay"
135166

136-
- id: TMPL-REQ-011
167+
- id: TMPL-PLT-004
137168
title: The tool shall support .NET 9 runtime.
138169
justification: |
139170
.NET 9 support enables users to leverage the latest .NET features.
140171
tests:
141172
- "dotnet9.x@TemplateTool_VersionDisplay"
142173
- "dotnet9.x@TemplateTool_HelpDisplay"
143174

144-
- id: TMPL-REQ-012
175+
- id: TMPL-PLT-005
145176
title: The tool shall support .NET 10 runtime.
146177
justification: |
147178
.NET 10 support ensures the tool remains compatible with the latest .NET ecosystem.

src/DemaConsulting.TemplateDotNetTool/Context.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ private void OpenLogFile(string logFile)
112112
{
113113
try
114114
{
115-
_logWriter = new StreamWriter(logFile, append: false);
115+
// Open with AutoFlush enabled so log entries are immediately written to disk
116+
// even if the application terminates unexpectedly before Dispose is called
117+
_logWriter = new StreamWriter(logFile, append: false) { AutoFlush = true };
116118
}
117119
// Generic catch is justified here to wrap any file system exception with context.
118120
// Expected exceptions include IOException, UnauthorizedAccessException, ArgumentException,
@@ -267,7 +269,7 @@ public void WriteError(string message)
267269
{
268270
var previousColor = Console.ForegroundColor;
269271
Console.ForegroundColor = ConsoleColor.Red;
270-
Console.WriteLine(message);
272+
Console.Error.WriteLine(message);
271273
Console.ForegroundColor = previousColor;
272274
}
273275

src/DemaConsulting.TemplateDotNetTool/Program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,19 @@ private static int Main(string[] args)
6565
catch (ArgumentException ex)
6666
{
6767
// Print expected argument exceptions and return error code
68-
Console.WriteLine($"Error: {ex.Message}");
68+
Console.Error.WriteLine($"Error: {ex.Message}");
6969
return 1;
7070
}
7171
catch (InvalidOperationException ex)
7272
{
7373
// Print expected operation exceptions and return error code
74-
Console.WriteLine($"Error: {ex.Message}");
74+
Console.Error.WriteLine($"Error: {ex.Message}");
7575
return 1;
7676
}
7777
catch (Exception ex)
7878
{
7979
// Print unexpected exceptions and re-throw to generate event logs
80-
Console.WriteLine($"Unexpected error: {ex.Message}");
80+
Console.Error.WriteLine($"Unexpected error: {ex.Message}");
8181
throw;
8282
}
8383
}

test/DemaConsulting.TemplateDotNetTool.Tests/ContextTests.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,28 @@ public void Context_Create_UnknownArgument_ThrowsArgumentException()
203203
Assert.Contains("Unsupported argument", exception.Message);
204204
}
205205

206+
/// <summary>
207+
/// Test creating a context with --log flag but no value throws exception.
208+
/// </summary>
209+
[TestMethod]
210+
public void Context_Create_LogFlag_WithoutValue_ThrowsArgumentException()
211+
{
212+
// Act & Assert
213+
var exception = Assert.Throws<ArgumentException>(() => Context.Create(["--log"]));
214+
Assert.Contains("--log", exception.Message);
215+
}
216+
217+
/// <summary>
218+
/// Test creating a context with --results flag but no value throws exception.
219+
/// </summary>
220+
[TestMethod]
221+
public void Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException()
222+
{
223+
// Act & Assert
224+
var exception = Assert.Throws<ArgumentException>(() => Context.Create(["--results"]));
225+
Assert.Contains("--results", exception.Message);
226+
}
227+
206228
/// <summary>
207229
/// Test WriteLine writes to console output when not silent.
208230
/// </summary>
@@ -256,4 +278,116 @@ public void Context_WriteLine_Silent_DoesNotWriteToConsole()
256278
Console.SetOut(originalOut);
257279
}
258280
}
281+
282+
/// <summary>
283+
/// Test WriteError does not write to console when silent.
284+
/// </summary>
285+
[TestMethod]
286+
public void Context_WriteError_Silent_DoesNotWriteToConsole()
287+
{
288+
// Arrange
289+
var originalError = Console.Error;
290+
try
291+
{
292+
using var errWriter = new StringWriter();
293+
Console.SetError(errWriter);
294+
using var context = Context.Create(["--silent"]);
295+
296+
// Act
297+
context.WriteError("Test error message");
298+
299+
// Assert - error output should be suppressed in silent mode
300+
var output = errWriter.ToString();
301+
Assert.DoesNotContain("Test error message", output);
302+
}
303+
finally
304+
{
305+
Console.SetError(originalError);
306+
}
307+
}
308+
309+
/// <summary>
310+
/// Test WriteError sets exit code to 1.
311+
/// </summary>
312+
[TestMethod]
313+
public void Context_WriteError_SetsErrorExitCode()
314+
{
315+
// Arrange
316+
var originalError = Console.Error;
317+
try
318+
{
319+
using var errWriter = new StringWriter();
320+
Console.SetError(errWriter);
321+
using var context = Context.Create([]);
322+
323+
// Act
324+
context.WriteError("Test error message");
325+
326+
// Assert
327+
Assert.AreEqual(1, context.ExitCode);
328+
}
329+
finally
330+
{
331+
Console.SetError(originalError);
332+
}
333+
}
334+
335+
/// <summary>
336+
/// Test WriteError writes message to console when not silent.
337+
/// </summary>
338+
[TestMethod]
339+
public void Context_WriteError_NotSilent_WritesToConsole()
340+
{
341+
// Arrange
342+
var originalError = Console.Error;
343+
try
344+
{
345+
using var errWriter = new StringWriter();
346+
Console.SetError(errWriter);
347+
using var context = Context.Create([]);
348+
349+
// Act
350+
context.WriteError("Test error message");
351+
352+
// Assert
353+
var output = errWriter.ToString();
354+
Assert.Contains("Test error message", output);
355+
}
356+
finally
357+
{
358+
Console.SetError(originalError);
359+
}
360+
}
361+
362+
/// <summary>
363+
/// Test WriteError writes message to log file when logging is enabled.
364+
/// </summary>
365+
[TestMethod]
366+
public void Context_WriteError_WritesToLogFile()
367+
{
368+
// Arrange
369+
var logFile = Path.GetTempFileName();
370+
try
371+
{
372+
// Act - use silent to avoid console output; verify the error still goes to the log
373+
using (var context = Context.Create(["--silent", "--log", logFile]))
374+
{
375+
context.WriteError("Test error in log");
376+
Assert.AreEqual(1, context.ExitCode);
377+
}
378+
379+
// Assert - log file should contain the error message
380+
Assert.IsTrue(File.Exists(logFile));
381+
var logContent = File.ReadAllText(logFile);
382+
Assert.Contains("Test error in log", logContent);
383+
}
384+
finally
385+
{
386+
if (File.Exists(logFile))
387+
{
388+
File.Delete(logFile);
389+
}
390+
}
391+
}
259392
}
393+

test/DemaConsulting.TemplateDotNetTool.Tests/DemaConsulting.TemplateDotNetTool.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<!-- Build Configuration -->
55
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
6-
<LangVersion>12</LangVersion>
6+
<LangVersion>latest</LangVersion>
77
<ImplicitUsings>enable</ImplicitUsings>
88
<Nullable>enable</Nullable>
99

0 commit comments

Comments
 (0)