diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..9e028ef
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "csharpier": {
+ "version": "1.3.0",
+ "commands": [
+ "csharpier"
+ ],
+ "rollForward": false
+ }
+ }
+}
diff --git a/.editorconfig b/.editorconfig
index 6c81757..e872bb7 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,46 @@
-[*.cs]
+[*.cs]
# IDE0028: Simplify collection initialization
dotnet_diagnostic.IDE0028.severity = none
+
+# Formatting follows CSharpier; `dotnet csharpier check` enforces it in CI. These keys keep the IDE
+# formatter consistent with it, and IDE0055 is off because some layouts, such as a multi-line
+# constructor initializer before an empty body, cannot be expressed to it.
+dotnet_diagnostic.IDE0055.severity = none
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c9ded5c..fb2ba00 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -42,16 +42,21 @@ jobs:
with:
dotnet-version: |
10.0.x
- 11.0.100-preview.7.26381.103
+ 11.0.100-rc.1.26425.128
- name: Restore dependencies
run: dotnet restore KoFi.Client.slnx
+ - name: Check formatting
+ run: |
+ dotnet tool restore
+ dotnet csharpier check .
+
- name: Build solution
run: dotnet build KoFi.Client.slnx --configuration Release --no-restore
- name: Run unit tests
- run: dotnet test tests/KoFi.Client.Tests/KoFi.Client.Tests.csproj --configuration Release --no-build --verbosity normal -- --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml
+ run: dotnet test --project ${{ github.workspace }}/tests/KoFi.Client.Tests/KoFi.Client.Tests.csproj --configuration Release --no-build --verbosity normal -- --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml
- name: Summarize coverage
diff --git a/Directory.Build.props b/Directory.Build.props
index ccd07a5..209f74c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -21,8 +21,8 @@
https://github.com/Agash/KoFi.Client
ko-fi;kofi;donations;creator-economy;webhooks;api-client;dotnet;csharp;aot;nativeaot
https://github.com/Agash/KoFi.Client
- true
+ true
README.md
-
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 023a0bc..ff38400 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,7 +1,13 @@
-
+
diff --git a/global.json b/global.json
index 6827f37..0f28551 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "11.0.100-preview.7.26381.103",
+ "version": "11.0.100-rc.1.26425.128",
"allowPrerelease": true,
"rollForward": "latestPreview"
},
diff --git a/samples/KoFi.Client.Sample/KoFi.Client.Sample.csproj b/samples/KoFi.Client.Sample/KoFi.Client.Sample.csproj
index f60baa6..d604d66 100644
--- a/samples/KoFi.Client.Sample/KoFi.Client.Sample.csproj
+++ b/samples/KoFi.Client.Sample/KoFi.Client.Sample.csproj
@@ -1,30 +1,28 @@
-
- net10.0
- Exe
- enable
- enable
- preview
- true
- false
- false
- false
- $(NoWarn);CA1305
- KoFi.Client.Sample
-
+
+ net11.0
+ Exe
+ enable
+ enable
+ preview
+ true
+ false
+ true
+ KoFi.Client.Sample
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
\ No newline at end of file
+
+
+
+
diff --git a/samples/KoFi.Client.Sample/Program.cs b/samples/KoFi.Client.Sample/Program.cs
index a4a48e4..e6b0f9c 100644
--- a/samples/KoFi.Client.Sample/Program.cs
+++ b/samples/KoFi.Client.Sample/Program.cs
@@ -1,3 +1,4 @@
+using System.Collections.Concurrent;
using DevTunnels.Client;
using DevTunnels.Client.Authentication;
using DevTunnels.Client.Hosting;
@@ -10,7 +11,6 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Spectre.Console;
-using System.Collections.Concurrent;
CancellationTokenSource shutdown = new();
@@ -45,11 +45,11 @@ public static async Task RunAsync(CancellationToken cancellationToken)
{
AnsiConsole.Clear();
- AnsiConsole.Write(
- new FigletText("KoFi Sample")
- .Color(Color.HotPink));
+ AnsiConsole.Write(new FigletText("KoFi Sample").Color(Color.HotPink));
- AnsiConsole.MarkupLine("[grey]Transport-neutral Ko-fi webhook sample with ASP.NET Core, Spectre.Console, and DevTunnels.Client.[/]");
+ AnsiConsole.MarkupLine(
+ "[grey]Transport-neutral Ko-fi webhook sample with ASP.NET Core, Spectre.Console, and DevTunnels.Client.[/]"
+ );
AnsiConsole.WriteLine();
SampleConfiguration configuration = PromptConfiguration();
@@ -66,10 +66,13 @@ public static async Task RunAsync(CancellationToken cancellationToken)
_ = app.MapGet(
"/",
- () => Results.Text(
- "KoFi.Client.Sample is running.\n" +
- "POST Ko-fi webhook payloads to the configured route.\n",
- "text/plain"));
+ () =>
+ Results.Text(
+ "KoFi.Client.Sample is running.\n"
+ + "POST Ko-fi webhook payloads to the configured route.\n",
+ "text/plain"
+ )
+ );
_ = app.MapKoFiWebhook(
configuration.WebhookPath,
@@ -89,7 +92,8 @@ public static async Task RunAsync(CancellationToken cancellationToken)
{
lock (consoleLock)
{
- string remoteIp = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
+ string remoteIp =
+ httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
string requestId = httpContext.TraceIdentifier;
string auth = result.IsAuthenticated ? "[green]yes[/]" : "[red]no[/]";
@@ -97,16 +101,20 @@ public static async Task RunAsync(CancellationToken cancellationToken)
string status = $"[blue]{result.Response.StatusCode}[/]";
AnsiConsole.MarkupLineInterpolated(
- $"[grey]Request[/] [white]{Markup.Escape(requestId)}[/] from [white]{Markup.Escape(remoteIp)}[/] -> status {status}, authenticated {auth}, known event {known}.");
+ $"[grey]Request[/] [white]{Markup.Escape(requestId)}[/] from [white]{Markup.Escape(remoteIp)}[/] -> status {status}, authenticated {auth}, known event {known}."
+ );
if (!string.IsNullOrWhiteSpace(result.FailureReason))
{
- AnsiConsole.MarkupLineInterpolated($"[yellow]Reason:[/] {Markup.Escape(result.FailureReason)}");
+ AnsiConsole.MarkupLineInterpolated(
+ $"[yellow]Reason:[/] {Markup.Escape(result.FailureReason)}"
+ );
}
}
await Task.CompletedTask.ConfigureAwait(false);
- });
+ }
+ );
string localBaseUrl = $"http://127.0.0.1:{configuration.LocalPort}";
@@ -118,13 +126,20 @@ public static async Task RunAsync(CancellationToken cancellationToken)
if (configuration.UseDevTunnels)
{
- devTunnelsRuntime = await StartDevTunnelsAsync(configuration, cancellationToken).ConfigureAwait(false);
+ devTunnelsRuntime = await StartDevTunnelsAsync(configuration, cancellationToken)
+ .ConfigureAwait(false);
RenderTunnelSummary(configuration, devTunnelsRuntime.PublicBaseUrl);
}
RenderUsageInstructions(configuration, localBaseUrl, devTunnelsRuntime?.PublicBaseUrl);
- await RunCommandLoopAsync(configuration, receivedEvents, devTunnelsRuntime, consoleLock, cancellationToken)
+ await RunCommandLoopAsync(
+ configuration,
+ receivedEvents,
+ devTunnelsRuntime,
+ consoleLock,
+ cancellationToken
+ )
.ConfigureAwait(false);
if (devTunnelsRuntime is not null)
@@ -142,14 +157,18 @@ private static SampleConfiguration PromptConfiguration()
new TextPrompt("Local [green]HTTP port[/]?")
.DefaultValue(5073)
.ValidationErrorMessage("[red]Please enter a valid port.[/]")
- .Validate(port => port is > 0 and <= 65535
- ? ValidationResult.Success()
- : ValidationResult.Error("[red]Port must be between 1 and 65535.[/]")));
+ .Validate(port =>
+ port is > 0 and <= 65535
+ ? ValidationResult.Success()
+ : ValidationResult.Error("[red]Port must be between 1 and 65535.[/]")
+ )
+ );
string webhookPath = AnsiConsole.Prompt(
new TextPrompt("Webhook [green]path[/]?")
.DefaultValue("/webhooks/kofi/events")
- .AllowEmpty());
+ .AllowEmpty()
+ );
if (string.IsNullOrWhiteSpace(webhookPath))
{
@@ -164,9 +183,13 @@ private static SampleConfiguration PromptConfiguration()
string verificationToken = AnsiConsole.Prompt(
new TextPrompt("Ko-fi [green]verification token[/]?")
.PromptStyle("hotpink")
- .Secret());
+ .Secret()
+ );
- bool useDevTunnels = AnsiConsole.Confirm("Use [green]Azure Dev Tunnels[/] for a public HTTPS URL?", true);
+ bool useDevTunnels = AnsiConsole.Confirm(
+ "Use [green]Azure Dev Tunnels[/] for a public HTTPS URL?",
+ true
+ );
string tunnelId = "kofi-client-sample";
LoginProvider loginProvider = LoginProvider.GitHub;
@@ -176,7 +199,8 @@ private static SampleConfiguration PromptConfiguration()
tunnelId = AnsiConsole.Prompt(
new TextPrompt("Dev Tunnel [green]tunnel ID[/]?")
.DefaultValue("kofi-client-sample")
- .AllowEmpty());
+ .AllowEmpty()
+ );
if (string.IsNullOrWhiteSpace(tunnelId))
{
@@ -186,7 +210,8 @@ private static SampleConfiguration PromptConfiguration()
loginProvider = AnsiConsole.Prompt(
new SelectionPrompt()
.Title("Login provider for [green]devtunnel[/]?")
- .AddChoices(LoginProvider.GitHub, LoginProvider.Microsoft));
+ .AddChoices(LoginProvider.GitHub, LoginProvider.Microsoft)
+ );
}
return new SampleConfiguration(
@@ -195,71 +220,88 @@ private static SampleConfiguration PromptConfiguration()
VerificationToken: verificationToken,
UseDevTunnels: useDevTunnels,
TunnelId: tunnelId,
- LoginProvider: loginProvider);
+ LoginProvider: loginProvider
+ );
}
private static async Task StartDevTunnelsAsync(
SampleConfiguration configuration,
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken
+ )
{
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold]Azure Dev Tunnels walkthrough[/]");
AnsiConsole.WriteLine();
- DevTunnelsClient client = new(new DevTunnelsClientOptions
- {
- CommandTimeout = TimeSpan.FromSeconds(20),
- });
+ DevTunnelsClient client = new(
+ new DevTunnelsClientOptions { CommandTimeout = TimeSpan.FromSeconds(20) }
+ );
- DevTunnelCliProbeResult probe = await client.ProbeCliAsync(cancellationToken).ConfigureAwait(false);
+ DevTunnelCliProbeResult probe = await client
+ .ProbeCliAsync(cancellationToken)
+ .ConfigureAwait(false);
if (!probe.IsInstalled)
{
throw new InvalidOperationException(
- "The devtunnel CLI is not installed or could not be found. " +
- "Install it first, then re-run the sample.");
+ "The devtunnel CLI is not installed or could not be found. "
+ + "Install it first, then re-run the sample."
+ );
}
- AnsiConsole.MarkupLineInterpolated($"[green]CLI found:[/] devtunnel [white]{Markup.Escape(probe.Version?.ToString() ?? "unknown")}[/]");
+ AnsiConsole.MarkupLineInterpolated(
+ $"[green]CLI found:[/] devtunnel [white]{Markup.Escape(probe.Version?.ToString() ?? "unknown")}[/]"
+ );
AnsiConsole.MarkupLineInterpolated(
- $"[grey]Ensuring login with[/] [white]{Markup.Escape(configuration.LoginProvider.ToString())}[/][grey]...[/]");
+ $"[grey]Ensuring login with[/] [white]{Markup.Escape(configuration.LoginProvider.ToString())}[/][grey]...[/]"
+ );
- _ = await client.EnsureLoggedInAsync(configuration.LoginProvider, cancellationToken).ConfigureAwait(false);
+ _ = await client
+ .EnsureLoggedInAsync(configuration.LoginProvider, cancellationToken)
+ .ConfigureAwait(false);
AnsiConsole.MarkupLine("[green]Login confirmed.[/]");
- _ = await client.CreateOrUpdateTunnelAsync(
- configuration.TunnelId,
- new DevTunnelOptions
- {
- Description = "KoFi.Client.Sample tunnel",
- AllowAnonymous = true,
- },
- cancellationToken).ConfigureAwait(false);
+ _ = await client
+ .CreateOrUpdateTunnelAsync(
+ configuration.TunnelId,
+ new DevTunnelOptions
+ {
+ Description = "KoFi.Client.Sample tunnel",
+ AllowAnonymous = true,
+ },
+ cancellationToken
+ )
+ .ConfigureAwait(false);
- _ = await client.CreateOrReplacePortAsync(
- configuration.TunnelId,
- configuration.LocalPort,
- new DevTunnelPortOptions
- {
- Protocol = "http",
- },
- cancellationToken).ConfigureAwait(false);
+ _ = await client
+ .CreateOrReplacePortAsync(
+ configuration.TunnelId,
+ configuration.LocalPort,
+ new DevTunnelPortOptions { Protocol = "http" },
+ cancellationToken
+ )
+ .ConfigureAwait(false);
- IDevTunnelHostSession session = await client.StartHostSessionAsync(
- new DevTunnelHostStartOptions
- {
- TunnelId = configuration.TunnelId,
- },
- cancellationToken).ConfigureAwait(false);
+ IDevTunnelHostSession session = await client
+ .StartHostSessionAsync(
+ new DevTunnelHostStartOptions { TunnelId = configuration.TunnelId },
+ cancellationToken
+ )
+ .ConfigureAwait(false);
await session.WaitForReadyAsync(cancellationToken).ConfigureAwait(false);
- Uri publicBaseUrl = session.PublicUrl
- ?? throw new InvalidOperationException("The Dev Tunnel host session became ready without a public URL.");
+ Uri publicBaseUrl =
+ session.PublicUrl
+ ?? throw new InvalidOperationException(
+ "The Dev Tunnel host session became ready without a public URL."
+ );
- AnsiConsole.MarkupLineInterpolated($"[green]Tunnel ready:[/] [link]{Markup.Escape(publicBaseUrl.ToString())}[/]");
+ AnsiConsole.MarkupLineInterpolated(
+ $"[green]Tunnel ready:[/] [link]{Markup.Escape(publicBaseUrl.ToString())}[/]"
+ );
return new DevTunnelsRuntime(session, publicBaseUrl);
}
@@ -278,17 +320,25 @@ private static void RenderStartupSummary(SampleConfiguration configuration, stri
_ = table.AddRow("Webhook path", $"[white]{Markup.Escape(configuration.WebhookPath)}[/]");
_ = table.AddRow("Local webhook URL", $"[white]{Markup.Escape(localWebhookUrl)}[/]");
_ = table.AddRow("Verification token", "[grey](hidden)[/]");
- _ = table.AddRow("Dev Tunnels enabled", configuration.UseDevTunnels ? "[green]yes[/]" : "[yellow]no[/]");
+ _ = table.AddRow(
+ "Dev Tunnels enabled",
+ configuration.UseDevTunnels ? "[green]yes[/]" : "[yellow]no[/]"
+ );
- AnsiConsole.Write(new Panel(table)
- .Header("[bold]Local runtime[/]")
- .Border(BoxBorder.Rounded)
- .BorderColor(Color.HotPink));
+ AnsiConsole.Write(
+ new Panel(table)
+ .Header("[bold]Local runtime[/]")
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.HotPink)
+ );
}
private static void RenderTunnelSummary(SampleConfiguration configuration, Uri publicBaseUrl)
{
- string publicWebhookUrl = CombineUrl(publicBaseUrl.ToString().TrimEnd('/'), configuration.WebhookPath);
+ string publicWebhookUrl = CombineUrl(
+ publicBaseUrl.ToString().TrimEnd('/'),
+ configuration.WebhookPath
+ );
Table table = new Table()
.RoundedBorder()
@@ -300,16 +350,19 @@ private static void RenderTunnelSummary(SampleConfiguration configuration, Uri p
_ = table.AddRow("Public base URL", $"[white]{Markup.Escape(publicBaseUrl.ToString())}[/]");
_ = table.AddRow("Public webhook URL", $"[white]{Markup.Escape(publicWebhookUrl)}[/]");
- AnsiConsole.Write(new Panel(table)
- .Header("[bold]Public tunnel[/]")
- .Border(BoxBorder.Rounded)
- .BorderColor(Color.Green));
+ AnsiConsole.Write(
+ new Panel(table)
+ .Header("[bold]Public tunnel[/]")
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.Green)
+ );
}
private static void RenderUsageInstructions(
SampleConfiguration configuration,
string localBaseUrl,
- Uri? publicBaseUrl)
+ Uri? publicBaseUrl
+ )
{
string localWebhookUrl = CombineUrl(localBaseUrl, configuration.WebhookPath);
string? publicWebhookUrl = publicBaseUrl is null
@@ -326,15 +379,20 @@ private static void RenderUsageInstructions(
new Text(string.Empty),
new Markup($"[grey]Local webhook URL:[/] [white]{Markup.Escape(localWebhookUrl)}[/]"),
publicWebhookUrl is not null
- ? new Markup($"[grey]Public webhook URL:[/] [white]{Markup.Escape(publicWebhookUrl)}[/]")
+ ? new Markup(
+ $"[grey]Public webhook URL:[/] [white]{Markup.Escape(publicWebhookUrl)}[/]"
+ )
: new Markup("[grey]Public webhook URL:[/] [yellow](Dev Tunnels disabled)[/]"),
new Text(string.Empty),
- new Markup("[grey]Commands are available below while the sample is running.[/]"));
+ new Markup("[grey]Commands are available below while the sample is running.[/]")
+ );
- AnsiConsole.Write(new Panel(rows)
- .Header("[bold]How to use the sample[/]")
- .Border(BoxBorder.Rounded)
- .BorderColor(Color.Blue));
+ AnsiConsole.Write(
+ new Panel(rows)
+ .Header("[bold]How to use the sample[/]")
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.Blue)
+ );
}
private static async Task RunCommandLoopAsync(
@@ -342,7 +400,8 @@ private static async Task RunCommandLoopAsync(
ConcurrentQueue receivedEvents,
DevTunnelsRuntime? devTunnelsRuntime,
object consoleLock,
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken
+ )
{
while (!cancellationToken.IsCancellationRequested)
{
@@ -355,7 +414,9 @@ private static async Task RunCommandLoopAsync(
"Show webhook URLs",
"Show recent events",
"Show sample payload hint",
- "Exit"));
+ "Exit"
+ )
+ );
switch (command)
{
@@ -363,7 +424,10 @@ private static async Task RunCommandLoopAsync(
lock (consoleLock)
{
string localBaseUrl = $"http://127.0.0.1:{configuration.LocalPort}";
- string localWebhookUrl = CombineUrl(localBaseUrl, configuration.WebhookPath);
+ string localWebhookUrl = CombineUrl(
+ localBaseUrl,
+ configuration.WebhookPath
+ );
Table table = new Table()
.RoundedBorder()
@@ -376,9 +440,13 @@ private static async Task RunCommandLoopAsync(
{
string publicWebhookUrl = CombineUrl(
devTunnelsRuntime.PublicBaseUrl.ToString().TrimEnd('/'),
- configuration.WebhookPath);
+ configuration.WebhookPath
+ );
- _ = table.AddRow("Public", $"[white]{Markup.Escape(publicWebhookUrl)}[/]");
+ _ = table.AddRow(
+ "Public",
+ $"[white]{Markup.Escape(publicWebhookUrl)}[/]"
+ );
}
AnsiConsole.Write(table);
@@ -412,7 +480,8 @@ private static async Task RunCommandLoopAsync(
Markup.Escape(evt.FromName ?? "(anonymous)"),
evt.Amount.ToString("0.00"),
Markup.Escape(evt.Currency),
- Markup.Escape(evt.Timestamp.ToString("u")));
+ Markup.Escape(evt.Timestamp.ToString("u"))
+ );
}
AnsiConsole.Write(table);
@@ -423,10 +492,12 @@ private static async Task RunCommandLoopAsync(
case "Show sample payload hint":
lock (consoleLock)
{
- AnsiConsole.Write(new Panel(GetSamplePayloadHint(configuration.VerificationToken))
- .Header("[bold]Example Ko-fi form body[/]")
- .Border(BoxBorder.Rounded)
- .BorderColor(Color.Yellow));
+ AnsiConsole.Write(
+ new Panel(GetSamplePayloadHint(configuration.VerificationToken))
+ .Header("[bold]Example Ko-fi form body[/]")
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.Yellow)
+ );
}
break;
@@ -457,10 +528,12 @@ private static void RenderReceivedEvent(KoFiWebhookEvent evt)
_ = grid.AddRow("[bold]Message[/]", Markup.Escape(evt.Message));
}
- AnsiConsole.Write(new Panel(grid)
- .Header("[bold green]Webhook event received[/]")
- .Border(BoxBorder.Rounded)
- .BorderColor(Color.Green));
+ AnsiConsole.Write(
+ new Panel(grid)
+ .Header("[bold green]Webhook event received[/]")
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.Green)
+ );
}
private static string GetEventDisplayName(KoFiWebhookEvent evt)
@@ -487,14 +560,15 @@ private static string CombineUrl(string baseUrl, string path)
private static string GetSamplePayloadHint(string verificationToken)
{
return """
- Ko-fi posts application/x-www-form-urlencoded with a single field named data.
+ Ko-fi posts application/x-www-form-urlencoded with a single field named data.
- Example form field value:
- data={"verification_token":"REPLACE_TOKEN","message_id":"sample-message-id","timestamp":"2026-03-17T12:00:00Z","type":"Donation","is_public":true,"from_name":"Sample Supporter","message":"Hello from the sample","amount":"5.00","url":"https://ko-fi.com","email":"supporter@example.com","currency":"EUR","is_subscription_payment":false,"is_first_subscription_payment":false,"kofi_transaction_id":"txn_123","tier_name":null,"discord_username":null,"discord_user_id":null}
+ Example form field value:
+ data={"verification_token":"REPLACE_TOKEN","message_id":"sample-message-id","timestamp":"2026-03-17T12:00:00Z","type":"Donation","is_public":true,"from_name":"Sample Supporter","message":"Hello from the sample","amount":"5.00","url":"https://ko-fi.com","email":"supporter@example.com","currency":"EUR","is_subscription_payment":false,"is_first_subscription_payment":false,"kofi_transaction_id":"txn_123","tier_name":null,"discord_username":null,"discord_user_id":null}
- Replace REPLACE_TOKEN with the configured verification token:
- """
- + Environment.NewLine + verificationToken;
+ Replace REPLACE_TOKEN with the configured verification token:
+ """
+ + Environment.NewLine
+ + verificationToken;
}
private sealed record SampleConfiguration(
@@ -503,18 +577,16 @@ private sealed record SampleConfiguration(
string VerificationToken,
bool UseDevTunnels,
string TunnelId,
- LoginProvider LoginProvider);
+ LoginProvider LoginProvider
+ );
///
/// Initializes a new instance of the class.
///
/// The live host session returned by the client.
/// The public base URL exposed by the tunnel.
- private sealed class DevTunnelsRuntime(
- dynamic session,
- Uri publicBaseUrl)
+ private sealed class DevTunnelsRuntime(dynamic session, Uri publicBaseUrl)
{
-
///
/// Gets the active host session.
///
@@ -543,4 +615,4 @@ public async Task StopAsync(CancellationToken cancellationToken)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client.AspNetCore/HttpContextWebhookRequestMapper.cs b/src/KoFi.Client.AspNetCore/HttpContextWebhookRequestMapper.cs
index a5154a8..33653b0 100644
--- a/src/KoFi.Client.AspNetCore/HttpContextWebhookRequestMapper.cs
+++ b/src/KoFi.Client.AspNetCore/HttpContextWebhookRequestMapper.cs
@@ -22,7 +22,8 @@ public static class HttpContextWebhookRequestMapper
///
public static async Task FromHttpContextAsync(
HttpContext context,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(context);
@@ -55,10 +56,12 @@ public static async Task FromHttpContextAsync(
{
Method = context.Request.Method,
Path = context.Request.Path.HasValue ? context.Request.Path.Value : "/",
- QueryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : null,
+ QueryString = context.Request.QueryString.HasValue
+ ? context.Request.QueryString.Value
+ : null,
ContentType = context.Request.ContentType,
Headers = headers,
Body = body,
};
}
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client.AspNetCore/KoFi.Client.AspNetCore.csproj b/src/KoFi.Client.AspNetCore/KoFi.Client.AspNetCore.csproj
index a8c9449..ae737b0 100644
--- a/src/KoFi.Client.AspNetCore/KoFi.Client.AspNetCore.csproj
+++ b/src/KoFi.Client.AspNetCore/KoFi.Client.AspNetCore.csproj
@@ -1,37 +1,36 @@
-
- net9.0;net10.0;net11.0
+
+ net9.0;net10.0;net11.0
true
- enable
- enable
- preview
- true
- true
- true
- KoFi.Client.AspNetCore
- v
- true
- KoFi.Client.AspNetCore
- Agash
- Agash
- ASP.NET Core adapter package for KoFi.Client.
- MIT
- https://github.com/Agash/KoFi.Client
- git
- false
+ enable
+ enable
+ preview
+ true
+ true
+ true
+ KoFi.Client.AspNetCore
+ v
+ true
+ KoFi.Client.AspNetCore
+ Agash
+ Agash
+ ASP.NET Core adapter package for KoFi.Client.
+ MIT
+ https://github.com/Agash/KoFi.Client
+ git
+ false
+
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
diff --git a/src/KoFi.Client.AspNetCore/KoFiEndpointRouteBuilderExtensions.cs b/src/KoFi.Client.AspNetCore/KoFiEndpointRouteBuilderExtensions.cs
index 7d42236..66c0994 100644
--- a/src/KoFi.Client.AspNetCore/KoFiEndpointRouteBuilderExtensions.cs
+++ b/src/KoFi.Client.AspNetCore/KoFiEndpointRouteBuilderExtensions.cs
@@ -30,7 +30,8 @@ public static class KoFiEndpointRouteBuilderExtensions
public static IEndpointConventionBuilder MapKoFiWebhook(
this IEndpointRouteBuilder endpoints,
string pattern,
- Action configure)
+ Action configure
+ )
{
ArgumentNullException.ThrowIfNull(endpoints);
ArgumentException.ThrowIfNullOrEmpty(pattern);
@@ -38,42 +39,50 @@ public static IEndpointConventionBuilder MapKoFiWebhook(
KoFiWebhookEndpointOptions options = new()
{
- ResolveWebhookOptionsAsync = static (_, _) => Task.FromResult(new KoFiWebhookOptions
- {
- VerificationToken = string.Empty,
- }),
+ ResolveWebhookOptionsAsync = static (_, _) =>
+ Task.FromResult(new KoFiWebhookOptions { VerificationToken = string.Empty }),
};
configure(options);
- IEndpointConventionBuilder builder = endpoints.MapPost(pattern, async context =>
- {
- IKoFiWebhookHandler handler = context.RequestServices.GetRequiredService();
-
- KoFiWebhookOptions webhookOptions =
- await options.ResolveWebhookOptionsAsync(context, context.RequestAborted).ConfigureAwait(false);
+ IEndpointConventionBuilder builder = endpoints.MapPost(
+ pattern,
+ async context =>
+ {
+ IKoFiWebhookHandler handler =
+ context.RequestServices.GetRequiredService();
- WebhookRequest request =
- await HttpContextWebhookRequestMapper.FromHttpContextAsync(context, context.RequestAborted)
+ KoFiWebhookOptions webhookOptions = await options
+ .ResolveWebhookOptionsAsync(context, context.RequestAborted)
.ConfigureAwait(false);
- WebhookHandleResult result =
- await handler.HandleAsync(request, webhookOptions, context.RequestAborted)
+ WebhookRequest request = await HttpContextWebhookRequestMapper
+ .FromHttpContextAsync(context, context.RequestAborted)
.ConfigureAwait(false);
- if (result.Event is KoFiWebhookEvent evt && options.OnEventAsync is not null)
- {
- await options.OnEventAsync(evt, context, context.RequestAborted).ConfigureAwait(false);
- }
+ WebhookHandleResult result = await handler
+ .HandleAsync(request, webhookOptions, context.RequestAborted)
+ .ConfigureAwait(false);
- if (options.OnResultAsync is not null)
- {
- await options.OnResultAsync(result, context, context.RequestAborted).ConfigureAwait(false);
+ if (result.Event is KoFiWebhookEvent evt && options.OnEventAsync is not null)
+ {
+ await options
+ .OnEventAsync(evt, context, context.RequestAborted)
+ .ConfigureAwait(false);
+ }
+
+ if (options.OnResultAsync is not null)
+ {
+ await options
+ .OnResultAsync(result, context, context.RequestAborted)
+ .ConfigureAwait(false);
+ }
+
+ await WebhookResponseHttpContextWriter
+ .WriteAsync(context, result.Response, context.RequestAborted)
+ .ConfigureAwait(false);
}
-
- await WebhookResponseHttpContextWriter.WriteAsync(context, result.Response, context.RequestAborted)
- .ConfigureAwait(false);
- });
+ );
return builder;
}
@@ -100,7 +109,13 @@ public static IEndpointConventionBuilder MapKoFiWebhook(
string pattern,
Func> resolveWebhookOptionsAsync,
Func? onEventAsync = null,
- Func, HttpContext, CancellationToken, Task>? onResultAsync = null)
+ Func<
+ WebhookHandleResult,
+ HttpContext,
+ CancellationToken,
+ Task
+ >? onResultAsync = null
+ )
{
ArgumentNullException.ThrowIfNull(resolveWebhookOptionsAsync);
@@ -111,7 +126,8 @@ public static IEndpointConventionBuilder MapKoFiWebhook(
options.ResolveWebhookOptionsAsync = resolveWebhookOptionsAsync;
options.OnEventAsync = onEventAsync;
options.OnResultAsync = onResultAsync;
- });
+ }
+ );
}
///
@@ -136,43 +152,53 @@ public static IEndpointConventionBuilder MapKoFiWebhook(
string pattern,
string verificationToken,
Func? onEventAsync = null,
- Func, HttpContext, CancellationToken, Task>? onResultAsync = null)
+ Func<
+ WebhookHandleResult,
+ HttpContext,
+ CancellationToken,
+ Task
+ >? onResultAsync = null
+ )
{
ArgumentNullException.ThrowIfNull(endpoints);
ArgumentException.ThrowIfNullOrEmpty(pattern);
ArgumentException.ThrowIfNullOrEmpty(verificationToken);
- IEndpointConventionBuilder builder = endpoints.MapPost(pattern, async context =>
- {
- IKoFiWebhookHandler handler = context.RequestServices.GetRequiredService();
+ IEndpointConventionBuilder builder = endpoints.MapPost(
+ pattern,
+ async context =>
+ {
+ IKoFiWebhookHandler handler =
+ context.RequestServices.GetRequiredService();
- WebhookRequest request =
- await HttpContextWebhookRequestMapper.FromHttpContextAsync(context, context.RequestAborted)
+ WebhookRequest request = await HttpContextWebhookRequestMapper
+ .FromHttpContextAsync(context, context.RequestAborted)
.ConfigureAwait(false);
- WebhookHandleResult result =
- await handler.HandleAsync(
+ WebhookHandleResult result = await handler
+ .HandleAsync(
request,
- new KoFiWebhookOptions
- {
- VerificationToken = verificationToken,
- },
- context.RequestAborted)
+ new KoFiWebhookOptions { VerificationToken = verificationToken },
+ context.RequestAborted
+ )
.ConfigureAwait(false);
- if (result.Event is KoFiWebhookEvent evt && onEventAsync is not null)
- {
- await onEventAsync(evt, context, context.RequestAborted).ConfigureAwait(false);
- }
+ if (result.Event is KoFiWebhookEvent evt && onEventAsync is not null)
+ {
+ await onEventAsync(evt, context, context.RequestAborted).ConfigureAwait(false);
+ }
- if (onResultAsync is not null)
- {
- await onResultAsync(result, context, context.RequestAborted).ConfigureAwait(false);
- }
+ if (onResultAsync is not null)
+ {
+ await onResultAsync(result, context, context.RequestAborted)
+ .ConfigureAwait(false);
+ }
- await WebhookResponseHttpContextWriter.WriteAsync(context, result.Response, context.RequestAborted)
- .ConfigureAwait(false);
- });
+ await WebhookResponseHttpContextWriter
+ .WriteAsync(context, result.Response, context.RequestAborted)
+ .ConfigureAwait(false);
+ }
+ );
_ = builder.WithMetadata(new KoFiFixedVerificationTokenMetadata(verificationToken));
@@ -180,4 +206,4 @@ await WebhookResponseHttpContextWriter.WriteAsync(context, result.Response, cont
}
private sealed record KoFiFixedVerificationTokenMetadata(string VerificationToken);
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client.AspNetCore/KoFiWebhookEndpointOptions.cs b/src/KoFi.Client.AspNetCore/KoFiWebhookEndpointOptions.cs
index 42a8533..1da6396 100644
--- a/src/KoFi.Client.AspNetCore/KoFiWebhookEndpointOptions.cs
+++ b/src/KoFi.Client.AspNetCore/KoFiWebhookEndpointOptions.cs
@@ -18,7 +18,11 @@ public sealed class KoFiWebhookEndpointOptions
/// This is intentionally request-based so host applications can resolve tokens and other
/// configuration from instance-scoped settings, route values, or custom stores.
///
- public required Func> ResolveWebhookOptionsAsync { get; set; }
+ public required Func<
+ HttpContext,
+ CancellationToken,
+ Task
+ > ResolveWebhookOptionsAsync { get; set; }
///
/// Gets or sets an optional callback invoked after a normalized Ko-fi event has been
@@ -34,5 +38,10 @@ public sealed class KoFiWebhookEndpointOptions
/// Gets or sets an optional callback invoked after the Ko-fi handler completes, regardless
/// of whether it produced a normalized event.
///
- public Func, HttpContext, CancellationToken, Task>? OnResultAsync { get; set; }
-}
\ No newline at end of file
+ public Func<
+ WebhookHandleResult,
+ HttpContext,
+ CancellationToken,
+ Task
+ >? OnResultAsync { get; set; }
+}
diff --git a/src/KoFi.Client.AspNetCore/WebhookResponseHttpContextWriter.cs b/src/KoFi.Client.AspNetCore/WebhookResponseHttpContextWriter.cs
index 34d3dc9..acc64d2 100644
--- a/src/KoFi.Client.AspNetCore/WebhookResponseHttpContextWriter.cs
+++ b/src/KoFi.Client.AspNetCore/WebhookResponseHttpContextWriter.cs
@@ -21,7 +21,8 @@ public static class WebhookResponseHttpContextWriter
public static async Task WriteAsync(
HttpContext context,
WebhookResponse response,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(response);
@@ -35,8 +36,10 @@ public static async Task WriteAsync(
foreach ((string key, string[] values) in response.Headers)
{
- if (string.Equals(key, "Content-Type", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(key, "Content-Length", StringComparison.OrdinalIgnoreCase))
+ if (
+ string.Equals(key, "Content-Type", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(key, "Content-Length", StringComparison.OrdinalIgnoreCase)
+ )
{
continue;
}
@@ -47,11 +50,13 @@ public static async Task WriteAsync(
if (response.Body is { Length: > 0 })
{
context.Response.ContentLength = response.Body.Length;
- await context.Response.Body.WriteAsync(response.Body, cancellationToken).ConfigureAwait(false);
+ await context
+ .Response.Body.WriteAsync(response.Body, cancellationToken)
+ .ConfigureAwait(false);
}
else
{
context.Response.ContentLength = 0;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client.DependencyInjection/KoFi.Client.DependencyInjection.csproj b/src/KoFi.Client.DependencyInjection/KoFi.Client.DependencyInjection.csproj
index e5135e4..7f109ac 100644
--- a/src/KoFi.Client.DependencyInjection/KoFi.Client.DependencyInjection.csproj
+++ b/src/KoFi.Client.DependencyInjection/KoFi.Client.DependencyInjection.csproj
@@ -18,7 +18,10 @@
-
+
diff --git a/src/KoFi.Client.DependencyInjection/KoFiClientServiceCollectionExtensions.cs b/src/KoFi.Client.DependencyInjection/KoFiClientServiceCollectionExtensions.cs
index b8ece34..0981ccb 100644
--- a/src/KoFi.Client.DependencyInjection/KoFiClientServiceCollectionExtensions.cs
+++ b/src/KoFi.Client.DependencyInjection/KoFiClientServiceCollectionExtensions.cs
@@ -22,4 +22,4 @@ public static IServiceCollection AddKoFiClient(this IServiceCollection services)
services.TryAddSingleton();
return services;
}
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Abstractions/IKoFiWebhookHandler.cs b/src/KoFi.Client/Abstractions/IKoFiWebhookHandler.cs
index 52d1ae7..2ea7621 100644
--- a/src/KoFi.Client/Abstractions/IKoFiWebhookHandler.cs
+++ b/src/KoFi.Client/Abstractions/IKoFiWebhookHandler.cs
@@ -31,5 +31,6 @@ public interface IKoFiWebhookHandler
Task> HandleAsync(
WebhookRequest request,
KoFiWebhookOptions options,
- CancellationToken cancellationToken = default);
-}
\ No newline at end of file
+ CancellationToken cancellationToken = default
+ );
+}
diff --git a/src/KoFi.Client/Events/KoFiCommerceEvent.cs b/src/KoFi.Client/Events/KoFiCommerceEvent.cs
index 50354b8..227e677 100644
--- a/src/KoFi.Client/Events/KoFiCommerceEvent.cs
+++ b/src/KoFi.Client/Events/KoFiCommerceEvent.cs
@@ -20,4 +20,4 @@ public abstract record KoFiCommerceEvent : KoFiWebhookEvent
/// Gets the shipping address, if present.
///
public KoFiShipping? Shipping { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Events/KoFiCommissionEvent.cs b/src/KoFi.Client/Events/KoFiCommissionEvent.cs
index a5e343b..4fb1f91 100644
--- a/src/KoFi.Client/Events/KoFiCommissionEvent.cs
+++ b/src/KoFi.Client/Events/KoFiCommissionEvent.cs
@@ -3,4 +3,4 @@
///
/// Represents a Ko-fi commission event.
///
-public sealed record KoFiCommissionEvent : KoFiCommerceEvent;
\ No newline at end of file
+public sealed record KoFiCommissionEvent : KoFiCommerceEvent;
diff --git a/src/KoFi.Client/Events/KoFiDonationEvent.cs b/src/KoFi.Client/Events/KoFiDonationEvent.cs
index 79ca964..209202a 100644
--- a/src/KoFi.Client/Events/KoFiDonationEvent.cs
+++ b/src/KoFi.Client/Events/KoFiDonationEvent.cs
@@ -3,4 +3,4 @@
///
/// Represents a Ko-fi donation event.
///
-public sealed record KoFiDonationEvent : KoFiSupporterEvent;
\ No newline at end of file
+public sealed record KoFiDonationEvent : KoFiSupporterEvent;
diff --git a/src/KoFi.Client/Events/KoFiReferralEvent.cs b/src/KoFi.Client/Events/KoFiReferralEvent.cs
index eed0203..c26f201 100644
--- a/src/KoFi.Client/Events/KoFiReferralEvent.cs
+++ b/src/KoFi.Client/Events/KoFiReferralEvent.cs
@@ -3,4 +3,4 @@
///
/// Represents a Ko-fi referral event.
///
-public sealed record KoFiReferralEvent : KoFiSupporterEvent;
\ No newline at end of file
+public sealed record KoFiReferralEvent : KoFiSupporterEvent;
diff --git a/src/KoFi.Client/Events/KoFiShopOrderEvent.cs b/src/KoFi.Client/Events/KoFiShopOrderEvent.cs
index 9e9ed2d..98e62fe 100644
--- a/src/KoFi.Client/Events/KoFiShopOrderEvent.cs
+++ b/src/KoFi.Client/Events/KoFiShopOrderEvent.cs
@@ -3,4 +3,4 @@
///
/// Represents a Ko-fi shop order event.
///
-public sealed record KoFiShopOrderEvent : KoFiCommerceEvent;
\ No newline at end of file
+public sealed record KoFiShopOrderEvent : KoFiCommerceEvent;
diff --git a/src/KoFi.Client/Events/KoFiSubscriptionRenewedEvent.cs b/src/KoFi.Client/Events/KoFiSubscriptionRenewedEvent.cs
index b057612..83b8f6a 100644
--- a/src/KoFi.Client/Events/KoFiSubscriptionRenewedEvent.cs
+++ b/src/KoFi.Client/Events/KoFiSubscriptionRenewedEvent.cs
@@ -3,4 +3,4 @@
///
/// Represents a recurring renewal payment for an existing Ko-fi subscription.
///
-public sealed record KoFiSubscriptionRenewedEvent : KoFiSupporterEvent;
\ No newline at end of file
+public sealed record KoFiSubscriptionRenewedEvent : KoFiSupporterEvent;
diff --git a/src/KoFi.Client/Events/KoFiSubscriptionStartedEvent.cs b/src/KoFi.Client/Events/KoFiSubscriptionStartedEvent.cs
index 061fb01..48ec7f3 100644
--- a/src/KoFi.Client/Events/KoFiSubscriptionStartedEvent.cs
+++ b/src/KoFi.Client/Events/KoFiSubscriptionStartedEvent.cs
@@ -3,4 +3,4 @@
///
/// Represents the first successful payment for a Ko-fi subscription.
///
-public sealed record KoFiSubscriptionStartedEvent : KoFiSupporterEvent;
\ No newline at end of file
+public sealed record KoFiSubscriptionStartedEvent : KoFiSupporterEvent;
diff --git a/src/KoFi.Client/Events/KoFiSupporterEvent.cs b/src/KoFi.Client/Events/KoFiSupporterEvent.cs
index 296f05e..7eef700 100644
--- a/src/KoFi.Client/Events/KoFiSupporterEvent.cs
+++ b/src/KoFi.Client/Events/KoFiSupporterEvent.cs
@@ -13,4 +13,4 @@ public abstract record KoFiSupporterEvent : KoFiWebhookEvent
/// Gets the Ko-fi membership tier name, if present.
///
public string? TierName { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Events/KoFiUnknownWebhookEvent.cs b/src/KoFi.Client/Events/KoFiUnknownWebhookEvent.cs
index 2dff1fc..0e240e1 100644
--- a/src/KoFi.Client/Events/KoFiUnknownWebhookEvent.cs
+++ b/src/KoFi.Client/Events/KoFiUnknownWebhookEvent.cs
@@ -15,4 +15,4 @@ public sealed record KoFiUnknownWebhookEvent : KoFiWebhookEvent
/// Gets the raw payload as received from Ko-fi.
///
public required JsonElement RawPayload { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Events/KoFiWebhookEvent.cs b/src/KoFi.Client/Events/KoFiWebhookEvent.cs
index 103e881..25cde64 100644
--- a/src/KoFi.Client/Events/KoFiWebhookEvent.cs
+++ b/src/KoFi.Client/Events/KoFiWebhookEvent.cs
@@ -68,4 +68,4 @@ public abstract record KoFiWebhookEvent
/// Gets the Discord user identifier associated with the event, if present.
///
public string? DiscordUserId { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Internal/ConstantTimeStringComparer.cs b/src/KoFi.Client/Internal/ConstantTimeStringComparer.cs
index 7c5472f..5fb061d 100644
--- a/src/KoFi.Client/Internal/ConstantTimeStringComparer.cs
+++ b/src/KoFi.Client/Internal/ConstantTimeStringComparer.cs
@@ -27,6 +27,7 @@ public static bool Equals(string? left, string? right)
byte[] leftBytes = Encoding.UTF8.GetBytes(left);
byte[] rightBytes = Encoding.UTF8.GetBytes(right);
- return leftBytes.Length == rightBytes.Length && CryptographicOperations.FixedTimeEquals(leftBytes, rightBytes);
+ return leftBytes.Length == rightBytes.Length
+ && CryptographicOperations.FixedTimeEquals(leftBytes, rightBytes);
}
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Json/KoFiJsonSerializerContext.cs b/src/KoFi.Client/Json/KoFiJsonSerializerContext.cs
index 99966da..b7b4720 100644
--- a/src/KoFi.Client/Json/KoFiJsonSerializerContext.cs
+++ b/src/KoFi.Client/Json/KoFiJsonSerializerContext.cs
@@ -1,5 +1,5 @@
-using KoFi.Client.Models;
using System.Text.Json.Serialization;
+using KoFi.Client.Models;
namespace KoFi.Client.Json;
@@ -13,8 +13,9 @@ namespace KoFi.Client.Json;
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
- WriteIndented = false)]
+ WriteIndented = false
+)]
[JsonSerializable(typeof(KoFiPayload))]
[JsonSerializable(typeof(KoFiShopItem))]
[JsonSerializable(typeof(KoFiShipping))]
-internal sealed partial class KoFiJsonSerializerContext : JsonSerializerContext;
\ No newline at end of file
+internal sealed partial class KoFiJsonSerializerContext : JsonSerializerContext;
diff --git a/src/KoFi.Client/KoFi.Client.csproj b/src/KoFi.Client/KoFi.Client.csproj
index 63f944d..b84cb5c 100644
--- a/src/KoFi.Client/KoFi.Client.csproj
+++ b/src/KoFi.Client/KoFi.Client.csproj
@@ -1,29 +1,29 @@
-
- net9.0;net10.0;net11.0
+
+ net9.0;net10.0;net11.0
true
- enable
- enable
- preview
- true
- true
- true
- KoFi.Client
- KoFi.Client
- Agash
- Agash
- Typed, transport-neutral .NET client library for Ko-fi webhook processing.
- MIT
- https://github.com/Agash/KoFi.Client
- git
- false
- v
- true
-
+ enable
+ enable
+ preview
+ true
+ true
+ true
+ KoFi.Client
+ KoFi.Client
+ Agash
+ Agash
+ Typed, transport-neutral .NET client library for Ko-fi webhook processing.
+ MIT
+ https://github.com/Agash/KoFi.Client
+ git
+ false
+ v
+ true
+
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/src/KoFi.Client/Models/KoFiPayload.cs b/src/KoFi.Client/Models/KoFiPayload.cs
index a8f6857..19ec8b9 100644
--- a/src/KoFi.Client/Models/KoFiPayload.cs
+++ b/src/KoFi.Client/Models/KoFiPayload.cs
@@ -132,4 +132,4 @@ public sealed record KoFiPayload
///
[JsonPropertyName("discord_user_id")]
public string? DiscordUserId { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Models/KoFiShipping.cs b/src/KoFi.Client/Models/KoFiShipping.cs
index a28b756..40595f0 100644
--- a/src/KoFi.Client/Models/KoFiShipping.cs
+++ b/src/KoFi.Client/Models/KoFiShipping.cs
@@ -54,4 +54,4 @@ public sealed record KoFiShipping
///
[JsonPropertyName("telephone")]
public string? Telephone { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Models/KoFiShopItem.cs b/src/KoFi.Client/Models/KoFiShopItem.cs
index 8690c78..336c8ea 100644
--- a/src/KoFi.Client/Models/KoFiShopItem.cs
+++ b/src/KoFi.Client/Models/KoFiShopItem.cs
@@ -24,4 +24,4 @@ public sealed record KoFiShopItem
///
[JsonPropertyName("quantity")]
public required int Quantity { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Options/KoFiWebhookOptions.cs b/src/KoFi.Client/Options/KoFiWebhookOptions.cs
index 6705bff..04b6a0d 100644
--- a/src/KoFi.Client/Options/KoFiWebhookOptions.cs
+++ b/src/KoFi.Client/Options/KoFiWebhookOptions.cs
@@ -14,4 +14,4 @@ public sealed class KoFiWebhookOptions
/// the webhook to be accepted.
///
public required string VerificationToken { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/KoFi.Client/Webhooks/KoFiWebhookHandler.cs b/src/KoFi.Client/Webhooks/KoFiWebhookHandler.cs
index f8a878c..6844cd5 100644
--- a/src/KoFi.Client/Webhooks/KoFiWebhookHandler.cs
+++ b/src/KoFi.Client/Webhooks/KoFiWebhookHandler.cs
@@ -1,13 +1,13 @@
-using Agash.Webhook.Abstractions;
+using System.Globalization;
+using System.Text;
+using System.Text.Json;
+using Agash.Webhook.Abstractions;
using KoFi.Client.Abstractions;
using KoFi.Client.Events;
using KoFi.Client.Internal;
using KoFi.Client.Json;
using KoFi.Client.Models;
using KoFi.Client.Options;
-using System.Globalization;
-using System.Text;
-using System.Text.Json;
namespace KoFi.Client.Webhooks;
@@ -28,7 +28,8 @@ public sealed class KoFiWebhookHandler : IKoFiWebhookHandler
public Task> HandleAsync(
WebhookRequest request,
KoFiWebhookOptions options,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(options);
@@ -38,26 +39,31 @@ public Task> HandleAsync(
if (!string.Equals(request.Method, "POST", StringComparison.OrdinalIgnoreCase))
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(405),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = "Unsupported HTTP method. Ko-fi webhooks must use POST.",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(405),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason = "Unsupported HTTP method. Ko-fi webhooks must use POST.",
+ }
+ );
}
if (!request.HasContentType(FormUrlEncodedContentType))
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = "Unsupported content type. Expected application/x-www-form-urlencoded.",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason =
+ "Unsupported content type. Expected application/x-www-form-urlencoded.",
+ }
+ );
}
string formBody;
@@ -67,14 +73,16 @@ public Task> HandleAsync(
}
catch (Exception ex)
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = $"Unable to decode request body as UTF-8: {ex.Message}",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason = $"Unable to decode request body as UTF-8: {ex.Message}",
+ }
+ );
}
Dictionary formValues;
@@ -84,100 +92,136 @@ public Task> HandleAsync(
}
catch (Exception ex)
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = $"Malformed form body: {ex.Message}",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason = $"Malformed form body: {ex.Message}",
+ }
+ );
}
- if (!formValues.TryGetValue(DataFieldName, out string? rawJson) || string.IsNullOrWhiteSpace(rawJson))
+ if (
+ !formValues.TryGetValue(DataFieldName, out string? rawJson)
+ || string.IsNullOrWhiteSpace(rawJson)
+ )
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = "The form body did not contain a non-empty 'data' field.",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason = "The form body did not contain a non-empty 'data' field.",
+ }
+ );
}
KoFiPayload? payload;
JsonDocument? rawJsonDocument;
try
{
- payload = JsonSerializer.Deserialize(rawJson, KoFiJsonSerializerContext.Default.KoFiPayload);
+ payload = JsonSerializer.Deserialize(
+ rawJson,
+ KoFiJsonSerializerContext.Default.KoFiPayload
+ );
rawJsonDocument = JsonDocument.Parse(rawJson);
}
catch (JsonException ex)
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = $"The 'data' field did not contain valid Ko-fi JSON: {ex.Message}",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason =
+ $"The 'data' field did not contain valid Ko-fi JSON: {ex.Message}",
+ }
+ );
}
if (payload is null)
{
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = "The Ko-fi payload could not be deserialized.",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason = "The Ko-fi payload could not be deserialized.",
+ }
+ );
}
- if (!ConstantTimeStringComparer.Equals(payload.VerificationToken, options.VerificationToken))
+ if (
+ !ConstantTimeStringComparer.Equals(payload.VerificationToken, options.VerificationToken)
+ )
{
rawJsonDocument?.Dispose();
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(401),
- IsAuthenticated = false,
- IsKnownEvent = false,
- Event = null,
- FailureReason = "The Ko-fi verification token did not match the expected value.",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(401),
+ IsAuthenticated = false,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason =
+ "The Ko-fi verification token did not match the expected value.",
+ }
+ );
}
- if (!decimal.TryParse(payload.Amount, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal amount))
+ if (
+ !decimal.TryParse(
+ payload.Amount,
+ NumberStyles.Number,
+ CultureInfo.InvariantCulture,
+ out decimal amount
+ )
+ )
{
rawJsonDocument?.Dispose();
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(400),
- IsAuthenticated = true,
- IsKnownEvent = false,
- Event = null,
- FailureReason = $"The Ko-fi amount value '{payload.Amount}' could not be parsed as a decimal.",
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(400),
+ IsAuthenticated = true,
+ IsKnownEvent = false,
+ Event = null,
+ FailureReason =
+ $"The Ko-fi amount value '{payload.Amount}' could not be parsed as a decimal.",
+ }
+ );
}
- KoFiWebhookEvent normalizedEvent = MapEvent(payload, amount, rawJsonDocument?.RootElement.Clone());
+ KoFiWebhookEvent normalizedEvent = MapEvent(
+ payload,
+ amount,
+ rawJsonDocument?.RootElement.Clone()
+ );
bool isKnownEvent = normalizedEvent is not KoFiUnknownWebhookEvent;
rawJsonDocument?.Dispose();
- return Task.FromResult(new WebhookHandleResult
- {
- Response = WebhookResponse.Empty(200),
- IsAuthenticated = true,
- IsKnownEvent = isKnownEvent,
- Event = normalizedEvent,
- FailureReason = null,
- });
+ return Task.FromResult(
+ new WebhookHandleResult
+ {
+ Response = WebhookResponse.Empty(200),
+ IsAuthenticated = true,
+ IsKnownEvent = isKnownEvent,
+ Event = normalizedEvent,
+ FailureReason = null,
+ }
+ );
}
private static Dictionary ParseFormUrlEncoded(string formBody)
@@ -244,15 +288,22 @@ private static string UrlDecodeFormComponent(string value)
return Uri.UnescapeDataString(plusNormalized);
}
- private static KoFiWebhookEvent MapEvent(KoFiPayload payload, decimal amount, JsonElement? rawPayload)
+ private static KoFiWebhookEvent MapEvent(
+ KoFiPayload payload,
+ decimal amount,
+ JsonElement? rawPayload
+ )
{
return payload.Type switch
{
"Donation" => CreateDonationEvent(payload, amount),
- "Subscription" when payload.IsSubscriptionPayment && payload.IsFirstSubscriptionPayment =>
+ "Subscription"
+ when payload.IsSubscriptionPayment && payload.IsFirstSubscriptionPayment =>
CreateSubscriptionStartedEvent(payload, amount),
- "Subscription" when payload.IsSubscriptionPayment =>
- CreateSubscriptionRenewedEvent(payload, amount),
+ "Subscription" when payload.IsSubscriptionPayment => CreateSubscriptionRenewedEvent(
+ payload,
+ amount
+ ),
"Shop Order" => CreateShopOrderEvent(payload, amount),
"Commission" => CreateCommissionEvent(payload, amount),
"Referral" => CreateReferralEvent(payload, amount),
@@ -280,7 +331,10 @@ private static KoFiDonationEvent CreateDonationEvent(KoFiPayload payload, decima
};
}
- private static KoFiSubscriptionStartedEvent CreateSubscriptionStartedEvent(KoFiPayload payload, decimal amount)
+ private static KoFiSubscriptionStartedEvent CreateSubscriptionStartedEvent(
+ KoFiPayload payload,
+ decimal amount
+ )
{
return new()
{
@@ -300,7 +354,10 @@ private static KoFiSubscriptionStartedEvent CreateSubscriptionStartedEvent(KoFiP
};
}
- private static KoFiSubscriptionRenewedEvent CreateSubscriptionRenewedEvent(KoFiPayload payload, decimal amount)
+ private static KoFiSubscriptionRenewedEvent CreateSubscriptionRenewedEvent(
+ KoFiPayload payload,
+ decimal amount
+ )
{
return new()
{
@@ -382,7 +439,11 @@ private static KoFiCommissionEvent CreateCommissionEvent(KoFiPayload payload, de
};
}
- private static KoFiUnknownWebhookEvent CreateUnknownEvent(KoFiPayload payload, decimal amount, JsonElement rawPayload)
+ private static KoFiUnknownWebhookEvent CreateUnknownEvent(
+ KoFiPayload payload,
+ decimal amount,
+ JsonElement rawPayload
+ )
{
return new()
{
@@ -401,4 +462,4 @@ private static KoFiUnknownWebhookEvent CreateUnknownEvent(KoFiPayload payload, d
RawPayload = rawPayload,
};
}
-}
\ No newline at end of file
+}
diff --git a/tests/KoFi.Client.Tests/KoFi.Client.Tests.csproj b/tests/KoFi.Client.Tests/KoFi.Client.Tests.csproj
index e620481..4bfbfc9 100644
--- a/tests/KoFi.Client.Tests/KoFi.Client.Tests.csproj
+++ b/tests/KoFi.Client.Tests/KoFi.Client.Tests.csproj
@@ -1,5 +1,4 @@
-
net10.0;net11.0
enable
@@ -9,17 +8,11 @@
Exe
true
- false
-
-
+ true
-
- $(NoWarn);IDE0005;IDE1006;CA1707
-
-
-
+ $(NoWarn);IDE1006;CA1707;CS1591
@@ -36,5 +29,4 @@
-
diff --git a/tests/KoFi.Client.Tests/Webhooks/KoFiWebhookHandlerTests.cs b/tests/KoFi.Client.Tests/Webhooks/KoFiWebhookHandlerTests.cs
index 1911d6e..f57c1ef 100644
--- a/tests/KoFi.Client.Tests/Webhooks/KoFiWebhookHandlerTests.cs
+++ b/tests/KoFi.Client.Tests/Webhooks/KoFiWebhookHandlerTests.cs
@@ -20,29 +20,31 @@ private static string PayloadJson(
string amount = "5.00",
string token = Token,
bool isSubscriptionPayment = false,
- bool isFirstSubscriptionPayment = false) =>
+ bool isFirstSubscriptionPayment = false
+ ) =>
$$"""
- {
- "verification_token": "{{token}}",
- "message_id": "msg-1",
- "timestamp": "2026-01-01T12:00:00Z",
- "type": "{{type}}",
- "is_public": true,
- "from_name": "Jane",
- "message": "Thanks!",
- "amount": "{{amount}}",
- "currency": "USD",
- "kofi_transaction_id": "txn-1",
- "is_subscription_payment": {{(isSubscriptionPayment ? "true" : "false")}},
- "is_first_subscription_payment": {{(isFirstSubscriptionPayment ? "true" : "false")}}
- }
- """;
+ {
+ "verification_token": "{{token}}",
+ "message_id": "msg-1",
+ "timestamp": "2026-01-01T12:00:00Z",
+ "type": "{{type}}",
+ "is_public": true,
+ "from_name": "Jane",
+ "message": "Thanks!",
+ "amount": "{{amount}}",
+ "currency": "USD",
+ "kofi_transaction_id": "txn-1",
+ "is_subscription_payment": {{(isSubscriptionPayment ? "true" : "false")}},
+ "is_first_subscription_payment": {{(isFirstSubscriptionPayment ? "true" : "false")}}
+ }
+ """;
private static WebhookRequest Request(
string? json = null,
string method = "POST",
string contentType = "application/x-www-form-urlencoded",
- string? rawBody = null)
+ string? rawBody = null
+ )
{
string body = rawBody ?? $"data={Uri.EscapeDataString(json ?? PayloadJson())}";
@@ -58,8 +60,10 @@ private static WebhookRequest Request(
[TestMethod]
public async Task HandleAsync_WhenMethodIsNotPost_Returns405()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(method: "GET"), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(method: "GET"),
+ Options
+ );
Assert.AreEqual(405, result.Response.StatusCode);
Assert.IsFalse(result.IsAuthenticated);
@@ -69,8 +73,10 @@ public async Task HandleAsync_WhenMethodIsNotPost_Returns405()
[TestMethod]
public async Task HandleAsync_WhenContentTypeIsNotFormUrlEncoded_Returns400()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(contentType: "application/json"), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(contentType: "application/json"),
+ Options
+ );
Assert.AreEqual(400, result.Response.StatusCode);
Assert.IsFalse(result.IsAuthenticated);
@@ -79,8 +85,10 @@ public async Task HandleAsync_WhenContentTypeIsNotFormUrlEncoded_Returns400()
[TestMethod]
public async Task HandleAsync_WhenDataFieldIsMissing_Returns400()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(rawBody: "other=value"), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(rawBody: "other=value"),
+ Options
+ );
Assert.AreEqual(400, result.Response.StatusCode);
Assert.IsNotNull(result.FailureReason);
@@ -90,8 +98,10 @@ public async Task HandleAsync_WhenDataFieldIsMissing_Returns400()
[TestMethod]
public async Task HandleAsync_WhenDataIsNotValidJson_Returns400()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(rawBody: "data=not-json"), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(rawBody: "data=not-json"),
+ Options
+ );
Assert.AreEqual(400, result.Response.StatusCode);
Assert.IsFalse(result.IsAuthenticated);
@@ -100,8 +110,10 @@ public async Task HandleAsync_WhenDataIsNotValidJson_Returns400()
[TestMethod]
public async Task HandleAsync_WhenVerificationTokenDoesNotMatch_Returns401()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(PayloadJson(token: "wrong-token")), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(PayloadJson(token: "wrong-token")),
+ Options
+ );
Assert.AreEqual(401, result.Response.StatusCode);
Assert.IsFalse(result.IsAuthenticated);
@@ -111,8 +123,10 @@ public async Task HandleAsync_WhenVerificationTokenDoesNotMatch_Returns401()
[TestMethod]
public async Task HandleAsync_WhenAmountIsNotParseable_Returns400ButIsAuthenticated()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(PayloadJson(amount: "not-a-number")), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(PayloadJson(amount: "not-a-number")),
+ Options
+ );
Assert.AreEqual(400, result.Response.StatusCode);
Assert.IsTrue(result.IsAuthenticated);
@@ -122,8 +136,10 @@ public async Task HandleAsync_WhenAmountIsNotParseable_Returns400ButIsAuthentica
[TestMethod]
public async Task HandleAsync_WhenPayloadIsAValidDonation_ReturnsDonationEvent()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(),
+ Options
+ );
Assert.AreEqual(200, result.Response.StatusCode);
Assert.IsTrue(result.IsAuthenticated);
@@ -141,8 +157,15 @@ public async Task HandleAsync_WhenPayloadIsAValidDonation_ReturnsDonationEvent()
public async Task HandleAsync_WhenFirstSubscriptionPayment_ReturnsSubscriptionStarted()
{
WebhookHandleResult result = await _handler.HandleAsync(
- Request(PayloadJson("Subscription", isSubscriptionPayment: true, isFirstSubscriptionPayment: true)),
- Options);
+ Request(
+ PayloadJson(
+ "Subscription",
+ isSubscriptionPayment: true,
+ isFirstSubscriptionPayment: true
+ )
+ ),
+ Options
+ );
_ = Assert.IsInstanceOfType(result.Event);
}
@@ -152,7 +175,8 @@ public async Task HandleAsync_WhenRecurringSubscriptionPayment_ReturnsSubscripti
{
WebhookHandleResult result = await _handler.HandleAsync(
Request(PayloadJson("Subscription", isSubscriptionPayment: true)),
- Options);
+ Options
+ );
_ = Assert.IsInstanceOfType(result.Event);
}
@@ -163,8 +187,10 @@ public async Task HandleAsync_WhenRecurringSubscriptionPayment_ReturnsSubscripti
[DataRow("Referral", typeof(KoFiReferralEvent))]
public async Task HandleAsync_MapsKnownPayloadTypes(string payloadType, Type expected)
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(PayloadJson(payloadType)), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(PayloadJson(payloadType)),
+ Options
+ );
Assert.IsTrue(result.IsKnownEvent);
Assert.IsInstanceOfType(result.Event, expected);
@@ -173,8 +199,10 @@ public async Task HandleAsync_MapsKnownPayloadTypes(string payloadType, Type exp
[TestMethod]
public async Task HandleAsync_WhenPayloadTypeIsUnrecognised_ReturnsUnknownEventStill200()
{
- WebhookHandleResult result =
- await _handler.HandleAsync(Request(PayloadJson("Something New")), Options);
+ WebhookHandleResult result = await _handler.HandleAsync(
+ Request(PayloadJson("Something New")),
+ Options
+ );
Assert.AreEqual(200, result.Response.StatusCode);
Assert.IsTrue(result.IsAuthenticated);
@@ -188,17 +216,20 @@ public async Task HandleAsync_WhenCancelled_Throws()
using CancellationTokenSource cts = new();
await cts.CancelAsync();
- _ = await Assert.ThrowsExactlyAsync(
- () => _handler.HandleAsync(Request(), Options, cts.Token));
+ _ = await Assert.ThrowsExactlyAsync(() =>
+ _handler.HandleAsync(Request(), Options, cts.Token)
+ );
}
[TestMethod]
- public async Task HandleAsync_WhenRequestIsNull_Throws()
- => await Assert.ThrowsExactlyAsync(
- () => _handler.HandleAsync(null!, Options));
+ public async Task HandleAsync_WhenRequestIsNull_Throws() =>
+ await Assert.ThrowsExactlyAsync(() =>
+ _handler.HandleAsync(null!, Options)
+ );
[TestMethod]
- public async Task HandleAsync_WhenOptionsAreNull_Throws()
- => await Assert.ThrowsExactlyAsync(
- () => _handler.HandleAsync(Request(), null!));
+ public async Task HandleAsync_WhenOptionsAreNull_Throws() =>
+ await Assert.ThrowsExactlyAsync(() =>
+ _handler.HandleAsync(Request(), null!)
+ );
}