Skip to content

Commit

Permalink
#9 Allow URLs to be on prem
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrii Snihyr committed Aug 27, 2018
1 parent 3a76301 commit 107276c
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 74 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

[![Build status](https://ci.appveyor.com/api/projects/status/et2718qanpkjf55m?svg=true)](https://ci.appveyor.com/project/BerserkerDotNet/vsts-net)

[![Nuget](https://buildstats.info/nuget/VSTS.Net?v=0.2.1)](https://www.nuget.org/packages/VSTS.Net)
[![Nuget](https://buildstats.info/nuget/VSTS.Net?v=0.2.2)](https://www.nuget.org/packages/VSTS.Net)

[![Nuget](https://buildstats.info/nuget/VSTS.Net.AspNetCore?v=0.2.1)](https://www.nuget.org/packages/VSTS.Net.AspNetCore)
[![Nuget](https://buildstats.info/nuget/VSTS.Net.AspNetCore?v=0.2.2)](https://www.nuget.org/packages/VSTS.Net.AspNetCore)

[Api documentation](https://berserkerdotnet.github.io/VSTS.Net/site/api/index.html)

Expand All @@ -17,9 +17,11 @@
var query = @"SELECT [System.Id] FROM WorkItems
WHERE [System.WorkItemType] IN ('Bug', 'Task') AND [System.AssignedTo] Ever '[email protected]' AND System.ChangedDate >= '01/01/2018'";

var client = VstsClient.Get(instanceName: "foo", accessToken: "secure token");
var urlBuilderFactory = new OnlineUrlBuilderFactory("foo");
var client = VstsClient.Get(urlBuilderFactory, accessToken: "secure token");
var items = await client.GetWorkItemsAsync(new WorkItemsQuery(query));
```
For OnPrem (TFS) versions use `OnPremUrlBuilderFactory` instead of `OnlineUrlBuilderFactory`

### Asp.Net Core
In the `Startup.cs` add `VstsNet` to the services collection
Expand All @@ -28,6 +30,12 @@ In the `Startup.cs` add `VstsNet` to the services collection
services.AddVstsNet(instanceName: "foo", accessToken: "secure token");
```

or if you have OnPrem (TFS) version:

```csharp
services.AddVstsNet(new Uri("https://foo.mydomain.com"), accessToken: "secure token");
```

Now you can consume Vsts client through DI:

```csharp
Expand Down
32 changes: 23 additions & 9 deletions src/VSTS.Net.AspNetCore/AspNetServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,45 @@ public static void AddVstsNet(this IServiceCollection services, string instanceN
}

public static void AddVstsNet(this IServiceCollection services, string instanceName, string accessToken, Action<VstsClientConfiguration> cfg)
{
services.AddSingleton<IVstsUrlBuilderFactory, OnlineUrlBuilderFactory>(ctx => new OnlineUrlBuilderFactory(instanceName));
RegisterServices(services, accessToken, cfg);
}

public static void AddVstsNet(this IServiceCollection services, Uri baseAddress, string accessToken)
{
AddVstsNet(services, baseAddress, accessToken, _ => { });
}

public static void AddVstsNet(this IServiceCollection services, Uri baseAddress, string accessToken, Action<VstsClientConfiguration> cfg)
{
services.AddSingleton<IVstsUrlBuilderFactory, OnPremUrlBuilderFactory>(ctx => new OnPremUrlBuilderFactory(baseAddress));
RegisterServices(services, accessToken, cfg);
}

private static void RegisterServices(IServiceCollection services, string accessToken, Action<VstsClientConfiguration> cfg)
{
var config = VstsClientConfiguration.Default;
cfg(config);
services.Add(new ServiceDescriptor(typeof(VstsClientConfiguration), config));

var httpClient = HttpClientUtil.Create(accessToken);
services.AddSingleton<IHttpClient, DefaultHttpClient>(ctx =>
{
var logger = ctx.GetService<ILogger<DefaultHttpClient>>();
return new DefaultHttpClient(httpClient, logger);
});

services.AddSingleton<IVstsClient, VstsClient>(ctx => CreateVstsClient(instanceName, ctx, config));
services.AddSingleton<IVstsWorkItemsClient, VstsClient>(ctx => GetVstsClient(instanceName, ctx));
services.AddSingleton<IVstsPullRequestsClient, VstsClient>(ctx => GetVstsClient(instanceName, ctx));
services.AddSingleton<IVstsClient, VstsClient>();
services.AddSingleton<IVstsWorkItemsClient, VstsClient>();
services.AddSingleton<IVstsPullRequestsClient, VstsClient>();
}

private static VstsClient CreateVstsClient(string instanceName, IServiceProvider ctx, VstsClientConfiguration config)
{
var client = ctx.GetService<IHttpClient>();
var logger = ctx.GetService<ILogger<VstsClient>>();
return new VstsClient(instanceName, client, config, logger);
}

private static VstsClient GetVstsClient(string instanceName, IServiceProvider ctx)
{
return ctx.GetService<IVstsClient>() as VstsClient;
return new VstsClient(new OnlineUrlBuilderFactory(instanceName), client, config, logger);
}
}
}
2 changes: 1 addition & 1 deletion src/VSTS.Net.AspNetCore/VSTS.Net.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>AspNetCore integration for Vsts.Net</Description>
<Authors>Andrii Snihyr</Authors>
<Version>0.2.1</Version>
<Version>0.2.2</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<Copyright>Copyright © Andrii Snihyr</Copyright>
<PackageLicenseUrl>https://github.com/BerserkerDotNet/VSTS.Net/blob/master/LICENSE</PackageLicenseUrl>
Expand Down
105 changes: 81 additions & 24 deletions src/VSTS.Net.Tests/AspNetServiceCollectionExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,24 @@ public void ShouldRegisterAllVstsClientInterfaces()
public void ShouldRegisterDefaultConfiguration()
{
_services.AddVstsNet(InstanceName, Token);
var clientRegistration = _services.Single(d => d.ServiceType == typeof(IVstsClient));
var mockServiceProvider = new Mock<IServiceProvider>();
var client = clientRegistration.ImplementationFactory(mockServiceProvider.Object) as VstsClient;
var configRegistration = _services.Single(d => d.ServiceType == typeof(VstsClientConfiguration));
var config = configRegistration.ImplementationInstance as VstsClientConfiguration;

client.Configuration.Should().NotBeNull();
client.Configuration.Should().BeEquivalentTo(VstsClientConfiguration.Default);
config.Should().NotBeNull();
config.Should().NotBeNull();
config.Should().BeEquivalentTo(VstsClientConfiguration.Default);
}

[Test]
public void ShouldRegisterDefaultConfigurationWithOnPrem()
{
_services.AddVstsNet(new Uri("https://foo.com"), Token);
var configRegistration = _services.Single(d => d.ServiceType == typeof(VstsClientConfiguration));
var config = configRegistration.ImplementationInstance as VstsClientConfiguration;

config.Should().NotBeNull();
config.Should().NotBeNull();
config.Should().BeEquivalentTo(VstsClientConfiguration.Default);
}

[Test]
Expand All @@ -65,35 +77,80 @@ public void ShouldRegisterCustomConfiguration()
const string expecetdWIAPIVersion = "3.0";
const int expectedWorkitemsBatchSize = 210;

_services.AddVstsNet(InstanceName, Token, config =>
_services.AddVstsNet(InstanceName, Token, cfg =>
{
config.WorkItemsApiVersion = expecetdWIAPIVersion;
config.PullRequestsApiVersion = expectedPRApiVersion;
config.WorkitemsBatchSize = expectedWorkitemsBatchSize;
cfg.WorkItemsApiVersion = expecetdWIAPIVersion;
cfg.PullRequestsApiVersion = expectedPRApiVersion;
cfg.WorkitemsBatchSize = expectedWorkitemsBatchSize;
});
var clientRegistration = _services.Single(d => d.ServiceType == typeof(IVstsClient));
var mockServiceProvider = new Mock<IServiceProvider>();
var client = clientRegistration.ImplementationFactory(mockServiceProvider.Object) as VstsClient;

client.Configuration.Should().NotBeNull();
client.Configuration.WorkItemsApiVersion.Should().Be(expecetdWIAPIVersion);
client.Configuration.PullRequestsApiVersion.Should().Be(expectedPRApiVersion);
client.Configuration.WorkitemsBatchSize.Should().Be(expectedWorkitemsBatchSize);

var configRegistration = _services.Single(d => d.ServiceType == typeof(VstsClientConfiguration));
var config = configRegistration.ImplementationInstance as VstsClientConfiguration;

config.Should().NotBeNull();
config.WorkItemsApiVersion.Should().Be(expecetdWIAPIVersion);
config.PullRequestsApiVersion.Should().Be(expectedPRApiVersion);
config.WorkitemsBatchSize.Should().Be(expectedWorkitemsBatchSize);
}

private void TestClientRegistration<T>()
[Test]
public void ShouldRegisterCustomConfigurationWithOnPrem()
{
const string expectedPRApiVersion = "5.2";
const string expecetdWIAPIVersion = "3.2";
const int expectedWorkitemsBatchSize = 123;

_services.AddVstsNet(new Uri("https://foo.com"), Token, cfg =>
{
cfg.WorkItemsApiVersion = expecetdWIAPIVersion;
cfg.PullRequestsApiVersion = expectedPRApiVersion;
cfg.WorkitemsBatchSize = expectedWorkitemsBatchSize;
});

var configRegistration = _services.Single(d => d.ServiceType == typeof(VstsClientConfiguration));
var config = configRegistration.ImplementationInstance as VstsClientConfiguration;

config.Should().NotBeNull();
config.WorkItemsApiVersion.Should().Be(expecetdWIAPIVersion);
config.PullRequestsApiVersion.Should().Be(expectedPRApiVersion);
config.WorkitemsBatchSize.Should().Be(expectedWorkitemsBatchSize);
}

[Test]
public void ShouldRegisterOnlineUrlFactoryBuilder()
{
_services.AddVstsNet(InstanceName, Token);
_services.Count.Should().BeGreaterThan(0);
_services.Should().ContainSingle(d => d.ServiceType == typeof(IVstsUrlBuilderFactory) && d.Lifetime == ServiceLifetime.Singleton);

var urlFactoryRegistration = _services.Single(d => d.ServiceType == typeof(IVstsUrlBuilderFactory));
var urlFactory = urlFactoryRegistration.ImplementationFactory(Mock.Of<IServiceProvider>()) as OnlineUrlBuilderFactory;

urlFactory.Should().NotBeNull();
}

[Test]
public void ShouldRegisterOnPremUrlFactoryBuilder()
{
var mockServiceProvider = new Mock<IServiceProvider>();
mockServiceProvider.Setup(p => p.GetService(It.Is<Type>(t => t == typeof(IVstsClient))))
.Returns(_services.Single(d => d.ServiceType == typeof(IVstsClient)).ImplementationFactory(mockServiceProvider.Object));
const string baseUrl = "https://foo.com";

_services.AddVstsNet(new Uri(baseUrl), Token);
_services.Count.Should().BeGreaterThan(0);
_services.Should().ContainSingle(d => d.ServiceType == typeof(IVstsUrlBuilderFactory) && d.Lifetime == ServiceLifetime.Singleton);

var urlFactoryRegistration = _services.Single(d => d.ServiceType == typeof(IVstsUrlBuilderFactory));
var urlFactory = urlFactoryRegistration.ImplementationFactory(Mock.Of<IServiceProvider>()) as OnPremUrlBuilderFactory;

urlFactory.Should().NotBeNull();
}

private void TestClientRegistration<T>()
{
_services.Count.Should().BeGreaterThan(0);
_services.Should().ContainSingle(d => d.ServiceType == typeof(T) && d.Lifetime == ServiceLifetime.Singleton);

var clientRegistration = _services.Single(d => d.ServiceType == typeof(T));
var client = clientRegistration.ImplementationFactory(mockServiceProvider.Object) as VstsClient;

client.Should().NotBeNull();
clientRegistration.ImplementationType.Should().Be(typeof(VstsClient));
}
}
}
3 changes: 2 additions & 1 deletion src/VSTS.Net.Tests/Types/BaseHttpClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public abstract class BaseHttpClientTests
public void SetUp()
{
_httpClientMock = new Mock<IHttpClient>();
_client = new VstsClient(InstanceName, _httpClientMock.Object, VstsClientConfiguration.Default, Mock.Of<ILogger<VstsClient>>());
var factory = new OnlineUrlBuilderFactory(InstanceName);
_client = new VstsClient(factory, _httpClientMock.Object, VstsClientConfiguration.Default, Mock.Of<ILogger<VstsClient>>());
var source = new CancellationTokenSource();
_cancellationToken = source.Token;
}
Expand Down
31 changes: 31 additions & 0 deletions src/VSTS.Net.Tests/UrlBuilder/UrlBuilderFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using FluentAssertions;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using VSTS.Net.Types;

namespace VSTS.Net.Tests.UrlBuilder
{
[TestFixture]
public class UrlBuilderFactoryTests
{
[Test]
public void OnlineUrlBuilderFactoryTest()
{
var factory = new OnlineUrlBuilderFactory(instance: "foo", subDomain: "bar");
var result = factory.Create().Build();

result.Should().Be($"https://foo.bar.visualstudio.com?api-version={Constants.CurrentWorkItemsApiVersion}");
}

[Test]
public void OnPremUrlBuilderFactoryTest()
{
var factory = new OnPremUrlBuilderFactory(new Uri("https://foo.com"));
var result = factory.Create().Build();

result.Should().Be($"https://foo.com?api-version={Constants.CurrentWorkItemsApiVersion}");
}
}
}
36 changes: 36 additions & 0 deletions src/VSTS.Net.Tests/UrlBuilder/UrlBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using FluentAssertions;
using NUnit.Framework;
using System;
using VSTS.Net.Types;

namespace VSTS.Net.Tests.UrlBuilder
{
[TestFixture]
public class UrlBuilderTests
{
[Test]
public void ComposeOnlineUrlWithInstanceName()
{
var result = VstsUrlBuilder.Create("foo").Build();
result.Should().Be($"https://foo.visualstudio.com?api-version={Constants.CurrentWorkItemsApiVersion}");
}

[Test]
public void ComposeOnlineUrlWihSubdomainAndInstanceName()
{
var result = VstsUrlBuilder.Create(instance: "foo", subDomain: "bar.buzz").Build();
result.Should().Be($"https://foo.bar.buzz.visualstudio.com?api-version={Constants.CurrentWorkItemsApiVersion}");
}

[TestCase("http://foo.mycompany.com")]
[TestCase("https://foo.mycompany.com")]
[TestCase("https://foo.mycompany.buzz.com")]
[TestCase("http://localhost:45789")]
[TestCase("http://localhost/")]
public void ComposeUrlWithCustomBaseAddress(string baseAddress)
{
var result = VstsUrlBuilder.Create(new Uri(baseAddress)).Build();
result.Should().Be($"{baseAddress.Trim('/')}?api-version={Constants.CurrentWorkItemsApiVersion}");
}
}
}
15 changes: 3 additions & 12 deletions src/VSTS.Net.Tests/WorkItems/VstsWorkItemsClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ public void ExecuteQueryShouldThrowArgumentNullExceptionIfQueryIsNull()
.Throw<ArgumentNullException>("query");
}

[Test]
public void ExecuteQueryShouldThrowArgumentNullExceptionIfConfigurationIsNull()
{
var client = new VstsClient(null, _httpClientMock.Object);
client.Awaiting(async c => await c.ExecuteQueryAsync(new WorkItemsQuery("dummy")))
.Should()
.Throw<ArgumentNullException>("configuration");
}

[Test]
public void ExecuteQueryShouldThrowArgumentExceptionIfQueryIsEmpty()
{
Expand Down Expand Up @@ -259,7 +250,7 @@ public async Task GetWorkItemUpdatesShouldReturnEmptyListIfResponseNull()
private bool VerifyWiqlQueryUrl(string url)
{
var expectedUrl = $"https://{InstanceName}.visualstudio.com/_apis/wit/wiql?api-version={Constants.CurrentWorkItemsApiVersion}";
return string.Equals(url, expectedUrl, StringComparison.Ordinal);
return string.Equals(url, expectedUrl, StringComparison.OrdinalIgnoreCase);
}

private bool VerifyWorkItemsUrl(string url, IEnumerable<string> fields, IEnumerable<int> ids)
Expand All @@ -268,13 +259,13 @@ private bool VerifyWorkItemsUrl(string url, IEnumerable<string> fields, IEnumera
var idsString = string.Join(',', ids);
var expectedUrl = $"https://{InstanceName}.visualstudio.com/_apis/wit/workitems?ids={idsString}&fields={fieldsString}&api-version={Constants.CurrentWorkItemsApiVersion}";

return string.Equals(url, expectedUrl, StringComparison.Ordinal);
return string.Equals(url, expectedUrl, StringComparison.OrdinalIgnoreCase);
}

private bool VerifyUpdatesUrl(string url, int workitemId)
{
var expectedUrl = $"https://{InstanceName}.visualstudio.com/_apis/wit/workitems/{workitemId}/updates?api-version={Constants.CurrentWorkItemsApiVersion}";
return string.Equals(url, expectedUrl, StringComparison.Ordinal);
return string.Equals(url, expectedUrl, StringComparison.OrdinalIgnoreCase);
}
}
}
10 changes: 10 additions & 0 deletions src/VSTS.Net/Interfaces/IVstsUrlBuilderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using VSTS.Net.Types;

namespace VSTS.Net.Interfaces
{
public interface IVstsUrlBuilderFactory
{
VstsUrlBuilder Create();
}
}
20 changes: 20 additions & 0 deletions src/VSTS.Net/Types/OnPremUrlBuilderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using VSTS.Net.Interfaces;

namespace VSTS.Net.Types
{
public class OnPremUrlBuilderFactory : IVstsUrlBuilderFactory
{
private readonly Uri _baseUri;

public OnPremUrlBuilderFactory(Uri baseUri)
{
_baseUri = baseUri;
}

public VstsUrlBuilder Create()
{
return VstsUrlBuilder.Create(_baseUri);
}
}
}
Loading

0 comments on commit 107276c

Please sign in to comment.