Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .netconfig
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,20 @@

etag = da7c0104131bd474b52fc9bc9f9bda6470e24ae38d4fb9f5c4f719bc01370ab5
weak
[file "src/Tests/Attributes.cs"]
url = https://github.com/devlooped/catbag/blob/main/Xunit/Attributes.cs
sha = 615c1e2521340dcd85132807b368a52ff53e4ba7

etag = ec1645067cc2319c2ce3304900c260eb8ec700d50b6d8d62285914a6c96e01f9
weak
[file ".github/actions/dotnet/action.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/actions/dotnet/action.yml
sha = f2b690ce307acb76c5b8d7faec1a5b971a93653e
etag = 27ea11baa2397b3ec9e643a935832da97719c4e44215cfd135c49cad4c29373f
weak
[file "src/WhatsApp/Throw.cs"]
url = https://github.com/devlooped/catbag/blob/main/System/Throw.cs
sha = 48aeee281f63caabffeae14d7d84a6ece89e7907

etag = 1b3819a90c19d9de27b8c40e539934549aadd79ed1925cda8e8af6061eba3608
weak
[file "src/Tests/Extensions/Attributes.cs"]
url = https://github.com/devlooped/catbag/blob/main/Xunit/Attributes.cs
etag = c77e7b435ce1df06fb60a3b0e15a0833d8e45d4d19f366c6184140ebb4814b1a
weak
sha = 40914971d4d6b42d6f8a90923b131136f7e609a5

2 changes: 1 addition & 1 deletion src/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
WriteIndented = true
});

builder.UseWhatsApp<IWhatsAppClient, ILogger<Program>, JsonSerializerOptions>(async (client, logger, options, message) =>
builder.UseWhatsApp<IWhatsAppClient, ILogger<Program>, JsonSerializerOptions>(async (client, logger, options, message, cancellation) =>
{
logger.LogInformation("💬 Received message: {Message}", message);

Expand Down
96 changes: 0 additions & 96 deletions src/Tests/Attributes.cs

This file was deleted.

172 changes: 172 additions & 0 deletions src/Tests/Extensions/Attributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Configuration;

namespace Xunit;

public class SecretsFactAttribute : FactAttribute
{
public static IConfiguration Configuration { get; set; } = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddUserSecrets<SecretsFactAttribute>()
.Build();

public SecretsFactAttribute(params string[] secrets)
{
var missing = new HashSet<string>();

foreach (var secret in secrets)
{
if (string.IsNullOrEmpty(Configuration[secret]))
missing.Add(secret);
}

if (missing.Count > 0)
Skip = "Missing user secrets: " + string.Join(',', missing);
}
}

public class LocalFactAttribute : SecretsFactAttribute
{
public LocalFactAttribute(params string[] secrets) : base(secrets)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
Skip = "Non-CI test";
}
}

public class CIFactAttribute : FactAttribute
{
public CIFactAttribute()
{
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
Skip = "CI-only test";
}
}

public class RuntimeFactAttribute : FactAttribute
{
/// <summary>
/// Use <c>nameof(OSPLatform.Windows|Linux|OSX|FreeBSD)</c>
/// </summary>
public RuntimeFactAttribute(string osPlatform)
{
if (osPlatform != null && !RuntimeInformation.IsOSPlatform(OSPlatform.Create(osPlatform)))
Skip = $"Only running on {osPlatform}.";
}

public RuntimeFactAttribute(Architecture architecture)
{
if (RuntimeInformation.ProcessArchitecture != architecture)
Skip = $"Requires {architecture} but was {RuntimeInformation.ProcessArchitecture}.";
}

/// <summary>
/// Empty constructor for use in combination with RuntimeIdentifier property.
/// </summary>
public RuntimeFactAttribute() { }

/// <summary>
/// Sets the runtime identifier the test requires to run.
/// </summary>
public string? RuntimeIdentifier
{
get => RuntimeInformation.RuntimeIdentifier;
set
{
if (value != null && RuntimeInformation.RuntimeIdentifier != value)
Skip += $"Requires {value} but was {RuntimeInformation.RuntimeIdentifier}.";
}
}
}

public class RuntimeTheoryAttribute : TheoryAttribute
{
/// <summary>
/// Use <c>nameof(OSPLatform.Windows|Linux|OSX|FreeBSD)</c>
/// </summary>
public RuntimeTheoryAttribute(string osPlatform)
{
if (osPlatform != null && !RuntimeInformation.IsOSPlatform(OSPlatform.Create(osPlatform)))
Skip = $"Only running on {osPlatform}.";
}

public RuntimeTheoryAttribute(Architecture architecture)
{
if (RuntimeInformation.ProcessArchitecture != architecture)
Skip = $"Requires {architecture} but was {RuntimeInformation.ProcessArchitecture}.";
}

/// <summary>
/// Empty constructor for use in combination with RuntimeIdentifier property.
/// </summary>
public RuntimeTheoryAttribute() { }

/// <summary>
/// Sets the runtime identifier the test requires to run.
/// </summary>
public string? RuntimeIdentifier
{
get => RuntimeInformation.RuntimeIdentifier;
set
{
if (value != null && RuntimeInformation.RuntimeIdentifier != value)
Skip += $"Requires {value} but was {RuntimeInformation.RuntimeIdentifier}.";
}
}
}

public class SecretsTheoryAttribute : TheoryAttribute
{
public SecretsTheoryAttribute(params string[] secrets)
{
var missing = new HashSet<string>();

foreach (var secret in secrets)
{
if (string.IsNullOrEmpty(SecretsFactAttribute.Configuration[secret]))
missing.Add(secret);
}

if (missing.Count > 0)
Skip = "Missing user secrets: " + string.Join(',', missing);
}
}

public class LocalTheoryAttribute : SecretsTheoryAttribute
{
public LocalTheoryAttribute(params string[] secrets) : base(secrets)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
Skip = "Non-CI test";
}
}

public class CITheoryAttribute : SecretsTheoryAttribute
{
public CITheoryAttribute(params string[] secrets) : base(secrets)
{
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
Skip = "CI-only test";
}
}

public class DebuggerFactAttribute : FactAttribute
{
public DebuggerFactAttribute()
{
if (!System.Diagnostics.Debugger.IsAttached)
Skip = "Only running in the debugger";
}
}

public class DebuggerTheoryAttribute : TheoryAttribute
{
public DebuggerTheoryAttribute()
{
if (!System.Diagnostics.Debugger.IsAttached)
Skip = "Only running in the debugger";
}
}
29 changes: 29 additions & 0 deletions src/Tests/Extensions/LoggerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

public static class LoggerFactoryExtensions
{
public static ILoggerFactory AsLoggerFactory(this ITestOutputHelper output) => new LoggerFactory(output);
}

public class LoggerFactory(ITestOutputHelper output) : ILoggerFactory
{
public ILogger CreateLogger(string categoryName) => new TestOutputLogger(output, categoryName);
public void AddProvider(ILoggerProvider provider) { }
public void Dispose() { }

// create ilogger implementation over testoutputhelper
public class TestOutputLogger(ITestOutputHelper output, string categoryName) : ILogger
{
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null!;

public bool IsEnabled(LogLevel logLevel) => true;

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
if (state == null) throw new ArgumentNullException(nameof(state));
output.WriteLine($"{logLevel}: {categoryName}: {formatter(state, exception)}");
}
}
}
62 changes: 62 additions & 0 deletions src/Tests/PipelineTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
namespace Devlooped.WhatsApp;

public class PipelineTests(ITestOutputHelper output)
{
readonly static Service service = new("1234", "1234");
readonly static User user = new("kzu", "5678");

[Fact]
public async Task CanBuildEmptyPipeline()
{
var builder = new WhatsAppHandlerBuilder();

var handler = builder.Build();

await handler.HandleAsync(new ReactionMessage("1234", service, user, 0, "🗽"));
}

[Fact]
public async Task CanBuildDecoratingPipeline()
{
var called = false;

var pipeline = new WhatsAppHandlerBuilder()
.Use((message, inner, cancellation) =>
{
called = true;
return inner.HandleAsync(message, cancellation);
})
.Build();

await pipeline.HandleAsync(new ReactionMessage("1234", service, user, 0, "🗽"));

Assert.True(called);
}

[Fact]
public async Task CanBuildLoggingPipeline()
{
var after = false;
var before = false;

var pipeline = new WhatsAppHandlerBuilder()
.Use((message, inner, cancellation) =>
{
after = true;
Assert.True(before);
return inner.HandleAsync(message, cancellation);
})
.UseLogging(output.AsLoggerFactory())
.Use((message, inner, cancellation) =>
{
before = true;
Assert.False(after);
return inner.HandleAsync(message, cancellation);
})
.Build();

await pipeline.HandleAsync(new ReactionMessage("1234", service, user, 0, "🗽"));

Assert.True(after);
}
}
Loading