Skip to content

Commit

Permalink
Several improvements (#442)
Browse files Browse the repository at this point in the history
- Partial AOT support
- Change from MVC to Minimal API
- Change from Newtonsoft.Json to System.Text.Json
- Ability to configure the packages filter
- Add REST API tests
  • Loading branch information
bdovaz authored Dec 11, 2024
1 parent ca9dff9 commit 9b6b519
Show file tree
Hide file tree
Showing 37 changed files with 920 additions and 536 deletions.
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG TARGETARCH
WORKDIR /app

RUN mkdir -p src/UnityNuGet && mkdir -p src/UnityNuGet.Server && mkdir -p src/UnityNuGet.Tests
RUN mkdir -p src/UnityNuGet && mkdir -p src/UnityNuGet.Server && mkdir -p src/UnityNuGet.Tests && mkdir -p src/UnityNuGet.Server.Tests

COPY src/Directory.Build.props src/Directory.Build.props
COPY src/Directory.Packages.props src/Directory.Packages.props
COPY src/*.sln src
COPY src/UnityNuGet/*.csproj src/UnityNuGet
COPY src/UnityNuGet.Server/*.csproj src/UnityNuGet.Server
COPY src/UnityNuGet.Tests/*.csproj src/UnityNuGet.Tests
COPY src/UnityNuGet.Server.Tests/*.csproj src/UnityNuGet.Server.Tests
RUN dotnet restore src -a $TARGETARCH

COPY . ./
RUN dotnet publish src -a $TARGETARCH -c Release -o /app/src/out
RUN dotnet publish src/UnityNuGet.Server -a $TARGETARCH -c Release -o /app/src/out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0
Expand Down
9 changes: 5 additions & 4 deletions examples/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ services:
build: ../..
environment:
- Registry:RootHttpUrl=http://localhost:5000/ # Server Url to build the absolute path to the package.
- Registry:Filter= # Filter in regex format so that only the indicated packages are processed.
- Registry:UnityScope=org.custom # Packages prefix, default is "org.nuget" but it can be modified to be able to have several containers with different prefixes and to be able to add several scope registries.
- Registry:MinimumUnityVersion=2020.1 # Minimum version of Unity required to install packages, default is "2019.1".
- Registry:PackageNameNuGetPostFix= (Custom NuGet) # Suffix of the package title, useful in case of having several containers and several scope registries, default is " (NuGet)".
- Registry:RegistryFilePath=custom_registry.json # Path to the file (relative or absolute) where the packages registry file will be stored, default is "registry.json".
- Registry:RootPersistentFolder=custom_unity_packages # Path to the folder (relative or absolute) where the packages cache will be stored, default is "unity_packages".
- Registry:RegistryFilePath=/data/registry.json # Path to the file (relative or absolute) where the packages registry file will be stored, default is "registry.json".
- Registry:RootPersistentFolder=/data/unity_packages # Path to the folder (relative or absolute) where the packages cache will be stored, default is "unity_packages".
- Registry:UpdateInterval=00:01:00 # Packages update interval, default is "00:10:00" (10 minutes).
- Logging:LogLevel:Default=Information
ports:
- 5000:8080
volumes:
- ./registry.json:/app/custom_registry.json # Override the package registry to be able to add or remove packages.
- ./unity_packages:/app/custom_unity_packages # Map the folder with the packages cache.
- ./registry.json:/data/registry.json # Override the package registry to be able to add or remove packages.
- ./unity_packages:/data/unity_packages # Map the folder with the packages cache.
- ./NuGet.Config:/root/.nuget/NuGet/NuGet.Config # Override Nuget.config file with repository information. This file can be used to configure a custom NuGet repository: https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file
4 changes: 4 additions & 0 deletions examples/docker/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"listed": true,
"version": "4.4.0"
},
"System.Linq.Async": {
"listed": true,
"version": "4.0.0"
},
"System.Memory": {
"listed": true,
"version": "4.5.0"
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
Expand Down
201 changes: 201 additions & 0 deletions src/UnityNuGet.Server.Tests/ApiControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using UnityNuGet.Npm;

namespace UnityNuGet.Server.Tests
{
public class ApiControllerTests
{
private readonly UnityNuGetWebApplicationFactory _webApplicationFactory;

public ApiControllerTests()
{
_webApplicationFactory = new UnityNuGetWebApplicationFactory();
}

[OneTimeTearDown]
public void OneTimeTearDown()
{
_webApplicationFactory.Dispose();
}

[Test]
public async Task Home_Success()
{
using HttpClient client = _webApplicationFactory.CreateDefaultClient();

HttpResponseMessage response = await client.GetAsync("/");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Found));
Assert.That(response.Headers.Location, Is.EqualTo(new Uri("/-/all", UriKind.Relative)));

string responseContent = await response.Content.ReadAsStringAsync();

Assert.That(responseContent, Is.Empty);
}

[Test]
public async Task GetAll_Success()
{
using HttpClient httpClient = _webApplicationFactory.CreateDefaultClient();

await WaitForInitialization(_webApplicationFactory.Services);

HttpResponseMessage response = await httpClient.GetAsync("/-/all");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

string responseContent = await response.Content.ReadAsStringAsync();

NpmPackageListAllResponse npmPackageListAllResponse = JsonSerializer.Deserialize(responseContent, UnityNugetJsonSerializerContext.Default.NpmPackageListAllResponse)!;

Assert.That(npmPackageListAllResponse.Packages, Has.Count.EqualTo(1));

Assert.Multiple(() =>
{
string packageName = $"org.nuget.{UnityNuGetWebApplicationFactory.PackageName.ToLowerInvariant()}";

Assert.That(npmPackageListAllResponse.Packages.ContainsKey(packageName), Is.True);
Assert.That(npmPackageListAllResponse.Packages[packageName].Name, Is.EqualTo(packageName));
Assert.That(npmPackageListAllResponse.Packages[packageName].Description, Is.Not.Null);
Assert.That(npmPackageListAllResponse.Packages[packageName].Author, Is.Not.Null);
});
}

[Test]
public async Task GetPackage_NotFound()
{
using HttpClient httpClient = _webApplicationFactory.CreateDefaultClient();

await WaitForInitialization(_webApplicationFactory.Services);

HttpResponseMessage response = await httpClient.GetAsync($"/InvalidPackageName");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

string responseContent = await response.Content.ReadAsStringAsync();

NpmError npmError = JsonSerializer.Deserialize(responseContent, UnityNugetJsonSerializerContext.Default.NpmError)!;

Assert.That(npmError.Error, Is.EqualTo("not_found"));
}

[Test]
public async Task GetPackage_Success()
{
using HttpClient httpClient = _webApplicationFactory.CreateDefaultClient();

await WaitForInitialization(_webApplicationFactory.Services);

string packageName = $"org.nuget.{UnityNuGetWebApplicationFactory.PackageName.ToLowerInvariant()}";

HttpResponseMessage response = await httpClient.GetAsync($"/{packageName}");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

string responseContent = await response.Content.ReadAsStringAsync();

NpmPackage npmPackage = JsonSerializer.Deserialize(responseContent, UnityNugetJsonSerializerContext.Default.NpmPackage)!;

Assert.Multiple(() =>
{
Assert.That(npmPackage.Id, Is.EqualTo(packageName));
Assert.That(npmPackage.Revision, Is.Not.Null);
Assert.That(npmPackage.Name, Is.EqualTo(packageName));
Assert.That(npmPackage.License, Is.Not.Null);
Assert.That(npmPackage.Description, Is.Not.Null);
});
}

[Test]
[TestCase("org.nuget.newtonsoft.json", "InvalidFile")]
[TestCase("InvalidId", "org.nuget.newtonsoft.json-11.0.1.tgz")]
[TestCase("org.nuget.newtonsoft.json", "org.nuget.newtonsoft.json_11.0.1.tgz")]
[TestCase("org.nuget.newtonsoft.json", "org.nuget.newtonsoft.json-11.0.1.InvalidExtension")]
public async Task DownloadPackage_NotFound(string id, string file)
{
using HttpClient httpClient = _webApplicationFactory.CreateDefaultClient();

await WaitForInitialization(_webApplicationFactory.Services);

HttpResponseMessage response = await httpClient.GetAsync($"/{id}/-/{file}");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

string responseContent = await response.Content.ReadAsStringAsync();

NpmError npmError = JsonSerializer.Deserialize(responseContent, UnityNugetJsonSerializerContext.Default.NpmError)!;

Assert.That(npmError.Error, Is.EqualTo("not_found"));
}

[Test]
public async Task DownloadPackage_Head_Success()
{
using HttpClient httpClient = _webApplicationFactory.CreateDefaultClient();

await WaitForInitialization(_webApplicationFactory.Services);

string packageName = $"org.nuget.{UnityNuGetWebApplicationFactory.PackageName.ToLowerInvariant()}";

HttpRequestMessage httpRequestMessage = new()
{
RequestUri = new Uri($"/{packageName}/-/{packageName}-11.0.1.tgz", UriKind.Relative),
Method = HttpMethod.Head
};

HttpResponseMessage response = await httpClient.SendAsync(httpRequestMessage);

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

byte[] responseContent = await response.Content.ReadAsByteArrayAsync();

Assert.Multiple(() =>
{
Assert.That(responseContent, Is.Empty);

Assert.That(response.Content.Headers.ContentType!.MediaType, Is.EqualTo("application/octet-stream"));
Assert.That(response.Content.Headers.ContentLength, Is.GreaterThan(0));
});
}

[Test]
public async Task DownloadPackage_Get_Success()
{
using HttpClient httpClient = _webApplicationFactory.CreateDefaultClient();

await WaitForInitialization(_webApplicationFactory.Services);

string packageName = $"org.nuget.{UnityNuGetWebApplicationFactory.PackageName.ToLowerInvariant()}";

HttpResponseMessage response = await httpClient.GetAsync($"/{packageName}/-/{packageName}-11.0.1.tgz");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

byte[] responseContent = await response.Content.ReadAsByteArrayAsync();

Assert.Multiple(() =>
{
Assert.That(responseContent, Is.Not.Empty);

Assert.That(response.Content.Headers.ContentType!.MediaType, Is.EqualTo("application/octet-stream"));
Assert.That(response.Content.Headers.ContentLength, Is.GreaterThan(0));
});
}

private static async Task WaitForInitialization(IServiceProvider serviceProvider)
{
RegistryCacheSingleton registryCacheSingleton = serviceProvider.GetRequiredService<RegistryCacheSingleton>();

while (registryCacheSingleton.Instance == null)
{
await Task.Delay(25);
}
}
}
}
24 changes: 24 additions & 0 deletions src/UnityNuGet.Server.Tests/UnityNuGet.Server.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="nunit" />
<PackageReference Include="NUnit.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\UnityNuGet.Server\UnityNuGet.Server.csproj" />
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions src/UnityNuGet.Server.Tests/UnityNuGetWebApplicationFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace UnityNuGet.Server.Tests
{
internal class UnityNuGetWebApplicationFactory : WebApplicationFactory<Program>
{
public const string PackageName = "Newtonsoft.Json";

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);

builder.ConfigureAppConfiguration(builder =>
{
builder.AddInMemoryCollection(new Dictionary<string, string?>
{
{ WebHostDefaults.ServerUrlsKey, "http://localhost" }
});
});

builder.ConfigureServices(services =>
{
services.Configure<RegistryOptions>(options =>
{
options.Filter = PackageName;
});
});
}
}
}
Loading

0 comments on commit 9b6b519

Please sign in to comment.