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

Aldaviva / FreshPager / 19227176152

10 Nov 2025 09:37AM UTC coverage: 76.087% (-0.3%) from 76.389%
19227176152

push

github

Aldaviva
Fix locked dependency hashes

34 of 108 branches covered (31.48%)

175 of 230 relevant lines covered (76.09%)

5.63 hits per line

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

94.52
/FreshPager/API/FreshpingResource.cs
1
using FreshPager.Data;
2
using Microsoft.AspNetCore.Mvc;
3
using Microsoft.Extensions.Options;
4
using Pager.Duty;
5
using Pager.Duty.Exceptions;
6
using Pager.Duty.Requests;
7
using Pager.Duty.Responses;
8
using System.Collections.Concurrent;
9
using ThrottleDebounce.Retry;
10

11
namespace FreshPager.API;
12

13
public class FreshpingResource: WebResource {
14

15
    private static readonly RetryOptions RETRY_OPTIONS = new() {
1✔
16
        MaxAttempts    = 20,
1✔
17
        Delay          = Delays.Exponential(TimeSpan.FromSeconds(5), max: TimeSpan.FromMinutes(5)),
1✔
18
        IsRetryAllowed = (exception, _) => exception is not (OutOfMemoryException or PagerDutyException { RetryAllowedAfterDelay: false })
×
19
    };
1✔
20

21
    private readonly  ILogger<FreshpingResource>                   logger;
22
    private readonly  IOptions<Configuration>                      configuration;
23
    internal readonly ConcurrentDictionary<FreshpingCheck, string> dedupKeys;
24

25
    public FreshpingResource(ILogger<FreshpingResource> logger, IOptions<Configuration> configuration) {
5✔
26
        this.logger        = logger;
5✔
27
        this.configuration = configuration;
5✔
28
        int checkCount = configuration.Value.pagerDutyIntegrationKeysByFreshpingCheckId.Count;
5✔
29
        dedupKeys = new ConcurrentDictionary<FreshpingCheck, string>(Math.Min(checkCount * 2, Environment.ProcessorCount), checkCount);
5✔
30
    }
5✔
31

32
    public void map(WebApplication webapp) {
33
        webapp.MapPost("/freshping", async Task<IResult> ([FromBody] FreshpingWebhookPayload payload, PagerDutyFactory pagerDutyFactory) => {
5✔
34
            FreshpingCheck check = payload.check;
8✔
35
            logger.Trace("Received webhook payload from Freshping: {payload}", payload);
8✔
36

5✔
37
            if (configuration.Value.pagerDutyIntegrationKeysByFreshpingCheckId.TryGetValue(check.id, out string? integrationKey)) {
8✔
38
                using IPagerDuty pagerDuty = pagerDutyFactory(integrationKey);
6✔
39

5✔
40
                if (!payload.isCheckUp) {
6✔
41
                    return await onCheckDown(check, pagerDuty, payload);
3✔
42
                } else {
5✔
43
                    return await onCheckUp(check, pagerDuty);
3✔
44
                }
5✔
45
            } else {
5✔
46
                logger.Warn("No PagerDuty integration key configured for Freshping check {check}, not sending an alert to PagerDuty", check.name);
2✔
47
                return Results.NoContent();
2✔
48
            }
5✔
49
        });
13✔
50
        logger.Debug("Listening for Freshping webhooks");
5✔
51
    }
5✔
52

53
    private async Task<IResult> onCheckDown(FreshpingCheck check, IPagerDuty pagerDuty, FreshpingWebhookPayload requestBody) {
54
        logger.Info("Freshping reports that {check} is down", check.name);
3✔
55
        dedupKeys.TryGetValue(check, out string? oldDedupKey);
3✔
56
        Uri reportUrl = new UrlBuilder("https", $"{requestBody.organizationSubdomain}.freshping.io").Path("reports").QueryParam("check_id", requestBody.checkId);
3✔
57

58
        try {
59
            TriggerAlert triggerAlert = new(Severity.Error, requestBody.eventTitle) {
3!
60
                DedupKey  = oldDedupKey,
3✔
61
                Timestamp = requestBody.requestDateTime,
3✔
62
                Links = {
3✔
63
                    new Link(reportUrl, "Freshping Report"),
3✔
64
                    new Link(requestBody.checkedUrl, "Checked Service URL")
3✔
65
                },
3✔
66
                CustomDetails = new Dictionary<string, object> {
3✔
67
                    ["Check Name"]              = requestBody.checkName,
3✔
68
                    ["Request Location"]        = requestBody.requestLocation,
3✔
69
                    ["Response Duration (sec)"] = requestBody.responseTime.TotalSeconds,
3✔
70
                    ["Response State"]          = requestBody.responseState,
3✔
71
                    ["Response Status Code"]    = requestBody.responseStatusCode?.ToString() ?? "(none)",
3✔
72
                    ["Response Summary"]        = requestBody.responseSummary
3✔
73
                },
3✔
74

3✔
75
                // The following fields only appear on the webapp alert details page, and nowhere in the mobile app
3✔
76
                Class     = requestBody.responseSummary,
3✔
77
                Component = requestBody.checkedUrl.ToString(),
3✔
78
                Source    = requestBody.requestLocation
3✔
79
            };
3✔
80

81
            string newDedupKey = await Retrier.Attempt(async _ => {
3✔
82
                AlertResponse alertResponse = await pagerDuty.Send(triggerAlert);
3✔
83
                dedupKeys[check] = alertResponse.DedupKey;
3✔
84
                return alertResponse.DedupKey;
3✔
85
            }, RETRY_OPTIONS);
6✔
86

87
            logger.Info("Triggered alert in PagerDuty for {check} being down, got deduplication key {key}", check.name, newDedupKey);
3✔
88
        } catch (Exception e) when (e is not OutOfMemoryException) {
3✔
89
            logger.Error(e, "Failed to trigger alert in PagerDuty after {attempts}, giving up", RETRY_OPTIONS.MaxAttempts);
×
90
            return Results.Problem(statusCode: StatusCodes.Status503ServiceUnavailable, detail: "Failed to trigger PagerDuty alert");
×
91
        }
92
        return Results.Created();
3✔
93
    }
3✔
94

95
    private async Task<IResult> onCheckUp(FreshpingCheck check, IPagerDuty pagerDuty) {
96
        logger.Info("Freshping reports that {check} is up", check.name);
3✔
97
        if (dedupKeys.TryRemove(check, out string? dedupKey)) {
3!
98
            ResolveAlert resolution = new(dedupKey);
3✔
99
            await Retrier.Attempt(async _ => await pagerDuty.Send(resolution), RETRY_OPTIONS);
6✔
100
            logger.Info("Resolved PagerDuty alert for {check} being down using deduplication key {key}", check.name, dedupKey);
3✔
101
        } else {
102
            logger.Warn("No known PagerDuty alerts for check {check}, not resolving anything", check.name);
×
103
        }
104
        return Results.NoContent();
3✔
105
    }
3✔
106

107
}
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