Skip to content

Commit fc8aa27

Browse files
committed
Add tests for SDK vulnerability checker and cache
Tests for the checker cover: vulnerable SDK reports correct CVEs, latest SDK reports none, EOL channel is detected, feature band matching works, unknown/invalid versions return null. Cache tests cover: sentinel timing, env var disable, read/write roundtrip, corrupt file handling, custom interval hours. Cache reader tests verify deserialization and the disable flag. Test fixtures under VulnerabilityTestRelease/ have two channels: 9.0 (active, with CVEs across releases) and 7.0 (EOL since 2024-05-14).
1 parent 21c4b2a commit fc8aa27

6 files changed

Lines changed: 585 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"channel-version": "7.0",
3+
"latest-release": "7.0.20",
4+
"latest-release-date": "2024-05-28",
5+
"latest-runtime": "7.0.20",
6+
"latest-sdk": "7.0.410",
7+
"support-phase": "eol",
8+
"eol-date": "2024-05-14",
9+
"lifecycle-policy": "https://www.microsoft.com/net/support/policy",
10+
"releases": [
11+
{
12+
"release-date": "2024-05-28",
13+
"release-version": "7.0.20",
14+
"security": true,
15+
"cve-list": [
16+
{
17+
"cve-id": "CVE-2024-88888",
18+
"cve-url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-88888"
19+
}
20+
],
21+
"release-notes": "https://github.com/dotnet/core/blob/main/release-notes/7.0/7.0.20/7.0.20.md",
22+
"runtime": {
23+
"version": "7.0.20",
24+
"version-display": "7.0.20",
25+
"files": []
26+
},
27+
"sdks": [
28+
{
29+
"version": "7.0.410",
30+
"version-display": "7.0.410",
31+
"runtime-version": "7.0.20",
32+
"vs-version": "",
33+
"vs-support": "",
34+
"csharp-version": "11.0",
35+
"fsharp-version": "7.0",
36+
"vb-version": "16.9",
37+
"files": []
38+
}
39+
]
40+
},
41+
{
42+
"release-date": "2024-04-09",
43+
"release-version": "7.0.19",
44+
"security": false,
45+
"release-notes": "https://github.com/dotnet/core/blob/main/release-notes/7.0/7.0.19/7.0.19.md",
46+
"runtime": {
47+
"version": "7.0.19",
48+
"version-display": "7.0.19",
49+
"files": []
50+
},
51+
"sdks": [
52+
{
53+
"version": "7.0.409",
54+
"version-display": "7.0.409",
55+
"runtime-version": "7.0.19",
56+
"vs-version": "",
57+
"vs-support": "",
58+
"csharp-version": "11.0",
59+
"fsharp-version": "7.0",
60+
"vb-version": "16.9",
61+
"files": []
62+
}
63+
]
64+
}
65+
]
66+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"channel-version": "9.0",
3+
"latest-release": "9.0.2",
4+
"latest-release-date": "2025-02-11",
5+
"latest-runtime": "9.0.2",
6+
"latest-sdk": "9.0.200",
7+
"support-phase": "active",
8+
"lifecycle-policy": "https://www.microsoft.com/net/support/policy",
9+
"releases": [
10+
{
11+
"release-date": "2025-02-11",
12+
"release-version": "9.0.2",
13+
"security": true,
14+
"cve-list": [
15+
{
16+
"cve-id": "CVE-2025-99999",
17+
"cve-url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-99999"
18+
}
19+
],
20+
"release-notes": "https://github.com/dotnet/core/blob/main/release-notes/9.0/9.0.2/9.0.2.md",
21+
"runtime": {
22+
"version": "9.0.2",
23+
"version-display": "9.0.2",
24+
"files": []
25+
},
26+
"sdks": [
27+
{
28+
"version": "9.0.200",
29+
"version-display": "9.0.200",
30+
"runtime-version": "9.0.2",
31+
"vs-version": "",
32+
"vs-support": "",
33+
"csharp-version": "13.0",
34+
"fsharp-version": "9.0",
35+
"vb-version": "16.9",
36+
"files": []
37+
}
38+
]
39+
},
40+
{
41+
"release-date": "2025-01-14",
42+
"release-version": "9.0.1",
43+
"security": true,
44+
"cve-list": [
45+
{
46+
"cve-id": "CVE-2025-12345",
47+
"cve-url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-12345"
48+
}
49+
],
50+
"release-notes": "https://github.com/dotnet/core/blob/main/release-notes/9.0/9.0.1/9.0.1.md",
51+
"runtime": {
52+
"version": "9.0.1",
53+
"version-display": "9.0.1",
54+
"files": []
55+
},
56+
"sdks": [
57+
{
58+
"version": "9.0.102",
59+
"version-display": "9.0.102",
60+
"runtime-version": "9.0.1",
61+
"vs-version": "",
62+
"vs-support": "",
63+
"csharp-version": "13.0",
64+
"fsharp-version": "9.0",
65+
"vb-version": "16.9",
66+
"files": []
67+
}
68+
]
69+
},
70+
{
71+
"release-date": "2024-11-12",
72+
"release-version": "9.0.0",
73+
"security": false,
74+
"release-notes": "https://github.com/dotnet/core/blob/main/release-notes/9.0/9.0.0/9.0.0.md",
75+
"runtime": {
76+
"version": "9.0.0",
77+
"version-display": "9.0.0",
78+
"files": []
79+
},
80+
"sdks": [
81+
{
82+
"version": "9.0.100",
83+
"version-display": "9.0.100",
84+
"runtime-version": "9.0.0",
85+
"vs-version": "",
86+
"vs-support": "",
87+
"csharp-version": "13.0",
88+
"fsharp-version": "9.0",
89+
"vb-version": "16.9",
90+
"files": []
91+
}
92+
]
93+
}
94+
]
95+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"releases-index": [
3+
{
4+
"channel-version": "9.0",
5+
"latest-release": "9.0.2",
6+
"latest-release-date": "2025-02-11",
7+
"security": true,
8+
"latest-runtime": "9.0.2",
9+
"latest-sdk": "9.0.200",
10+
"product": ".NET",
11+
"support-phase": "active",
12+
"eol-date": null,
13+
"releases.json": "https://builds.dotnet.microsoft.com/dotnet/release-metadata/9.0/releases.json"
14+
},
15+
{
16+
"channel-version": "7.0",
17+
"latest-release": "7.0.20",
18+
"latest-release-date": "2024-05-28",
19+
"security": true,
20+
"latest-runtime": "7.0.20",
21+
"latest-sdk": "7.0.410",
22+
"product": ".NET",
23+
"support-phase": "eol",
24+
"eol-date": "2024-05-14",
25+
"releases.json": "https://builds.dotnet.microsoft.com/dotnet/release-metadata/7.0/releases.json"
26+
}
27+
]
28+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json;
5+
using Microsoft.DotNet.Cli.SdkVulnerability;
6+
7+
namespace Microsoft.DotNet.Cli.Tests;
8+
9+
public class GivenSdkReleaseMetadataCache : SdkTest
10+
{
11+
public GivenSdkReleaseMetadataCache(ITestOutputHelper log) : base(log)
12+
{
13+
}
14+
15+
[Fact]
16+
public void IsDisabledWhenEnvVarIsSet()
17+
{
18+
var cache = new SdkReleaseMetadataCache(
19+
GetTempCacheDir(),
20+
name => name == EnvironmentVariableNames.SDK_VULNERABILITY_CHECK_DISABLE ? "true" : null);
21+
22+
cache.IsDisabled().Should().BeTrue();
23+
}
24+
25+
[Fact]
26+
public void IsNotDisabledByDefault()
27+
{
28+
var cache = new SdkReleaseMetadataCache(
29+
GetTempCacheDir(),
30+
_ => null);
31+
32+
cache.IsDisabled().Should().BeFalse();
33+
}
34+
35+
[Fact]
36+
public void IsDueForUpdateWhenNoSentinelExists()
37+
{
38+
var cache = new SdkReleaseMetadataCache(
39+
GetTempCacheDir(),
40+
_ => null);
41+
42+
cache.IsDueForUpdate().Should().BeTrue();
43+
}
44+
45+
[Fact]
46+
public void IsNotDueForUpdateWhenSentinelIsRecent()
47+
{
48+
string cacheDir = GetTempCacheDir();
49+
Directory.CreateDirectory(cacheDir);
50+
string sentinelPath = Path.Combine(cacheDir, ".sentinel");
51+
File.Create(sentinelPath).Close();
52+
File.SetLastAccessTime(sentinelPath, DateTime.Now);
53+
54+
var cache = new SdkReleaseMetadataCache(cacheDir, _ => null);
55+
56+
cache.IsDueForUpdate().Should().BeFalse();
57+
}
58+
59+
[Fact]
60+
public void IsDueForUpdateWhenSentinelIsOld()
61+
{
62+
string cacheDir = GetTempCacheDir();
63+
Directory.CreateDirectory(cacheDir);
64+
string sentinelPath = Path.Combine(cacheDir, ".sentinel");
65+
File.Create(sentinelPath).Close();
66+
File.SetLastAccessTime(sentinelPath, DateTime.Now.AddHours(-25));
67+
68+
var cache = new SdkReleaseMetadataCache(cacheDir, _ => null);
69+
70+
cache.IsDueForUpdate().Should().BeTrue();
71+
}
72+
73+
[Fact]
74+
public void ReadsNullWhenNoCacheExists()
75+
{
76+
var cache = new SdkReleaseMetadataCache(
77+
GetTempCacheDir(),
78+
_ => null);
79+
80+
cache.ReadCachedSummary("9.0.100").Should().BeNull();
81+
}
82+
83+
[Fact]
84+
public void ReadsCachedSummarySuccessfully()
85+
{
86+
string cacheDir = GetTempCacheDir();
87+
Directory.CreateDirectory(cacheDir);
88+
89+
var info = new SdkVulnerabilityInfo
90+
{
91+
IsEol = true,
92+
EolDate = new DateTime(2024, 5, 14),
93+
Cves = [new SdkCveInfo { Id = "CVE-2025-12345", Url = "https://example.com" }],
94+
LatestSdkVersion = "9.0.200"
95+
};
96+
97+
string json = JsonSerializer.Serialize(info, SdkVulnerabilityJsonContext.Default.SdkVulnerabilityInfo);
98+
File.WriteAllText(Path.Combine(cacheDir, "sdk-status-9.0.100.json"), json);
99+
100+
var cache = new SdkReleaseMetadataCache(cacheDir, _ => null);
101+
var result = cache.ReadCachedSummary("9.0.100");
102+
103+
result.Should().NotBeNull();
104+
result!.IsEol.Should().BeTrue();
105+
result.HasVulnerabilities.Should().BeTrue();
106+
result.Cves.Should().Contain(c => c.Id == "CVE-2025-12345");
107+
result.LatestSdkVersion.Should().Be("9.0.200");
108+
}
109+
110+
[Fact]
111+
public void ReturnsNullForCorruptCacheFile()
112+
{
113+
string cacheDir = GetTempCacheDir();
114+
Directory.CreateDirectory(cacheDir);
115+
File.WriteAllText(Path.Combine(cacheDir, "sdk-status-9.0.100.json"), "not valid json");
116+
117+
var cache = new SdkReleaseMetadataCache(cacheDir, _ => null);
118+
119+
cache.ReadCachedSummary("9.0.100").Should().BeNull();
120+
}
121+
122+
[Fact]
123+
public void CustomIntervalHoursIsRespected()
124+
{
125+
string cacheDir = GetTempCacheDir();
126+
Directory.CreateDirectory(cacheDir);
127+
string sentinelPath = Path.Combine(cacheDir, ".sentinel");
128+
File.Create(sentinelPath).Close();
129+
// Set sentinel to 2 hours ago
130+
File.SetLastAccessTime(sentinelPath, DateTime.Now.AddHours(-2));
131+
132+
// With default 24h interval, should not be due
133+
var cache24h = new SdkReleaseMetadataCache(cacheDir, _ => null);
134+
cache24h.IsDueForUpdate().Should().BeFalse();
135+
136+
// With 1h interval, should be due
137+
var cache1h = new SdkReleaseMetadataCache(
138+
cacheDir,
139+
name => name == EnvironmentVariableNames.SDK_VULNERABILITY_CHECK_INTERVAL_HOURS ? "1" : null);
140+
cache1h.IsDueForUpdate().Should().BeTrue();
141+
}
142+
143+
private string GetTempCacheDir()
144+
{
145+
return Path.Combine(Path.GetTempPath(), "sdk-vuln-test-" + Guid.NewGuid().ToString("N")[..8]);
146+
}
147+
}

0 commit comments

Comments
 (0)