Skip to content

Commit

Permalink
Design changes to the webhook notifier builder; testing the subscript…
Browse files Browse the repository at this point in the history
…ion resolution
  • Loading branch information
tsutomi committed Oct 29, 2023
1 parent 04d298d commit 03e206c
Show file tree
Hide file tree
Showing 15 changed files with 318 additions and 85 deletions.
2 changes: 1 addition & 1 deletion Deveel.Webhooks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Webhooks.XUnit", "te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Webhooks.Receiver.TestApi", "test\Deveel.Webhooks.Receiver.TestApi\Deveel.Webhooks.Receiver.TestApi.csproj", "{4942C858-277D-438D-B822-92055B8E8DF7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Webhooks.DeliveryResultLogging.Tests", "test\Deveel.Webhooks.DeliveryResultLogging.Tests\Deveel.Webhooks.DeliveryResultLogging.Tests.csproj", "{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Webhooks.DeliveryResultLogging.Tests", "test\Deveel.Webhooks.DeliveryResultLogging.Tests\Deveel.Webhooks.DeliveryResultLogging.Tests.csproj", "{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Deveel.Json {
/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/Deveel.Webhooks.Sender/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public static WebhookSenderBuilder<TWebhook> AddWebhookSender<TWebhook>(this ISe
var builder = new WebhookSenderBuilder<TWebhook>(services);

services.TryAddSingleton(builder);
services.AddOptions<WebhookSenderOptions<TWebhook>>();

return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Deveel.Data;
using Deveel.Data;

namespace Deveel.Webhooks {
/// <summary>
/// Extends the <see cref="WebhookNotifierBuilder{TWebhook}"/> to add Entity Framework
/// support for the delivery results entities.
/// </summary>
public static class WebhookNotifierBuilderExtensions {
/// <summary>
/// Configures the builder to use the default entity framework delivery
/// results support.
/// </summary>
/// <typeparam name="TWebhook">
/// The type of the webhook to be used in the notifier.
/// </typeparam>
/// <param name="builder">
/// The builder to configure.
/// </param>
/// <returns>
/// Returns the same builder instance for chaining calls.
/// </returns>
public static WebhookNotifierBuilder<TWebhook> UseEntityFrameworkDeliveryResults<TWebhook>(this WebhookNotifierBuilder<TWebhook> builder)
where TWebhook : class
=> builder.UseEntityFrameworkDeliveryResults<TWebhook>(typeof(DbWebhookDeliveryResult));


/// <summary>
/// Configures the builder to use the default entity framework delivery
/// results support.
/// </summary>
/// <typeparam name="TWebhook">
/// The type of the webhook to be used in the notifier.
/// </typeparam>
/// <param name="builder">
/// The builder to configure.
/// </param>
/// <param name="resultType">
/// The type of the delivery result entity to be used.
/// </param>
/// <returns>
/// Returns the same builder instance for chaining calls.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the given <paramref name="resultType"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the given <paramref name="resultType"/> is not a valid
/// type for the delivery result entity.
/// </exception>
public static WebhookNotifierBuilder<TWebhook> UseEntityFrameworkDeliveryResults<TWebhook>(this WebhookNotifierBuilder<TWebhook> builder, Type resultType)
where TWebhook : class {
ArgumentNullException.ThrowIfNull(resultType, nameof(resultType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public MongoDbWebhookStorageBuilder<TSubscription> WithConnectionStringName(stri
/// <returns>
/// Returns the current instance of the builder for chaining.
/// </returns>
public MongoDbWebhookStorageBuilder<TSubscription> WithTenantConnectionString(Action<ITenantInfo?, MongoConnectionBuilder> configure) {
public MongoDbWebhookStorageBuilder<TSubscription> WithTenantConnection(Action<ITenantInfo?, MongoConnectionBuilder> configure) {
Services.AddMongoDbContext<MongoDbWebhookContext>(configure);

return this;
Expand Down Expand Up @@ -161,32 +161,10 @@ public MongoDbWebhookStorageBuilder<TSubscription> UseSubscriptionRepository<TRe
Services.RemoveAll<IWebhookSubscriptionRepository<TSubscription>>();

Services.AddRepository<TRepository>();
//Services.AddScoped<IWebhookSubscriptionStore<MongoWebhookSubscription>, TStore>();
//Services.AddScoped<TStore>();

return this;
}

/// <summary>
/// Registers the given type of storage to be used for
/// storing the webhook delivery results.
/// </summary>
/// <typeparam name="TRepository">
/// The type of the storage to use for storing the webhook delivery results,
/// derived from <see cref="MongoDbWebhookDeliveryResultRepository"/>.
/// </typeparam>
/// <returns>
/// Returns the current instance of the builder for chaining.
/// </returns>
public MongoDbWebhookStorageBuilder<TSubscription> UseDeliveryResultRepository<TRepository>()
where TRepository : MongoDbWebhookDeliveryResultRepository {
Services.AddRepository<TRepository>();
// Services.AddScoped<IWebhookDeliveryResultStore<MongoWebhookDeliveryResult>, TStore>();

return this;
}


/// <summary>
/// Registers a service that is used to convert the webhook
/// of the given type to a <see cref="MongoWebhook"/> object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Threading;
using System.Threading.Tasks;

using Deveel.Data;

namespace Deveel.Webhooks {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Extensions.DependencyInjection;

namespace Deveel.Webhooks {
Expand Down Expand Up @@ -63,6 +57,5 @@ public static WebhookNotifierBuilder<TWebhook> UseDefaultSubscriptionResolver<TW
var resolverType = typeof(WebhookSubscriptionResolver<>).MakeGenericType(subscriptionType);
return builder.UseSubscriptionResolver(resolverType, lifetime);
}

}
}
27 changes: 3 additions & 24 deletions src/Deveel.Webhooks.Service/Webhooks/WebhookSubscriptionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;

using Deveel.Data;

using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -56,29 +54,10 @@ private void RegisterDefaults() {
Services.TryAddSingleton<IWebhookSubscriptionValidator<TSubscription>, WebhookSubscriptionValidator<TSubscription>>();

Services.TryAddScoped<IWebhookSubscriptionResolver, WebhookSubscriptionResolver<TSubscription>>();
Services.TryAddScoped<WebhookSubscriptionResolver<TSubscription>>();
// Services.TryAddScoped<ITenantWebhookSubscriptionResolver, TenantWebhookSubscriptionResolver<TSubscription>>();
}

///// <summary>
///// Adds the notification capabilities to the service.
///// </summary>
///// <typeparam name="TWebhook">
///// The type of the webhook that is notified the subscribers.
///// </typeparam>
///// <param name="configure">
///// A callback that is used to configure the webhook notifier.
///// </param>
///// <returns>
///// Returns this instance of the <see cref="WebhookSubscriptionBuilder{TSubscription}"/>.
///// </returns>
///// <seealso cref="ITenantWebhookNotifier{TWebhook}"/>
//public WebhookSubscriptionBuilder<TSubscription> UseNotifier<TWebhook>(Action<WebhookNotifierBuilder> configure)
// where TWebhook : class, IWebhook {
// Services.AddWebhookNotifier<TWebhook>(configure);

// return this;
//}

/// <summary>
/// Registers a custom <see cref="WebhookSubscriptionManager{TSubscription}"/>
/// that overrides the default one.
Expand Down Expand Up @@ -129,8 +108,8 @@ public WebhookSubscriptionBuilder<TSubscription> UseSubscriptionManager()
/// </returns>
public WebhookSubscriptionBuilder<TSubscription> AddSubscriptionValidator<TValidator>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
where TValidator : class, IWebhookSubscriptionValidator<TSubscription> {
Services.Add(new ServiceDescriptor(typeof(IWebhookSubscriptionValidator<TSubscription>), typeof(TValidator), lifetime));
Services.Add(new ServiceDescriptor(typeof(TValidator), typeof(TValidator), lifetime));

Services.AddEntityValidator<TValidator>(lifetime);

return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ namespace Deveel.Webhooks {
/// </typeparam>
public class WebhookSubscriptionResolver<TSubscription> : IWebhookSubscriptionResolver
where TSubscription : class, IWebhookSubscription {
private readonly IWebhookSubscriptionRepository<TSubscription> store;
private readonly IWebhookSubscriptionRepository<TSubscription> repository;
private readonly IWebhookSubscriptionCache? cache;
private ILogger logger;

/// <summary>
/// Constructs a <see cref="WebhookSubscriptionResolver{TSubscription}"/>
/// backed by a given store.
/// </summary>
/// <param name="store">
/// <param name="repository">
/// The store to be used to retrieve the subscriptions.
/// </param>
/// <param name="cache">
Expand All @@ -46,10 +46,10 @@ public class WebhookSubscriptionResolver<TSubscription> : IWebhookSubscriptionRe
/// An optional logger to be used to log the operations.
/// </param>
public WebhookSubscriptionResolver(
IWebhookSubscriptionRepository<TSubscription> store,
IWebhookSubscriptionRepository<TSubscription> repository,
IWebhookSubscriptionCache? cache = null,
ILogger<TenantWebhookSubscriptionResolver<TSubscription>>? logger = null) {
this.store = store;
this.repository = repository;
this.cache = cache;
this.logger = logger ?? NullLogger<TenantWebhookSubscriptionResolver<TSubscription>>.Instance;
}
Expand Down Expand Up @@ -78,7 +78,7 @@ public async Task<IList<IWebhookSubscription>> ResolveSubscriptionsAsync(string
if (list == null) {
logger.LogTrace("No webhook subscriptions to event {EventType} of tenant {TenantId} were found in cache", eventType);

var result = await store.GetByEventTypeAsync(eventType, activeOnly, cancellationToken);
var result = await repository.GetByEventTypeAsync(eventType, activeOnly, cancellationToken);
list = result.Cast<IWebhookSubscription>().ToList();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Deveel.Webhooks/Webhooks/IWebhookNotifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ public interface IWebhookNotifier<TWebhook> where TWebhook : class {
/// Returns an object that describes the aggregated final result of
/// the notification process executed.
/// </returns>
Task<WebhookNotificationResult<TWebhook>> NotifyAsync(EventInfo eventInfo, CancellationToken cancellationToken);
Task<WebhookNotificationResult<TWebhook>> NotifyAsync(EventInfo eventInfo, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Deveel.Data;

using Microsoft.Extensions.DependencyInjection;

using Xunit.Abstractions;

using static System.Formats.Asn1.AsnWriter;

namespace Deveel.Webhooks {
public class EntitySubscriptionResolverTests : EntityWebhookTestBase {
public EntitySubscriptionResolverTests(SqliteTestDatabase sqlite, ITestOutputHelper outputHelper)
: base(sqlite, outputHelper) {
}

protected IList<DbWebhookSubscription> Subscriptions { get; private set; }

protected IRepository<DbWebhookSubscription> Repository => Services.GetRequiredService<IRepository<DbWebhookSubscription>>();

public IWebhookSubscriptionResolver Resolver => Services.GetRequiredService<IWebhookSubscriptionResolver>();

public override async Task InitializeAsync() {
await base.InitializeAsync();

Subscriptions = new DbWebhookSubscriptionFaker().Generate(102);

await Repository.AddRangeAsync(Subscriptions);
}

public override async Task DisposeAsync() {
await Repository.RemoveRangeAsync(Subscriptions);

await base.DisposeAsync();
}

private DbWebhookSubscription Random(Func<DbWebhookSubscription, bool>? predicate = null, int maxRetries = 100) {
while (maxRetries-- >= 0) {
var index = System.Random.Shared.Next(0, Subscriptions.Count - 1);
var subscription = Subscriptions[index];

if (predicate == null || predicate(subscription))
return subscription;
}

throw new InvalidOperationException("Could not find a random subscription");
}

[Fact]
public async Task ResolveActiveSubscriptions() {
var subscription = Random(x => x.Events.Any());
var eventType = subscription.Events[0].EventType;

var subCount = Subscriptions.Count(x => x.Events.Any(y => y.EventType == eventType) &&
x.Status == WebhookSubscriptionStatus.Active);

var subscriptions = await Resolver.ResolveSubscriptionsAsync(eventType, true, CancellationToken.None);

Assert.NotNull(subscriptions);
Assert.NotEmpty(subscriptions);

Assert.Equal(subCount, subscriptions.Count);
}

[Fact]
public async Task ResolveAllSubscriptions() {
var subscription = Random(x => x.Events.Any());
var eventType = subscription.Events[0].EventType;

var subCount = Subscriptions.Count(x => x.Events.Any(y => y.EventType == eventType));

var subscriptions = await Resolver.ResolveSubscriptionsAsync(eventType, false, CancellationToken.None);

Assert.NotNull(subscriptions);
Assert.NotEmpty(subscriptions);

Assert.Equal(subCount, subscriptions.Count);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public abstract class MongoDbWebhookTestBase : IAsyncLifetime {

protected MongoDbWebhookTestBase(MongoTestDatabase mongo, ITestOutputHelper outputHelper) {
this.mongo = mongo;

Client = new MongoClient(mongo.ConnectionString);

Services = BuildServiceProvider(outputHelper);
Scope = Services.CreateScope();
}
Expand All @@ -40,17 +43,16 @@ protected MongoDbWebhookTestBase(MongoTestDatabase mongo, ITestOutputHelper outp

protected string ConnectionString => mongo.ConnectionString;

protected MongoClient Client { get; }

public virtual async Task InitializeAsync() {
var client = new MongoClient(ConnectionString);
await client.GetDatabase("webhooks").CreateCollectionAsync(MongoDbWebhookStorageConstants.SubscriptionCollectionName);
await client.GetDatabase("webhooks").CreateCollectionAsync(MongoDbWebhookStorageConstants.DeliveryResultsCollectionName);
await Client.GetDatabase("webhooks").CreateCollectionAsync(MongoDbWebhookStorageConstants.SubscriptionCollectionName);
await Client.GetDatabase("webhooks").CreateCollectionAsync(MongoDbWebhookStorageConstants.DeliveryResultsCollectionName);
}

public virtual async Task DisposeAsync() {
var client = new MongoClient(ConnectionString);

await client.GetDatabase("webhooks").DropCollectionAsync(MongoDbWebhookStorageConstants.SubscriptionCollectionName);
await client.GetDatabase("webhooks").DropCollectionAsync(MongoDbWebhookStorageConstants.DeliveryResultsCollectionName);
await Client.GetDatabase("webhooks").DropCollectionAsync(MongoDbWebhookStorageConstants.SubscriptionCollectionName);
await Client.GetDatabase("webhooks").DropCollectionAsync(MongoDbWebhookStorageConstants.DeliveryResultsCollectionName);

Scope.Dispose();
}
Expand Down
Loading

0 comments on commit 03e206c

Please sign in to comment.