Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/allow to init client without providing parameters #42

Merged
17 changes: 17 additions & 0 deletions examples/Console/UseOnlyVoiceOrVerification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Sinch;

namespace Examples
{
public class UseOnlyVoiceOrVerification
{
/// <summary>
/// If you want to use only voice and/or verification api, you can init sinch client without providing
/// necessary credentials. But be aware that when you try to use other services which depends on the common credentials you will get an exception.
/// </summary>
public void Example()
{
var sinchVoiceClient = new SinchClient(null, null, null).Voice("appKey", "appSecret");
var sinchVerificationClient = new SinchClient(null, null, null).Verification("appKey", "appSecret");
}
}
}
119 changes: 94 additions & 25 deletions src/Sinch/SinchClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using Sinch.Auth;
Expand Down Expand Up @@ -130,6 +132,39 @@ public class SinchClient : ISinchClient

private readonly ApiUrlOverrides _apiUrlOverrides;

private readonly string _keyId;
private readonly string _keySecret;
private readonly string _projectId;

private readonly ISinchNumbers _numbers;
private readonly ISinchSms _sms;
private readonly ISinchConversation _conversation;
private readonly ISinchAuth _auth;

private void ValidateCommonCredentials()
{
var exceptions = new List<Exception>();
if (string.IsNullOrEmpty(_keyId))
{
exceptions.Add(new InvalidOperationException("keyId should have a value"));
}

if (string.IsNullOrEmpty(_projectId))
{
exceptions.Add(new InvalidOperationException("projectId should have a value"));
}

if (string.IsNullOrEmpty(_keySecret))
{
exceptions.Add(new InvalidOperationException("keySecret should have a value"));
}

if (exceptions.Any())
{
throw new AggregateException("Credentials are missing", exceptions);
}
}

/// <summary>
/// Initialize a new <see cref="SinchClient"/>
/// </summary>
Expand All @@ -138,51 +173,56 @@ public class SinchClient : ISinchClient
/// <param name="projectId">Your project id.</param>
/// <param name="options">Optional. See: <see cref="SinchOptions"/></param>
/// <exception cref="ArgumentNullException"></exception>
public SinchClient(string keyId, string keySecret, string projectId,
public SinchClient(string projectId, string keyId, string keySecret,
Action<SinchOptions> options = default)
{
if (keyId is null)
_projectId = projectId;
_keyId = keyId;
_keySecret = keySecret;


var optionsObj = new SinchOptions();
options?.Invoke(optionsObj);

if (optionsObj.LoggerFactory is not null) _loggerFactory = new LoggerFactory(optionsObj.LoggerFactory);
var logger = _loggerFactory?.Create<SinchClient>();
logger?.LogInformation("Initializing SinchClient...");


if (string.IsNullOrEmpty(projectId))
{
throw new ArgumentNullException(nameof(keyId), "Should have a value");
logger?.LogWarning($"{nameof(projectId)} is not set!");
}

if (keySecret is null)
if (string.IsNullOrEmpty(keyId))
{
throw new ArgumentNullException(nameof(keySecret), "Should have a value");
logger?.LogWarning($"{nameof(keyId)} is not set!");
}

if (projectId is null)
if (string.IsNullOrEmpty(keySecret))
{
throw new ArgumentNullException(nameof(projectId), "Should have a value");
logger?.LogWarning($"{nameof(keySecret)} is not set!");
}

var optionsObj = new SinchOptions();
options?.Invoke(optionsObj);

if (optionsObj.LoggerFactory is not null) _loggerFactory = new LoggerFactory(optionsObj.LoggerFactory);

_httpClient = optionsObj.HttpClient ?? new HttpClient();

var logger = _loggerFactory?.Create<SinchClient>();
logger?.LogInformation("Initializing SinchClient...");

_apiUrlOverrides = optionsObj?.ApiUrlOverrides;

ISinchAuth auth =
new OAuth(keyId, keySecret, _httpClient, _loggerFactory?.Create<OAuth>(),
new OAuth(_keyId, _keySecret, _httpClient, _loggerFactory?.Create<OAuth>(),
new Uri(_apiUrlOverrides?.AuthUrl ?? AuthApiUrl));
Auth = auth;
_auth = auth;
var httpCamelCase = new Http(auth, _httpClient, _loggerFactory?.Create<Http>(),
JsonNamingPolicy.CamelCase);
var httpSnakeCase = new Http(auth, _httpClient, _loggerFactory?.Create<Http>(),
SnakeCaseNamingPolicy.Instance);

Numbers = new Numbers.Numbers(projectId, new Uri(_apiUrlOverrides?.NumbersUrl ?? NumbersApiUrl),
_numbers = new Numbers.Numbers(_projectId, new Uri(_apiUrlOverrides?.NumbersUrl ?? NumbersApiUrl),
_loggerFactory, httpCamelCase);
Sms = new Sms(projectId, GetSmsBaseAddress(optionsObj.SmsHostingRegion, _apiUrlOverrides?.SmsUrl),
_sms = new Sms(_projectId, GetSmsBaseAddress(optionsObj.SmsHostingRegion, _apiUrlOverrides?.SmsUrl),
_loggerFactory,
httpSnakeCase);
Conversation = new Conversation.SinchConversationClient(projectId,
_conversation = new SinchConversationClient(_projectId,
new Uri(_apiUrlOverrides?.ConversationUrl ??
string.Format(ConversationApiUrlTemplate, optionsObj.ConversationRegion.Value)),
_loggerFactory, httpSnakeCase);
Expand All @@ -192,7 +232,7 @@ public SinchClient(string keyId, string keySecret, string projectId,

private static Uri GetSmsBaseAddress(SmsHostingRegion smsHostingRegion, string smsUrlOverride)
{
if (smsUrlOverride != null)
if (!string.IsNullOrEmpty(smsUrlOverride))
{
return new Uri(smsUrlOverride);
}
Expand All @@ -205,17 +245,46 @@ private static Uri GetSmsBaseAddress(SmsHostingRegion smsHostingRegion, string s
}

/// <inheritdoc/>
public ISinchNumbers Numbers { get; }
public ISinchNumbers Numbers
{
get
{
ValidateCommonCredentials();
return _numbers;
}
}

/// <inheritdoc/>
public ISinchSms Sms { get; }
public ISinchSms Sms
{
get
{
// TODO: when support service plan id make sure validation is proper here.
ValidateCommonCredentials();
return _sms;
}
}

/// <inheritdoc/>
public ISinchConversation Conversation { get; set; }
public ISinchConversation Conversation
{
get
{
ValidateCommonCredentials();
return _conversation;
}
}


/// <inheritdoc/>
public ISinchAuth Auth { get; }
public ISinchAuth Auth
{
get
{
ValidateCommonCredentials();
return _auth;
}
}

/// <inheritdoc/>
public ISinchVerificationClient Verification(string appKey, string appSecret,
Expand Down
80 changes: 54 additions & 26 deletions tests/Sinch.Tests/SinchClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,78 @@ namespace Sinch.Tests
{
public class SinchClientTests
{
[Fact]
public void Should_instantiate_sinch_client_with_provided_required_params()
[Theory]
[InlineData(null, null, null)]
[InlineData("projectId", null, null)]
[InlineData(null, "keyId", null)]
[InlineData(null, null, "keySecret")]
[InlineData("projectId", "keySecret", null)]
[InlineData("projectId", null, "keySecret")]
[InlineData(null, "keySecret", "keySecret")]
public void InitSinchClientWithoutCredentials(string projectId, string keyId, string keySecret)
{
var sinch = new SinchClient("TEST_KEY", "TEST_KEY_SECRET", "TEST_PROJECT_ID");
var sinch = new SinchClient(projectId, keyId, keySecret);
sinch.Should().NotBeNull();
sinch.Numbers.Should().NotBeNull();
}

[Fact]
public void InitSinchClientWithCustomHttpClient()
[Theory]
[InlineData(null, null, null,
"Credentials are missing (keyId should have a value) (projectId should have a value) (keySecret should have a value)")]
[InlineData("projectId", null, null,
"Credentials are missing (keyId should have a value) (keySecret should have a value)")]
[InlineData(null, "keyId", null,
"Credentials are missing (projectId should have a value) (keySecret should have a value)")]
[InlineData(null, null, "keySecret",
"Credentials are missing (keyId should have a value) (projectId should have a value)")]
[InlineData("projectId", "keySecret", null, "Credentials are missing (keySecret should have a value)")]
[InlineData("projectId", null, "keySecret", "Credentials are missing (keyId should have a value)")]
[InlineData(null, "keySecret", "keySecret", "Credentials are missing (projectId should have a value)")]
public void ThrowAggregateExceptionWhenAccessingCommonCredentialsProducts(string projectId, string keyId,
string keySecret, string message)
{
var httpClient = new HttpClient();
var sinch = new SinchClient("TEST_KEY", "TEST_KEY_SECRET", "TEST_PROJECT_ID",
options => { options.HttpClient = httpClient; });
sinch.Should().NotBeNull();
Helpers.GetPrivateField<HttpClient, SinchClient>(sinch, "_httpClient").Should().Be(httpClient);
}
var sinch = new SinchClient(projectId, keyId, keySecret);
var smsOp = () => sinch.Sms;
var aggregateExceptionSms = smsOp.Should().Throw<AggregateException>().Which;
aggregateExceptionSms.Message.Should().BeEquivalentTo(message);

[Fact]
public void ThrowNullKeyId()
{
Func<ISinchClient> initAction = () => new SinchClient(null, "secret", "project");
initAction.Should().Throw<ArgumentNullException>("Should have a value");
var conversationOp = () => sinch.Conversation;
var aggregateExceptionConversation = conversationOp.Should().Throw<AggregateException>().Which;
aggregateExceptionConversation.Message.Should().BeEquivalentTo(message);

var numbersOp = () => sinch.Numbers;
var aggregateExceptionNumbers = numbersOp.Should().Throw<AggregateException>().Which;
aggregateExceptionNumbers.Message.Should().BeEquivalentTo(message);

var authOp = () => sinch.Auth;
var aggregateExceptionAuth = authOp.Should().Throw<AggregateException>().Which;
aggregateExceptionAuth.Message.Should().BeEquivalentTo(message);
}

[Fact]
public void ThrowNullKeySecret()
public void GetServiceWithoutExceptionsIfCredentialsAreSet()
{
Func<ISinchClient> initAction = () => new SinchClient("secret", null, "project");
initAction.Should().Throw<ArgumentNullException>("Should have a value");
var sinch = new SinchClient("projectId", "keyId", "keySecret");
sinch.Conversation.Should().NotBeNull();
sinch.Sms.Should().NotBeNull();
sinch.Auth.Should().NotBeNull();
sinch.Numbers.Should().NotBeNull();
}

[Fact]
public void ThrowNullProjectId()
public void InitializeOwnHttpIfNotPassed()
{
Func<ISinchClient> initAction = () => new SinchClient("id", "secret", null);
initAction.Should().Throw<ArgumentNullException>("Should have a value");
var sinch = new SinchClient("proj", "id", "secret");
Helpers.GetPrivateField<HttpClient, SinchClient>(sinch, "_httpClient").Should().NotBeNull();
}

[Fact]
public void InitializeOwnHttpIfNotPassed()
public void InitSinchClientWithCustomHttpClient()
{
var sinch = new SinchClient("id", "secret", "proj");
Helpers.GetPrivateField<HttpClient, SinchClient>(sinch, "_httpClient").Should().NotBeNull();
var httpClient = new HttpClient();
var sinch = new SinchClient("TEST_PROJECT_ID", "TEST_KEY", "TEST_KEY_SECRET",
options => { options.HttpClient = httpClient; });
sinch.Should().NotBeNull();
Helpers.GetPrivateField<HttpClient, SinchClient>(sinch, "_httpClient").Should().Be(httpClient);
}
}
}
4 changes: 2 additions & 2 deletions tests/Sinch.Tests/e2e/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class TestBase
protected TestBase()
{
Env.Load();
SinchClientMockStudio = new SinchClient("key_id", "key_secret", ProjectId,
SinchClientMockStudio = new SinchClient(ProjectId, "key_id", "key_secret",
options =>
{
options.ApiUrlOverrides = new ApiUrlOverrides()
Expand All @@ -29,7 +29,7 @@ protected TestBase()
};
});

SinchClientMockServer = new SinchClient("key_id", "key_secret", ProjectId, options =>
SinchClientMockServer = new SinchClient(ProjectId, "key_id", "key_secret", options =>
{
options.ApiUrlOverrides = new ApiUrlOverrides()
{
Expand Down
Loading