Skip to content

Commit be6add5

Browse files
stho32claude
andcommitted
[R0101] Unit-Tests einfuehren und Infrastructure-Klassen testbar machen
- SessionConfigurationFileHandler und SessionConfigurationFolderHandler von static auf Instanz-Klassen refactored - FolderHandler-Pfad per Konstruktor injizierbar fuer isolierte Tests - 12 BL-Tests (PairCodingSession, VersionInformation) - 28 Infrastructure-Tests (FileHandler, FolderHandler, SessionConfiguration) - 40 Tests gesamt, alle gruen, BL 100% Coverage Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f8e740e commit be6add5

11 files changed

Lines changed: 620 additions & 68 deletions

‎Source/VisualPairCoding/VisualPairCoding.AvaloniaUI/EnterNamesWindow.axaml.cs‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public partial class EnterNamesForm : Window
1313
{
1414
private bool _autostart = false;
1515
private bool isTotalDurationActivated = false;
16+
private readonly SessionConfigurationFileHandler _fileHandler = new();
17+
private readonly SessionConfigurationFolderHandler _folderHandler = new();
18+
1619
public EnterNamesForm()
1720
{
1821
InitializeComponent();
@@ -76,7 +79,7 @@ private void TotalDurationCheckBoxUnchecked(object sender, RoutedEventArgs e)
7679

7780
private void OnClosed(object? sender, EventArgs e)
7881
{
79-
SessionConfigurationFolderHandler.SaveAsRecentSession(
82+
_folderHandler.SaveAsRecentSession(
8083
new SessionConfiguration(GetParticipants(), (int)(minutesPerTurn.Value ?? 1))
8184
);
8285
}
@@ -92,7 +95,7 @@ private void OnMenuItemClicked(object? sender, RoutedEventArgs e)
9295
MenuItem clickedMenuItem = (MenuItem)e.Source!;
9396
string subMenuHeader = clickedMenuItem.Header!.ToString()!;
9497

95-
var sessionConfiguration = SessionConfigurationFolderHandler.LoadRecentSession(subMenuHeader);
98+
var sessionConfiguration = _folderHandler.LoadRecentSession(subMenuHeader);
9699

97100
LoadSessionIntoGui(sessionConfiguration);
98101
}
@@ -101,7 +104,7 @@ private void OnActivated(object? sender, EventArgs e)
101104
{
102105
MenuItem recentSessionsMenuItem = GetRecentMenuItem();
103106

104-
var configs = SessionConfigurationFolderHandler.GetRecentSessionNames();
107+
var configs = _folderHandler.GetRecentSessionNames();
105108

106109
recentSessionsMenuItem.Items.Clear();
107110
foreach(var config in configs)
@@ -169,7 +172,7 @@ private async void LoadSessionIntoGui(string fileName)
169172
{
170173
try
171174
{
172-
var session = SessionConfigurationFileHandler.Load(fileName);
175+
var session = _fileHandler.Load(fileName);
173176

174177
LoadSessionIntoGui(session);
175178
}
@@ -318,7 +321,7 @@ public async void SaveSessionConfiguration(object? sender, RoutedEventArgs args)
318321
{
319322
try
320323
{
321-
SessionConfigurationFileHandler.Save(file.Path.LocalPath, new SessionConfiguration(participants, (int)minutesPerTurn.Value!));
324+
_fileHandler.Save(file.Path.LocalPath, new SessionConfiguration(participants, (int)minutesPerTurn.Value!));
322325
await MessageBoxHelper.ShowInfo(this, "Config Saved", "Session Configuration saved successfully!");
323326
}
324327
catch (Exception ex)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
namespace VisualPairCoding.BL.Tests;
2+
3+
public class PairCodingSessionTests
4+
{
5+
[Fact]
6+
public void Constructor_SetsParticipants()
7+
{
8+
var participants = new[] { "Alice", "Bob" };
9+
var session = new PairCodingSession(participants, 7, TimeSpan.FromMinutes(60));
10+
11+
Assert.Equal(participants, session.Participants);
12+
}
13+
14+
[Fact]
15+
public void Constructor_SetsMinutesPerTurn()
16+
{
17+
var session = new PairCodingSession(new[] { "Alice" }, 5, TimeSpan.Zero);
18+
19+
Assert.Equal(5, session.MinutesPerTurn);
20+
}
21+
22+
[Fact]
23+
public void Constructor_SetsTotalDuration()
24+
{
25+
var duration = TimeSpan.FromMinutes(90);
26+
var session = new PairCodingSession(new[] { "Alice" }, 7, duration);
27+
28+
Assert.Equal(duration, session.TotalDuration);
29+
}
30+
31+
[Fact]
32+
public void Validate_WithNoParticipants_ReturnsErrorMessage()
33+
{
34+
var session = new PairCodingSession(Array.Empty<string>(), 7, TimeSpan.Zero);
35+
36+
var result = session.Validate();
37+
38+
Assert.NotNull(result);
39+
Assert.Contains("at least one participant", result);
40+
}
41+
42+
[Fact]
43+
public void Validate_WithOneParticipant_ReturnsNull()
44+
{
45+
var session = new PairCodingSession(new[] { "Alice" }, 7, TimeSpan.Zero);
46+
47+
var result = session.Validate();
48+
49+
Assert.Null(result);
50+
}
51+
52+
[Fact]
53+
public void Validate_WithMultipleParticipants_ReturnsNull()
54+
{
55+
var session = new PairCodingSession(new[] { "Alice", "Bob", "Charlie" }, 7, TimeSpan.Zero);
56+
57+
var result = session.Validate();
58+
59+
Assert.Null(result);
60+
}
61+
62+
[Theory]
63+
[InlineData(1)]
64+
[InlineData(7)]
65+
[InlineData(30)]
66+
public void Constructor_AcceptsVariousMinutesPerTurn(int minutes)
67+
{
68+
var session = new PairCodingSession(new[] { "Alice" }, minutes, TimeSpan.Zero);
69+
70+
Assert.Equal(minutes, session.MinutesPerTurn);
71+
}
72+
73+
[Fact]
74+
public void Validate_WithTenParticipants_ReturnsNull()
75+
{
76+
var participants = Enumerable.Range(1, 10).Select(i => $"Person{i}").ToArray();
77+
var session = new PairCodingSession(participants, 7, TimeSpan.Zero);
78+
79+
var result = session.Validate();
80+
81+
Assert.Null(result);
82+
}
83+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace VisualPairCoding.BL.Tests;
2+
3+
public class VersionInformationTests
4+
{
5+
[Fact]
6+
public void Version_ReturnsNonEmptyString()
7+
{
8+
var version = VersionInformation.Version;
9+
10+
Assert.NotNull(version);
11+
Assert.NotEmpty(version);
12+
}
13+
14+
[Fact]
15+
public void Version_ContainsPlaceholderOrValidVersion()
16+
{
17+
var version = VersionInformation.Version;
18+
19+
// In dev/test it's the placeholder, in CI it's replaced with the actual version
20+
Assert.True(
21+
version == "$$VERSION$$" || System.Text.RegularExpressions.Regex.IsMatch(version, @"^\d+\.\d+"),
22+
$"Version should be placeholder or semver-like, got: {version}");
23+
}
24+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
13+
<PackageReference Include="xunit" Version="2.9.2" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<Using Include="Xunit" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\VisualPairCoding.BL\VisualPairCoding.BL.csproj" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
namespace VisualPairCoding.Infrastructure.Tests;
2+
3+
public class SessionConfigurationFileHandlerTests : IDisposable
4+
{
5+
private readonly string _tempDir;
6+
private readonly SessionConfigurationFileHandler _handler;
7+
8+
public SessionConfigurationFileHandlerTests()
9+
{
10+
_tempDir = Path.Combine(Path.GetTempPath(), "VPCTests_" + Guid.NewGuid().ToString("N"));
11+
Directory.CreateDirectory(_tempDir);
12+
_handler = new SessionConfigurationFileHandler();
13+
}
14+
15+
public void Dispose()
16+
{
17+
if (Directory.Exists(_tempDir))
18+
Directory.Delete(_tempDir, true);
19+
}
20+
21+
[Fact]
22+
public void GetFilenameProposal_SingleParticipant_ReturnsNameWithExtension()
23+
{
24+
var result = _handler.GetFilenameProposal(new[] { "Alice" });
25+
26+
Assert.Equal("Alice.vpcsession", result);
27+
}
28+
29+
[Fact]
30+
public void GetFilenameProposal_MultipleParticipants_JoinsWithUnderscore()
31+
{
32+
var result = _handler.GetFilenameProposal(new[] { "Alice", "Bob", "Charlie" });
33+
34+
Assert.Equal("Alice_Bob_Charlie.vpcsession", result);
35+
}
36+
37+
[Fact]
38+
public void GetFilenameProposal_EmptyArray_ReturnsJustExtension()
39+
{
40+
var result = _handler.GetFilenameProposal(Array.Empty<string>());
41+
42+
Assert.Equal(".vpcsession", result);
43+
}
44+
45+
[Fact]
46+
public void Save_CreatesFileOnDisk()
47+
{
48+
var filePath = Path.Combine(_tempDir, "test.vpcsession");
49+
var config = new SessionConfiguration(new[] { "Alice", "Bob" }, 7);
50+
51+
_handler.Save(filePath, config);
52+
53+
Assert.True(File.Exists(filePath));
54+
}
55+
56+
[Fact]
57+
public void Save_WritesValidJson()
58+
{
59+
var filePath = Path.Combine(_tempDir, "test.vpcsession");
60+
var config = new SessionConfiguration(new[] { "Alice", "Bob" }, 7);
61+
62+
_handler.Save(filePath, config);
63+
64+
var json = File.ReadAllText(filePath);
65+
Assert.Contains("Alice", json);
66+
Assert.Contains("Bob", json);
67+
}
68+
69+
[Fact]
70+
public void Load_ReadsConfigCorrectly()
71+
{
72+
var filePath = Path.Combine(_tempDir, "test.vpcsession");
73+
var original = new SessionConfiguration(new[] { "Alice", "Bob" }, 7);
74+
_handler.Save(filePath, original);
75+
76+
var loaded = _handler.Load(filePath);
77+
78+
Assert.Equal(original.Participants, loaded.Participants);
79+
Assert.Equal(original.SessionLength, loaded.SessionLength);
80+
}
81+
82+
[Fact]
83+
public void SaveAndLoad_RoundTrip_PreservesAllData()
84+
{
85+
var filePath = Path.Combine(_tempDir, "roundtrip.vpcsession");
86+
var original = new SessionConfiguration(
87+
new[] { "Alice", "Bob", "Charlie", "Dave" }, 15);
88+
89+
_handler.Save(filePath, original);
90+
var loaded = _handler.Load(filePath);
91+
92+
Assert.Equal(original.Participants, loaded.Participants);
93+
Assert.Equal(original.SessionLength, loaded.SessionLength);
94+
}
95+
96+
[Fact]
97+
public void SaveAndLoad_RoundTrip_WithSingleParticipant()
98+
{
99+
var filePath = Path.Combine(_tempDir, "single.vpcsession");
100+
var original = new SessionConfiguration(new[] { "Solo" }, 1);
101+
102+
_handler.Save(filePath, original);
103+
var loaded = _handler.Load(filePath);
104+
105+
Assert.Single(loaded.Participants);
106+
Assert.Equal("Solo", loaded.Participants[0]);
107+
Assert.Equal(1, loaded.SessionLength);
108+
}
109+
110+
[Fact]
111+
public void SaveAndLoad_RoundTrip_WithTenParticipants()
112+
{
113+
var filePath = Path.Combine(_tempDir, "ten.vpcsession");
114+
var participants = Enumerable.Range(1, 10).Select(i => $"Person{i}").ToArray();
115+
var original = new SessionConfiguration(participants, 5);
116+
117+
_handler.Save(filePath, original);
118+
var loaded = _handler.Load(filePath);
119+
120+
Assert.Equal(10, loaded.Participants.Length);
121+
Assert.Equal(participants, loaded.Participants);
122+
}
123+
124+
[Fact]
125+
public void Load_NonexistentFile_ThrowsFileNotFoundException()
126+
{
127+
var filePath = Path.Combine(_tempDir, "nonexistent.vpcsession");
128+
129+
Assert.Throws<FileNotFoundException>(() => _handler.Load(filePath));
130+
}
131+
132+
[Fact]
133+
public void Load_InvalidJson_ThrowsJsonException()
134+
{
135+
var filePath = Path.Combine(_tempDir, "invalid.vpcsession");
136+
File.WriteAllText(filePath, "not valid json {{{");
137+
138+
Assert.Throws<System.Text.Json.JsonException>(() => _handler.Load(filePath));
139+
}
140+
141+
[Fact]
142+
public void Save_OverwritesExistingFile()
143+
{
144+
var filePath = Path.Combine(_tempDir, "overwrite.vpcsession");
145+
var first = new SessionConfiguration(new[] { "Alice" }, 5);
146+
var second = new SessionConfiguration(new[] { "Bob", "Charlie" }, 10);
147+
148+
_handler.Save(filePath, first);
149+
_handler.Save(filePath, second);
150+
var loaded = _handler.Load(filePath);
151+
152+
Assert.Equal(new[] { "Bob", "Charlie" }, loaded.Participants);
153+
Assert.Equal(10, loaded.SessionLength);
154+
}
155+
}

0 commit comments

Comments
 (0)