• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Aldaviva / GamesDoneQuickCalendarFactory / 20709849965

05 Jan 2026 08:40AM UTC coverage: 77.21% (+0.09%) from 77.12%
20709849965

push

github

Aldaviva
Prevent web-tailored run title formatting (\n) from appearing in calendar events.

126 of 199 branches covered (63.32%)

5 of 5 new or added lines in 2 files covered. (100.0%)

15 existing lines in 6 files now uncovered.

393 of 509 relevant lines covered (77.21%)

153.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.32
/GamesDoneQuickCalendarFactory/Program.cs
1
using Bom.Squad;
2
using GamesDoneQuickCalendarFactory;
3
using GamesDoneQuickCalendarFactory.Data;
4
using GamesDoneQuickCalendarFactory.Services;
5
using Ical.Net.Serialization;
6
using Microsoft.AspNetCore.Http.Headers;
7
using Microsoft.AspNetCore.HttpOverrides;
8
using Microsoft.AspNetCore.Mvc;
9
using Microsoft.AspNetCore.OutputCaching;
10
using Microsoft.Net.Http.Headers;
11
using NodaTime;
12
using System.Net.Mime;
13
using System.Text;
14
using System.Text.RegularExpressions;
15
using Unfucked;
16
using Unfucked.DateTime;
17
using Unfucked.HTTP;
18

19
BomSquad.DefuseUtf8Bom();
10✔
20

21
Encoding             responseEncoding     = Encoding.UTF8;
10✔
22
MediaTypeHeaderValue icalendarContentType = new("text/calendar") { Charset = responseEncoding.WebName };
10✔
23
string[]             varyHeaderValue      = [HeaderNames.AcceptEncoding];
10✔
24

25
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
10✔
26
builder.Host
10✔
27
    .UseWindowsService()
10✔
28
    .UseSystemd();
10✔
29

30
builder.Logging.AddUnfuckedConsole();
10✔
31

32
builder.Configuration.AlsoSearchForJsonFilesInExecutableDirectory();
10✔
33

34
// GZIP response compression is handled by Apache httpd, not Kestrel, per https://learn.microsoft.com/en-us/aspnet/core/performance/response-compression?view=aspnetcore-8.0#when-to-use-response-compression-middleware
35
builder.Services
10✔
36
    .Configure<Configuration>(builder.Configuration)
10✔
37
    .AddOutputCache()
10✔
38
    .AddSingleton<ICalendarGenerator, CalendarGenerator>()
10✔
39
    .AddSingleton<IEventDownloader, EventDownloader>()
10✔
40
    .AddSingleton<IGdqClient, GdqClient>()
10✔
41
    .AddSingleton<ICalendarPoller, CalendarPoller>()
10✔
42
    .AddSingleton<IGoogleCalendarSynchronizer, GoogleCalendarSynchronizer>()
10✔
43
    .AddSingleton<IClock>(SystemClock.Instance)
10✔
44
    .AddSingleton<HttpClient>(new UnfuckedHttpClient(new SocketsHttpHandler { PooledConnectionLifetime = (Minutes) 15 }) { Timeout = (Seconds) 30 });
10✔
45

46
builder.Services.AddSingleton(await State.load("state.json"));
10✔
47

48
await using WebApplication webApp = builder.Build();
10✔
49

50
webApp
10✔
51
    .UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto })
10✔
52
    .UseOutputCache()
10✔
53
    .Use(async (context, next) => {
10✔
54
        ICalendarPoller calendarPoller  = context.RequestServices.GetRequiredService<ICalendarPoller>();
10✔
55
        ResponseHeaders responseHeaders = context.Response.GetTypedHeaders();
10✔
56
        responseHeaders.CacheControl               = new CacheControlHeaderValue { Public = true, MaxAge = calendarPoller.getPollingInterval() }; // longer cache when no event running
10✔
57
        context.Response.Headers[HeaderNames.Vary] = varyHeaderValue;
10✔
58

10✔
59
        if (await calendarPoller.mostRecentlyPolledCalendar.ResultOrNullForException() is {} mostRecentlyPolledCalendar) {
10✔
60
            responseHeaders.ETag         = mostRecentlyPolledCalendar.etag;
10✔
61
            responseHeaders.LastModified = mostRecentlyPolledCalendar.dateModified.ToDateTimeOffset();
10✔
62
        }
10✔
63
        await next();
10✔
64
    });
20✔
65

66
webApp.MapGet("/", [OutputCache] async Task ([FromServices] ICalendarPoller calendarPoller, HttpResponse response) => {
10✔
67
    try {
10✔
68
        if (await calendarPoller.mostRecentlyPolledCalendar is {} mostRecentlyPolledCalendar) {
4✔
69
            response.GetTypedHeaders().ContentType = icalendarContentType;
4✔
70
            await new CalendarSerializer().SerializeAsync(mostRecentlyPolledCalendar.calendar, response.Body, responseEncoding);
4✔
71
        } else {
10✔
UNCOV
72
            response.StatusCode = StatusCodes.Status204NoContent;
×
73
        }
10✔
74
    } catch (Exception e) when (e is not OutOfMemoryException) {
4✔
75
        response.StatusCode  = StatusCodes.Status500InternalServerError;
×
76
        response.ContentType = MediaTypeNames.Text.Plain;
×
77
        await using StreamWriter bodyWriter = new(response.Body, responseEncoding);
×
78
        await bodyWriter.WriteAsync(e.ToString());
×
UNCOV
79
    }
×
80
});
10✔
81

82
webApp.MapGet("/badge.json", [OutputCache] async ([FromServices] IEventDownloader eventDownloader) =>
10✔
83
await eventDownloader.downloadSchedule() is {} schedule
16✔
84
    ? new ShieldsBadgeResponse(
16✔
85
        label: shortNamePattern().Replace(schedule.shortTitle, " ").ToLower(), // add spaces to abbreviation
16✔
86
        message: $"{schedule.runs.Count} {(schedule.runs.Count == 1 ? "run" : "runs")}",
16✔
87
        color: "success",
16✔
88
        logoSvg: Resources.gdqDpadBadgeLogo)
16✔
89
    : new ShieldsBadgeResponse("gdq", "no event now", "inactive", false, Resources.gdqDpadBadgeLogo));
16✔
90

91
await webApp.Services.GetRequiredService<IGoogleCalendarSynchronizer>().start();
10✔
92

93
await webApp.RunAsync();
10✔
94

95
internal partial class Program {
96

97
    [GeneratedRegex(@"(?<=\D)(?=\d)|(?<=[a-z])(?=[A-Z])")]
98
    private static partial Regex shortNamePattern();
99

100
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc