Skip to content

Commit ebc87af

Browse files
author
Keith Klein
committed
Add app icon, security hardening, logout, screenshots, unit tests
- Custom app icon (dark circle + glucose wave + live dot) - Security: DPAPI entropy, config ACL restriction, input validation/clamping - Logout / Clear Credentials context menu option - 105 xUnit tests covering models, settings sanitization, API parsing - CI workflow updated to run tests before publish - Screenshots and README updates
1 parent 177077a commit ebc87af

19 files changed

Lines changed: 854 additions & 15 deletions

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ jobs:
2424
- name: Restore dependencies
2525
run: dotnet restore DexcomOverlay/DexcomOverlay.csproj
2626

27+
- name: Run tests
28+
run: dotnet test DexcomOverlay.Tests/DexcomOverlay.Tests.csproj --configuration Release --verbosity normal
29+
2730
- name: Publish
2831
run: >
2932
dotnet publish DexcomOverlay/DexcomOverlay.csproj
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<UseWPF>true</UseWPF>
8+
9+
<IsPackable>false</IsPackable>
10+
<IsTestProject>true</IsTestProject>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
16+
<PackageReference Include="xunit" Version="2.5.3" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<Using Include="Xunit" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\DexcomOverlay\DexcomOverlay.csproj" />
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using DexcomOverlay.Models;
2+
3+
namespace DexcomOverlay.Tests.Models;
4+
5+
public class AppSettingsTests
6+
{
7+
[Fact]
8+
public void Defaults_HaveExpectedValues()
9+
{
10+
var settings = new AppSettings();
11+
12+
Assert.Equal("", settings.Username);
13+
Assert.Equal("", settings.Password);
14+
Assert.Equal("us", settings.Region);
15+
Assert.Null(settings.WindowX);
16+
Assert.Null(settings.WindowY);
17+
Assert.Equal(60, settings.RefreshIntervalSeconds);
18+
Assert.Equal(48, settings.FontSize);
19+
Assert.Equal(0.9, settings.Opacity);
20+
Assert.True(settings.ShowTrendArrow);
21+
Assert.False(settings.ShowMmol);
22+
Assert.True(settings.EnablePredictiveAlerts);
23+
Assert.Equal(15, settings.AlertCooldownMinutes);
24+
}
25+
26+
[Fact]
27+
public void Thresholds_HaveDefaults()
28+
{
29+
var t = new GlucoseThresholds();
30+
31+
Assert.Equal(55, t.UrgentLow);
32+
Assert.Equal(70, t.Low);
33+
Assert.Equal(180, t.High);
34+
Assert.Equal(250, t.UrgentHigh);
35+
}
36+
37+
[Fact]
38+
public void Thresholds_AreInOrder()
39+
{
40+
var t = new GlucoseThresholds();
41+
Assert.True(t.UrgentLow < t.Low);
42+
Assert.True(t.Low < t.High);
43+
Assert.True(t.High < t.UrgentHigh);
44+
}
45+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using DexcomOverlay.Models;
2+
3+
namespace DexcomOverlay.Tests.Models;
4+
5+
public class GlucoseReadingTests
6+
{
7+
// ── MmolL conversion ───────────────────────────────────────
8+
9+
[Theory]
10+
[InlineData(100, 5.6)] // 100 mg/dL → 5.6 mmol/L
11+
[InlineData(180, 10.0)] // 180 mg/dL → 10.0 mmol/L
12+
[InlineData(70, 3.9)] // 70 mg/dL → 3.9 mmol/L
13+
[InlineData(55, 3.1)] // 55 mg/dL → 3.1 mmol/L
14+
[InlineData(250, 13.9)] // 250 mg/dL → 13.9 mmol/L
15+
[InlineData(0, 0.0)] // edge: zero
16+
public void MmolL_ConvertsCorrectly(int mgdl, double expectedMmol)
17+
{
18+
var reading = new GlucoseReading { Value = mgdl };
19+
Assert.Equal(expectedMmol, reading.MmolL);
20+
}
21+
22+
// ── Trend arrows ───────────────────────────────────────────
23+
24+
[Theory]
25+
[InlineData(0, "")] // None
26+
[InlineData(1, "↑↑")] // DoubleUp
27+
[InlineData(2, "↑")] // SingleUp
28+
[InlineData(3, "↗")] // FortyFiveUp
29+
[InlineData(4, "→")] // Flat
30+
[InlineData(5, "↘")] // FortyFiveDown
31+
[InlineData(6, "↓")] // SingleDown
32+
[InlineData(7, "↓↓")] // DoubleDown
33+
[InlineData(8, "?")] // NotComputable
34+
[InlineData(9, "-")] // RateOutOfRange
35+
public void TrendArrow_ReturnsCorrectSymbol(int trendIndex, string expectedArrow)
36+
{
37+
var reading = new GlucoseReading { TrendIndex = trendIndex };
38+
Assert.Equal(expectedArrow, reading.TrendArrow);
39+
}
40+
41+
[Fact]
42+
public void TrendArrow_OutOfRange_ReturnsFallback()
43+
{
44+
var reading = new GlucoseReading { TrendIndex = 99 };
45+
Assert.Equal("→", reading.TrendArrow); // default fallback
46+
}
47+
48+
// ── Trend descriptions ─────────────────────────────────────
49+
50+
[Theory]
51+
[InlineData(0, "")]
52+
[InlineData(1, "rising quickly")]
53+
[InlineData(4, "steady")]
54+
[InlineData(7, "falling quickly")]
55+
[InlineData(8, "unable to determine")]
56+
[InlineData(9, "trend unavailable")]
57+
public void TrendDescription_ReturnsCorrectText(int trendIndex, string expected)
58+
{
59+
var reading = new GlucoseReading { TrendIndex = trendIndex };
60+
Assert.Equal(expected, reading.TrendDescription);
61+
}
62+
63+
[Fact]
64+
public void TrendDescription_OutOfRange_ReturnsFallback()
65+
{
66+
var reading = new GlucoseReading { TrendIndex = -1 };
67+
Assert.Equal("steady", reading.TrendDescription);
68+
}
69+
70+
// ── TrendDirectionMap ──────────────────────────────────────
71+
72+
[Theory]
73+
[InlineData("None", 0)]
74+
[InlineData("DoubleUp", 1)]
75+
[InlineData("SingleUp", 2)]
76+
[InlineData("FortyFiveUp", 3)]
77+
[InlineData("Flat", 4)]
78+
[InlineData("FortyFiveDown", 5)]
79+
[InlineData("SingleDown", 6)]
80+
[InlineData("DoubleDown", 7)]
81+
[InlineData("NotComputable", 8)]
82+
[InlineData("RateOutOfRange", 9)]
83+
public void TrendDirectionMap_ContainsAllDirections(string direction, int expectedIndex)
84+
{
85+
Assert.True(GlucoseReading.TrendDirectionMap.ContainsKey(direction));
86+
Assert.Equal(expectedIndex, GlucoseReading.TrendDirectionMap[direction]);
87+
}
88+
89+
[Fact]
90+
public void TrendDirectionMap_IsCaseInsensitive()
91+
{
92+
Assert.True(GlucoseReading.TrendDirectionMap.ContainsKey("flat"));
93+
Assert.True(GlucoseReading.TrendDirectionMap.ContainsKey("FLAT"));
94+
Assert.True(GlucoseReading.TrendDirectionMap.ContainsKey("Flat"));
95+
}
96+
97+
[Fact]
98+
public void TrendDirectionMap_Has10Entries()
99+
{
100+
Assert.Equal(10, GlucoseReading.TrendDirectionMap.Count);
101+
}
102+
103+
// ── Default values ─────────────────────────────────────────
104+
105+
[Fact]
106+
public void Defaults_AreReasonable()
107+
{
108+
var reading = new GlucoseReading();
109+
Assert.Equal(0, reading.Value);
110+
Assert.Equal("Flat", reading.TrendDirection);
111+
Assert.Equal(0, reading.TrendIndex);
112+
}
113+
}

0 commit comments

Comments
 (0)