Skip to content

Commit 6c47ca9

Browse files
committed
test: Cover the publish reachability probe, and switch it off in the suite
The probe broke three publish tests. The integration suite's Firmware:CdnBaseUrl is cdn-test.openshock.example, a hostname chosen not to resolve, because storage there is a Local backend and the assertions read the files off disk. Probing it failed every publish, rolled the promotion back, and took the abort test with it when the published artifacts it looks for were never written. So the suite sets VerifyPublishedArtifacts=false, and the probe gets its own tests against a stubbed transport instead: 200 reachable, 404 reported, 405 counted as present, transport failure reported, a miss retried before it counts, one request per artifact, and no requests at all when disabled. Those need neither a database nor a container, which is also why they are worth having - the behaviour is in how HTTP responses are read, and supplying the responses directly tests exactly that without a CDN to stand up.
1 parent d69505f commit 6c47ca9

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

‎RepositoryServer.Tests/Integration/WebApplicationFactory.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
205205
["Firmware:Storage:Local:BasePath"] = _cdnStoragePath,
206206
["Firmware:StagedReleaseTtl"] = "01:00:00",
207207
["Firmware:EditingReleaseTtl"] = "7.00:00:00",
208+
209+
// cdn-test.openshock.example does not resolve, and is not meant to: storage here is a
210+
// Local backend under _cdnStoragePath, and the assertions read it off disk. Leaving the
211+
// publish-time reachability probe on would make every publish test fail on a DNS lookup
212+
// for a hostname invented to be unreachable. The probe has its own tests, which point
213+
// Firmware:CdnBaseUrl at the test server and check both outcomes.
214+
["Firmware:VerifyPublishedArtifacts"] = "false",
208215
};
209216
foreach (var (key, value) in settings)
210217
{
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using Microsoft.Extensions.Logging.Abstractions;
2+
using OpenShock.RepositoryServer.Config;
3+
using OpenShock.RepositoryServer.Enums;
4+
using OpenShock.RepositoryServer.Services;
5+
using System.Net;
6+
7+
namespace OpenShock.RepositoryServer.Tests;
8+
9+
/// <summary>
10+
/// Covers the publish-time reachability probe against a stubbed transport.
11+
/// </summary>
12+
/// <remarks>
13+
/// No database and no container: what is under test is how the verifier reads HTTP responses, so
14+
/// the responses are supplied directly. That also keeps the probe's own behaviour tested in an
15+
/// environment where the integration suite has it switched off, which it must be — the suite's
16+
/// CdnBaseUrl is a hostname invented not to resolve.
17+
/// </remarks>
18+
public class PublishedArtifactVerifierTests
19+
{
20+
private static readonly Guid Board = Guid.Parse("11111111-1111-1111-1111-111111111111");
21+
22+
private static readonly (Guid, FirmwareArtifactType)[] OneArtifact =
23+
[(Board, FirmwareArtifactType.App)];
24+
25+
/// <summary>Answers every request the same way, and counts them.</summary>
26+
private sealed class StubHandler : HttpMessageHandler
27+
{
28+
private readonly Func<HttpRequestMessage, HttpResponseMessage> _respond;
29+
public int Calls { get; private set; }
30+
public List<HttpMethod> Methods { get; } = [];
31+
32+
public StubHandler(HttpStatusCode status)
33+
: this(_ => new HttpResponseMessage(status)) { }
34+
35+
public StubHandler(Func<HttpRequestMessage, HttpResponseMessage> respond) => _respond = respond;
36+
37+
protected override Task<HttpResponseMessage> SendAsync(
38+
HttpRequestMessage request, CancellationToken cancellationToken)
39+
{
40+
Calls++;
41+
Methods.Add(request.Method);
42+
return Task.FromResult(_respond(request));
43+
}
44+
}
45+
46+
private static PublishedArtifactVerifier Build(StubHandler handler, bool verify = true) =>
47+
new(new HttpClient(handler),
48+
new ApiConfig
49+
{
50+
Db = new DbConfig { Conn = "Host=localhost;Database=x;Username=x;Password=x" },
51+
Modules = new ModulesConfig
52+
{
53+
Id = "test", Name = "test", Author = "test",
54+
CdnBaseUrl = "https://cdn.example/repo"
55+
},
56+
Firmware = new FirmwareConfig
57+
{
58+
CdnBaseUrl = "https://cdn.example/firmware",
59+
Storage = new StorageConfig { Type = StorageType.Local },
60+
VerifyPublishedArtifacts = verify
61+
},
62+
CiCd = new CiCdConfig { Audience = "test" }
63+
},
64+
NullLogger<PublishedArtifactVerifier>.Instance);
65+
66+
[Test]
67+
public async Task Reachable_ReportsNothing()
68+
{
69+
var handler = new StubHandler(HttpStatusCode.OK);
70+
71+
var unreachable = await Build(handler).FindUnreachableAsync("1.0.0", OneArtifact);
72+
73+
await Assert.That(unreachable).IsEmpty();
74+
// HEAD, not GET: these are megabytes and the body is not wanted.
75+
await Assert.That(handler.Methods).Contains(HttpMethod.Head);
76+
}
77+
78+
/// <summary>
79+
/// The case this exists for: the object was written to storage, and the hostname the server
80+
/// advertises does not serve it.
81+
/// </summary>
82+
[Test]
83+
public async Task NotFound_IsReported()
84+
{
85+
var unreachable = await Build(new StubHandler(HttpStatusCode.NotFound))
86+
.FindUnreachableAsync("1.0.0", OneArtifact);
87+
88+
await Assert.That(unreachable).HasCount(1);
89+
await Assert.That(unreachable[0].Status).IsEqualTo(HttpStatusCode.NotFound);
90+
await Assert.That(unreachable[0].Url).Contains("1.0.0");
91+
}
92+
93+
/// <summary>
94+
/// A store that does not implement HEAD says so with 405. The object is evidently there, so
95+
/// refusing the publish over an unsupported method would be a false failure.
96+
/// </summary>
97+
[Test]
98+
public async Task MethodNotAllowed_CountsAsPresent()
99+
{
100+
var unreachable = await Build(new StubHandler(HttpStatusCode.MethodNotAllowed))
101+
.FindUnreachableAsync("1.0.0", OneArtifact);
102+
103+
await Assert.That(unreachable).IsEmpty();
104+
}
105+
106+
[Test]
107+
public async Task TransportFailure_IsReported()
108+
{
109+
var handler = new StubHandler(_ => throw new HttpRequestException("no such host"));
110+
111+
var unreachable = await Build(handler).FindUnreachableAsync("1.0.0", OneArtifact);
112+
113+
await Assert.That(unreachable).HasCount(1);
114+
await Assert.That(unreachable[0].Status).IsNull();
115+
}
116+
117+
/// <summary>
118+
/// A miss is retried before it counts, so edge propagation does not fail a good publish. An
119+
/// artifact that appears on the second attempt is reachable.
120+
/// </summary>
121+
[Test]
122+
public async Task Miss_IsRetriedBeforeItCounts()
123+
{
124+
var attempt = 0;
125+
var handler = new StubHandler(_ =>
126+
new HttpResponseMessage(++attempt == 1 ? HttpStatusCode.NotFound : HttpStatusCode.OK));
127+
128+
var unreachable = await Build(handler).FindUnreachableAsync("1.0.0", OneArtifact);
129+
130+
await Assert.That(unreachable).IsEmpty();
131+
await Assert.That(handler.Calls).IsEqualTo(2);
132+
}
133+
134+
/// <summary>
135+
/// Switched off, it makes no requests at all — for deployments that genuinely cannot reach
136+
/// their own CDN hostname, and for the integration suite.
137+
/// </summary>
138+
[Test]
139+
public async Task Disabled_MakesNoRequests()
140+
{
141+
var handler = new StubHandler(HttpStatusCode.NotFound);
142+
143+
var unreachable = await Build(handler, verify: false).FindUnreachableAsync("1.0.0", OneArtifact);
144+
145+
await Assert.That(unreachable).IsEmpty();
146+
await Assert.That(handler.Calls).IsEqualTo(0);
147+
}
148+
149+
/// <summary>
150+
/// One request per artifact, not per board: a release is up to five artifacts across a dozen
151+
/// boards and every one of them has to be accounted for.
152+
/// </summary>
153+
[Test]
154+
public async Task ProbesEveryArtifact()
155+
{
156+
var handler = new StubHandler(HttpStatusCode.OK);
157+
(Guid, FirmwareArtifactType)[] artifacts =
158+
[
159+
(Board, FirmwareArtifactType.App),
160+
(Board, FirmwareArtifactType.StaticFs),
161+
(Board, FirmwareArtifactType.Merged)
162+
];
163+
164+
await Build(handler).FindUnreachableAsync("1.0.0", artifacts);
165+
166+
await Assert.That(handler.Calls).IsEqualTo(3);
167+
}
168+
}

0 commit comments

Comments
 (0)