Skip to content

Commit

Permalink
feat: Added IServiceCollection extension methods for PublisherClient …
Browse files Browse the repository at this point in the history
…and SubscriberClient
  • Loading branch information
Rishabh-V committed Feb 14, 2023
1 parent 1a908dc commit fe942e0
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Google.Apis.CloudResourceManager.v1" Version="[1.57.0.2677, 2.0.0.0)" />
<ProjectReference Include="..\..\..\tools\Google.Cloud.ClientTesting\Google.Cloud.ClientTesting.csproj" />
<ProjectReference Include="..\Google.Cloud.PubSub.V1\Google.Cloud.PubSub.V1.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License").
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Apis.Auth.OAuth2;
using Grpc.Auth;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace Google.Cloud.PubSub.V1.Tests;

public class ServiceCollectionExtensionsTest
{
[Fact]
public void AddPublisherClient_WithTopicName()
{
// Arrange.
TopicName topicName = TopicName.FromProjectTopic("projectId", "topicId");
IServiceCollection serviceCollection = new ServiceCollection();
// As Application Default Credentials are not available for unit tests, we need to pass a fake credential.
serviceCollection.AddSingleton(new FakeCredential().ToChannelCredentials());

// Act.
serviceCollection.AddPublisherClient(topicName);
var provider = serviceCollection.BuildServiceProvider();
var client = provider.GetService<PublisherClient>();

// Assert.
Assert.NotNull(client);
Assert.Equal(topicName, client.TopicName);
}

[Fact]
public void AddPublisherClient_WithAction()
{
// Arrange.
TopicName topicName = TopicName.FromProjectTopic("projectId", "topicId");
IServiceCollection serviceCollection = new ServiceCollection();
// As Application Default Credentials are not available for unit tests, we need to pass a fake credential.
serviceCollection.AddSingleton(new FakeCredential().ToChannelCredentials());

// Act.
serviceCollection.AddPublisherClient(builder => builder.TopicName = topicName);
var provider = serviceCollection.BuildServiceProvider();
var client = provider.GetService<PublisherClient>();

// Assert.
Assert.NotNull(client);
Assert.Equal(topicName, client.TopicName);
}

[Fact]
public void AddPublisherClient_WithAction_NoTopicName()
{
// Arrange.
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new FakeCredential().ToChannelCredentials());

// Act.
serviceCollection.AddPublisherClient(builder => { });
var provider = serviceCollection.BuildServiceProvider();

// Assert.
// When TopicName isn't specified, it results in an InvalidOperationException.
Assert.Throws<InvalidOperationException>(provider.GetRequiredService<PublisherClient>);
}

[Fact]
public void AddPublisherClient_WithNullTopicName()
{
// Arrange.
// Passing null topicName to AddPublisherClient should throw ArgumentNullException.
IServiceCollection serviceCollection = new ServiceCollection();

// Act and Assert.
Assert.Throws<ArgumentNullException>(() => serviceCollection.AddPublisherClient(topicName: null));
}

[Fact]
public void AddPublisherClient_WithNullAction()
{
// Arrange.
// Passing null action to AddPublisherClient should throw ArgumentNullException.
IServiceCollection serviceCollection = new ServiceCollection();

// Act and assert.
Assert.Throws<ArgumentNullException>(() => serviceCollection.AddPublisherClient(action: null));
}

[Fact]
public void AddSubscriberClient_WithTopicName()
{
// Arrange.
SubscriptionName subscriptionName = SubscriptionName.FromProjectSubscription("projectId", "subscriptionId");
IServiceCollection serviceCollection = new ServiceCollection();
// As Application Default Credentials are not available for unit tests, we need to pass a fake credential.
serviceCollection.AddSingleton(new FakeCredential().ToChannelCredentials());

// Act.
serviceCollection.AddSubscriberClient(subscriptionName);
var provider = serviceCollection.BuildServiceProvider();
var client = provider.GetService<SubscriberClient>();

// Assert.
Assert.NotNull(client);
Assert.Equal(subscriptionName, client.SubscriptionName);
}

[Fact]
public void AddSubscriberClient_WithAction()
{
// Arrange.
SubscriptionName subscriptionName = SubscriptionName.FromProjectSubscription("projectId", "subscriptionId");
IServiceCollection serviceCollection = new ServiceCollection();
// As Application Default Credentials are not available for unit tests, we need to pass a fake credential.
serviceCollection.AddSingleton(new FakeCredential().ToChannelCredentials());

// Act.
serviceCollection.AddSubscriberClient(builder => builder.SubscriptionName = subscriptionName);
var provider = serviceCollection.BuildServiceProvider();
var client = provider.GetService<SubscriberClient>();

// Assert.
Assert.NotNull(client);
Assert.Equal(subscriptionName, client.SubscriptionName);
}

[Fact]
public void AddSubscriberClient_WithAction_NoSubscriptionName()
{
// Arrange.
// When SubscriptionName isn't specified in the builder, it should result in an InvalidOperationException.
IServiceCollection serviceCollection = new ServiceCollection();

//Act.
serviceCollection.AddSubscriberClient(builder => { });
var provider = serviceCollection.BuildServiceProvider();

// Assert.
Assert.Throws<InvalidOperationException>(provider.GetRequiredService<SubscriberClient>);
}

[Fact]
public void AddSubscriberClient_WithNullSubscriptionName()
{
// Arrange.
// Passing null subscriptionName to AddSubscriberClient should throw ArgumentNullException.
IServiceCollection serviceCollection = new ServiceCollection();

// Act and assert.
Assert.Throws<ArgumentNullException>(() => serviceCollection.AddSubscriberClient(subscriptionName: null));
}

[Fact]
public void AddSubscriberClient_WithNullAction()
{
// Arrange.
// Passing null action to AddSubscriberClient should throw ArgumentNullException.
IServiceCollection serviceCollection = new ServiceCollection();

// Act and assert.
Assert.Throws<ArgumentNullException>(() => serviceCollection.AddSubscriberClient(action: null));
}

// Fake credential used just to ensure that we don't fetch the application default credentials
// (which may not be available for unit tests).
private class FakeCredential : ITokenAccess
{
public Task<string> GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License").
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Gax;
using Google.Cloud.PubSub.V1;
using System;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Provides extension methods to configure dependency injection with PubSub.
/// </summary>
public static partial class ServiceCollectionExtensions
{
/// <summary>
/// Adds a singleton <see cref="PublisherClient"/> to the <see cref="IServiceCollection"/> as customized by the <paramref name="action"/>.
/// </summary>
/// <param name="services">
/// The <see cref="IServiceCollection"/> to add the singleton client to.
/// </param>
/// <param name="action">
/// An action delegate to invoke on the <see cref="PublisherClientBuilder"/> for configuring the <see cref="PublisherClient"/>. This is invoked before <paramref name="services"/> are used.
/// Must not be null and at-least <see cref="PublisherClientBuilder.TopicName"/> must be set.
/// </param>
/// <returns>The updated <see cref="IServiceCollection"/>, for method chaining.</returns>
public static IServiceCollection AddPublisherClient(this IServiceCollection services, Action<PublisherClientBuilder> action)
{
GaxPreconditions.CheckNotNull(action, nameof(action));
return services.AddSingleton(provider =>
{
var builder = new PublisherClientBuilder();
action.Invoke(builder);
return builder.Build(provider);
});
}

/// <summary>
/// Adds a singleton <see cref="PublisherClient"/> associated with the specified <see cref="TopicName"/>, using default settings to the <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">
/// The <see cref="IServiceCollection"/> to add the singleton client to.
/// </param>
/// <param name="topicName">The <see cref="TopicName"/> to publish messages to. Must not be null.</param>
/// <returns>The updated <see cref="IServiceCollection"/>, for method chaining.</returns>
public static IServiceCollection AddPublisherClient(this IServiceCollection services, TopicName topicName)
{
GaxPreconditions.CheckNotNull(topicName, nameof(topicName));
return services.AddPublisherClient(new Action<PublisherClientBuilder>(builder => builder.TopicName = topicName));
}

/// <summary>
/// Adds a singleton <see cref="SubscriberClient"/> to the <see cref="IServiceCollection"/> as customized by the <paramref name="action"/>.
/// </summary>
/// <param name="services">
/// The <see cref="IServiceCollection"/> to add the singleton client to.
/// </param>
/// <param name="action">
/// An action to invoke on the <see cref="SubscriberClientBuilder"/> for configuring the <see cref="SubscriberClient"/>. This is invoked before <paramref name="services"/> are used.
/// Must not be null and at-least <see cref="SubscriberClientBuilder.SubscriptionName"/> must be set.
/// </param>
/// <returns>The updated <see cref="IServiceCollection"/>, for method chaining.</returns>
public static IServiceCollection AddSubscriberClient(this IServiceCollection services, Action<SubscriberClientBuilder> action)
{
GaxPreconditions.CheckNotNull(action, nameof(action));
return services.AddSingleton(provider =>
{
var builder = new SubscriberClientBuilder();
action.Invoke(builder);
return builder.Build(provider);
});
}

/// <summary>
/// Adds a singleton <see cref="SubscriberClient"/> associated with the specified <see cref="SubscriptionName"/>, using default settings to the <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">
/// The <see cref="IServiceCollection"/> to add the singleton client to.
/// </param>
/// <param name="subscriptionName">The <see cref="SubscriptionName"/> to receive messages from. Must not be null.</param>
/// <returns>The updated <see cref="IServiceCollection"/>, for method chaining.</returns>
public static IServiceCollection AddSubscriberClient(this IServiceCollection services, SubscriptionName subscriptionName)
{
GaxPreconditions.CheckNotNull(subscriptionName, nameof(subscriptionName));
return services.AddSubscriberClient(new Action<SubscriberClientBuilder>(builder => builder.SubscriptionName = subscriptionName));
}
}

0 comments on commit fe942e0

Please sign in to comment.