diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 5ff93eca..324e5b58 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,7 +1,7 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
-name: Run Unittests on PR
+name: Run tests on PR
on:
push:
@@ -26,6 +26,9 @@ jobs:
- name: Build
run: dotnet build --no-restore
working-directory: ./src
- - name: Test
- run: dotnet test --no-build --verbosity normal
+ - name: Run unit tests
+ run: dotnet test --no-build --verbosity normal --minimum-expected-tests 1 -- --filter "TestCategory!=Integration" --ignore-exit-code 8
+ working-directory: ./src
+ - name: Run integration tests
+ run: dotnet test --no-build --verbosity normal --minimum-expected-tests 1 -- --filter "TestCategory=Integration" --ignore-exit-code 8
working-directory: ./src
diff --git a/src/PackageUploader.IntegrationTest/Fixtures/SyntheticPackageFile.cs b/src/PackageUploader.IntegrationTest/Fixtures/SyntheticPackageFile.cs
new file mode 100644
index 00000000..5329b323
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/Fixtures/SyntheticPackageFile.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace PackageUploader.IntegrationTest.Fixtures;
+
+/// Creates tiny, self-deleting synthetic package files for upload-path tests (not valid game content).
+internal sealed class SyntheticPackageFile : IDisposable
+{
+ public string Path { get; }
+
+ public long SizeInBytes { get; }
+
+ private SyntheticPackageFile(string path, long sizeInBytes)
+ {
+ Path = path;
+ SizeInBytes = sizeInBytes;
+ }
+
+ public static SyntheticPackageFile Create(long sizeInBytes = 64 * 1024, string extension = ".xvc", int seed = 1)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(sizeInBytes);
+
+ var path = System.IO.Path.Combine(
+ System.IO.Path.GetTempPath(),
+ $"pkguploader-it-{Guid.NewGuid():N}{extension}");
+
+ var random = new Random(seed);
+ const int bufferSize = 8 * 1024;
+ var buffer = new byte[bufferSize];
+
+ using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None))
+ {
+ long remaining = sizeInBytes;
+ while (remaining > 0)
+ {
+ int chunk = (int)Math.Min(bufferSize, remaining);
+ random.NextBytes(buffer.AsSpan(0, chunk));
+ stream.Write(buffer, 0, chunk);
+ remaining -= chunk;
+ }
+ }
+
+ return new SyntheticPackageFile(path, sizeInBytes);
+ }
+
+ public void Dispose()
+ {
+ try
+ {
+ if (File.Exists(Path))
+ {
+ File.Delete(Path);
+ }
+ }
+ catch (Exception)
+ {
+ // Best-effort cleanup; a leaked temp file must never fail a test.
+ }
+ }
+}
diff --git a/src/PackageUploader.IntegrationTest/Infrastructure/FakeAccessTokenProvider.cs b/src/PackageUploader.IntegrationTest/Infrastructure/FakeAccessTokenProvider.cs
new file mode 100644
index 00000000..1399be3f
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/Infrastructure/FakeAccessTokenProvider.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using PackageUploader.ClientApi.Client.Ingestion.TokenProvider;
+using PackageUploader.ClientApi.Client.Ingestion.TokenProvider.Models;
+
+namespace PackageUploader.IntegrationTest.Infrastructure;
+
+/// Test that returns a static fake token so the mock suite needs no real credentials.
+internal sealed class FakeAccessTokenProvider : IAccessTokenProvider
+{
+ public const string FakeToken = "fake-integration-test-token";
+
+ public Task GetTokenAsync(CancellationToken ct) =>
+ Task.FromResult(new IngestionAccessToken
+ {
+ AccessToken = FakeToken,
+ ExpiresOn = DateTimeOffset.UtcNow.AddHours(1),
+ });
+}
diff --git a/src/PackageUploader.IntegrationTest/Infrastructure/IntegrationTestBase.cs b/src/PackageUploader.IntegrationTest/Infrastructure/IntegrationTestBase.cs
new file mode 100644
index 00000000..bdfa8573
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/Infrastructure/IntegrationTestBase.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace PackageUploader.IntegrationTest.Infrastructure;
+
+/// Base class for integration tests; applies the Integration category and provides a host factory.
+[TestCategory(Category)]
+public abstract class IntegrationTestBase
+{
+ public const string Category = "Integration";
+
+ private protected static PackageUploaderTestHost CreateHost(
+ Action? configureIngestion = null) => new(configureIngestion);
+}
diff --git a/src/PackageUploader.IntegrationTest/Infrastructure/MockHttpMessageHandler.cs b/src/PackageUploader.IntegrationTest/Infrastructure/MockHttpMessageHandler.cs
new file mode 100644
index 00000000..18defbf6
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/Infrastructure/MockHttpMessageHandler.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+
+namespace PackageUploader.IntegrationTest.Infrastructure;
+
+/// In-process HTTP handler that returns scripted responses and records requests, backing the mock integration suite.
+internal sealed class MockHttpMessageHandler : HttpMessageHandler
+{
+ private readonly List _responders = [];
+ private readonly List _received = [];
+ private readonly Lock _receivedLock = new();
+
+ public IReadOnlyList ReceivedRequests
+ {
+ get
+ {
+ lock (_receivedLock)
+ {
+ return _received.ToArray();
+ }
+ }
+ }
+
+ public MockHttpMessageHandler When(HttpMethod method, string pathContains,
+ Func respond)
+ {
+ ArgumentNullException.ThrowIfNull(method);
+ ArgumentNullException.ThrowIfNull(pathContains);
+ ArgumentNullException.ThrowIfNull(respond);
+
+ _responders.Add(new Responder(method, pathContains, respond));
+ return this;
+ }
+
+ public MockHttpMessageHandler WhenJson(HttpMethod method, string pathContains, string json,
+ HttpStatusCode status = HttpStatusCode.OK) =>
+ When(method, pathContains, _ => new HttpResponseMessage(status)
+ {
+ Content = new StringContent(json, Encoding.UTF8, "application/json"),
+ });
+
+ protected override async Task SendAsync(HttpRequestMessage request,
+ CancellationToken cancellationToken)
+ {
+ string? body = request.Content is null
+ ? null
+ : await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+
+ lock (_receivedLock)
+ {
+ _received.Add(new RecordedRequest(
+ request.Method,
+ request.RequestUri!,
+ CloneHeaders(request.Headers),
+ body));
+ }
+
+ var responder = _responders.FirstOrDefault(r => r.Matches(request));
+ if (responder is null)
+ {
+ // Return a non-transient 4xx so a missing stub fails fast: the Ingestion pipeline's Polly
+ // policy retries on >=500, which would otherwise turn a missing stub into slow retries.
+ return new HttpResponseMessage(HttpStatusCode.BadRequest)
+ {
+ RequestMessage = request,
+ Content = new StringContent(
+ $"No mock responder registered for {request.Method} {request.RequestUri}",
+ Encoding.UTF8, "text/plain"),
+ };
+ }
+
+ var response = responder.Respond(request);
+ response.RequestMessage ??= request;
+ return response;
+ }
+
+ private static IReadOnlyDictionary CloneHeaders(HttpRequestHeaders headers)
+ {
+ var clone = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var header in headers)
+ {
+ clone[header.Key] = string.Join(", ", header.Value);
+ }
+ return clone;
+ }
+
+ private sealed class Responder(HttpMethod method, string pathContains,
+ Func respond)
+ {
+ public bool Matches(HttpRequestMessage request) =>
+ request.Method == method &&
+ request.RequestUri is not null &&
+ request.RequestUri.PathAndQuery.Contains(pathContains, StringComparison.OrdinalIgnoreCase);
+
+ public HttpResponseMessage Respond(HttpRequestMessage request) => respond(request);
+ }
+}
+
+/// Snapshot of a request observed by .
+internal sealed record RecordedRequest(
+ HttpMethod Method,
+ Uri Uri,
+ IReadOnlyDictionary Headers,
+ string? Body);
diff --git a/src/PackageUploader.IntegrationTest/Infrastructure/PackageUploaderTestHost.cs b/src/PackageUploader.IntegrationTest/Infrastructure/PackageUploaderTestHost.cs
new file mode 100644
index 00000000..9a3bc6f3
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/Infrastructure/PackageUploaderTestHost.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using PackageUploader.ClientApi;
+using PackageUploader.ClientApi.Client.Ingestion.TokenProvider;
+
+namespace PackageUploader.IntegrationTest.Infrastructure;
+
+/// Composes the real with the Ingestion network handler and access-token provider replaced by test doubles.
+internal sealed class PackageUploaderTestHost : IDisposable
+{
+ private const string IngestionHttpClientName = "IIngestionHttpClient";
+
+ private readonly ServiceProvider _provider;
+ private readonly IServiceScope _scope;
+
+ public MockHttpMessageHandler IngestionHandler { get; }
+
+ public IPackageUploaderService Service { get; }
+
+ public PackageUploaderTestHost(
+ Action? configureIngestion = null,
+ string ingestionBaseAddress = "https://ingestion.test.local/")
+ {
+ IngestionHandler = new MockHttpMessageHandler();
+ configureIngestion?.Invoke(IngestionHandler);
+
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["IngestionConfig:BaseAddress"] = ingestionBaseAddress,
+ })
+ .Build();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(configuration);
+ services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
+
+ services.AddPackageUploaderService(IngestionExtensions.AuthenticationMethod.Default);
+
+ services.RemoveAll();
+ services.AddScoped();
+
+ services.AddHttpClient(IngestionHttpClientName)
+ .ConfigurePrimaryHttpMessageHandler(() => IngestionHandler);
+
+ // IPackageUploaderService and the Ingestion auth handler are scoped; resolve them from an
+ // explicit scope (with scope validation on) rather than the root provider.
+ _provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true });
+ _scope = _provider.CreateScope();
+ Service = _scope.ServiceProvider.GetRequiredService();
+ }
+
+ public void Dispose()
+ {
+ _scope.Dispose();
+ _provider.Dispose();
+ }
+}
diff --git a/src/PackageUploader.IntegrationTest/PackageUploader.IntegrationTest.csproj b/src/PackageUploader.IntegrationTest/PackageUploader.IntegrationTest.csproj
new file mode 100644
index 00000000..85e2569e
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/PackageUploader.IntegrationTest.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net10.0
+ false
+ enable
+ enable
+ true
+ true
+ true
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/src/PackageUploader.IntegrationTest/SmokeTest.cs b/src/PackageUploader.IntegrationTest/SmokeTest.cs
new file mode 100644
index 00000000..5a83a4fb
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/SmokeTest.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using PackageUploader.IntegrationTest.Infrastructure;
+using System.Net.Http;
+
+namespace PackageUploader.IntegrationTest;
+
+///
+/// Smoke test that validates the integration project is discovered, builds, and that the mock
+/// harness routes a public service call through the real pipeline to the mock handler.
+///
+[TestClass]
+public sealed class SmokeTest : IntegrationTestBase
+{
+ [TestMethod]
+ public async Task TestHost_RoutesProductLookup_ThroughMockHandlerWithFakeAuth()
+ {
+ using var host = CreateHost(mock =>
+ mock.WhenJson(HttpMethod.Get, "/products/", "{\"id\":\"smoke-test-product\"}"));
+
+ var product = await host.Service.GetProductByProductIdAsync("smoke-test-product", TestContext.CancellationToken);
+
+ Assert.IsNotNull(product);
+ Assert.AreEqual(1, host.IngestionHandler.ReceivedRequests.Count);
+
+ var request = host.IngestionHandler.ReceivedRequests[0];
+ Assert.IsTrue(request.Headers.ContainsKey("Authorization"));
+ StringAssert.Contains(request.Headers["Authorization"], FakeAccessTokenProvider.FakeToken);
+ }
+
+ public TestContext TestContext { get; set; } = null!;
+}
diff --git a/src/PackageUploader.IntegrationTest/packages.lock.json b/src/PackageUploader.IntegrationTest/packages.lock.json
new file mode 100644
index 00000000..2bf55d70
--- /dev/null
+++ b/src/PackageUploader.IntegrationTest/packages.lock.json
@@ -0,0 +1,617 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "coverlet.collector": {
+ "type": "Direct",
+ "requested": "[10.0.0, )",
+ "resolved": "10.0.0",
+ "contentHash": "WFejCcOUR6k8UYyDnnR6Gk+obFYMsWrZuNqPJnsVFGVhpPSN0y20D4qbdKJnXinYGx9PQ397Hf9TnU1NBST8vA=="
+ },
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.5.2, )",
+ "resolved": "18.5.2",
+ "contentHash": "UNcGLx9pVtlXF8MPDR8KDp+/OKKNIJjpzwRyZSt609TSGvaD8mtuQMb5GKZvhMucPp0a5Juvn3kxXDceQZWmAg==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.3",
+ "Microsoft.Extensions.DependencyModel": "8.0.2",
+ "Microsoft.Testing.Platform": "2.1.0"
+ }
+ },
+ "Microsoft.Testing.Extensions.TrxReport": {
+ "type": "Direct",
+ "requested": "[2.2.2, )",
+ "resolved": "2.2.2",
+ "contentHash": "iEp69l8C0OlEnqUgZVoh621PrFIbaIbhjShUkW9pgPwH1GGLawLbi7cW1wyzLxZLI3jVSuKqV/JbSFz8Ael7Kg==",
+ "dependencies": {
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.2",
+ "Microsoft.Testing.Platform": "2.2.2"
+ }
+ },
+ "Moq": {
+ "type": "Direct",
+ "requested": "[4.20.72, )",
+ "resolved": "4.20.72",
+ "contentHash": "EA55cjyNn8eTNWrgrdZJH5QLFp2L43oxl1tlkoYUKIE9pRwL784OWiTXeCV5ApS+AMYEAlt7Fo03A2XfouvHmQ==",
+ "dependencies": {
+ "Castle.Core": "5.1.1"
+ }
+ },
+ "MSTest.TestAdapter": {
+ "type": "Direct",
+ "requested": "[4.2.2, )",
+ "resolved": "4.2.2",
+ "contentHash": "gMKNPoBnnlYM1DY+zAxJP05LDgXNHkjqxj6QQsm/O71nZh5BJ2SzsaTaQBQhXlu/HjzQ2CCbnMgufU13kYIpVA==",
+ "dependencies": {
+ "MSTest.TestFramework": "4.2.2",
+ "Microsoft.Testing.Extensions.VSTestBridge": "2.2.2",
+ "Microsoft.Testing.Platform.MSBuild": "2.2.2"
+ }
+ },
+ "MSTest.TestFramework": {
+ "type": "Direct",
+ "requested": "[4.2.2, )",
+ "resolved": "4.2.2",
+ "contentHash": "IGjOt2kE6NxIgWYcM40DYSzCFaajLe6wHEICPRBnCqj1K4f9HrBLMPo4PE4mM/uKHNgDBvhvj/t1bXenUcQKqQ==",
+ "dependencies": {
+ "MSTest.Analyzers": "4.2.2"
+ }
+ },
+ "Azure.Core": {
+ "type": "Transitive",
+ "resolved": "1.54.0",
+ "contentHash": "m6hHbx1q9+GCBZ5A9ykzFylPdTwscX2APH7PlnqV+yu+DH3RRtuIDJMRqdU17cMyinv0hCPofpegoyQ6qWPW7g==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "10.0.3",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.3",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.3",
+ "Microsoft.Identity.Client": "4.83.1",
+ "Microsoft.Identity.Client.Extensions.Msal": "4.83.1",
+ "System.ClientModel": "1.10.0",
+ "System.Memory.Data": "10.0.3"
+ }
+ },
+ "Azure.Identity": {
+ "type": "Transitive",
+ "resolved": "1.21.0",
+ "contentHash": "GeFv8sGwRKvDKwI2WFy8r0mhmlxEVZg24Sit2NogTjiSO8RVjllWM65OT6e1sKjOvG8V74y7hAbaELUUPjZQSw==",
+ "dependencies": {
+ "Azure.Core": "1.53.0"
+ }
+ },
+ "Azure.Storage.Blobs": {
+ "type": "Transitive",
+ "resolved": "12.27.0",
+ "contentHash": "zI5rg1tTtnA8T2g2/21l+1iIUdDjpEQQ0FI1BabJVEQJ1JUyTQKrc41eNabAHs0SBHprl6pu/6OqIMK9Ve+4tQ==",
+ "dependencies": {
+ "Azure.Core": "1.50.0",
+ "Azure.Storage.Common": "12.26.0"
+ }
+ },
+ "Azure.Storage.Common": {
+ "type": "Transitive",
+ "resolved": "12.26.0",
+ "contentHash": "XaT6CDcSshZb7KaCTwc6m4EouZbLBg7ciOEpsJSdJCvkNsZJQCvPKw7V5TtXno19AA1NpwtsZriYque8mzbQVg==",
+ "dependencies": {
+ "Azure.Core": "1.50.0",
+ "System.IO.Hashing": "10.0.1"
+ }
+ },
+ "Castle.Core": {
+ "type": "Transitive",
+ "resolved": "5.1.1",
+ "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==",
+ "dependencies": {
+ "System.Diagnostics.EventLog": "6.0.0"
+ }
+ },
+ "Microsoft.ApplicationInsights": {
+ "type": "Transitive",
+ "resolved": "2.23.0",
+ "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw=="
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "10.0.3",
+ "contentHash": "TV62UsrJZPX6gbt3c4WrtXh7bmaDIcMqf9uft1cc4L6gJXOU07hDGEh+bFQh/L2Az0R1WVOkiT66lFqS6G2NmA=="
+ },
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.3",
+ "contentHash": "bhwzJfzyiJM0nXJyNB7Y9OfsEXyxLdDBHG99soIp5JjnPydwkOaBdRCtRtWgQh3noSLi2cSIZ/wpbHNNE9knxQ=="
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "3lNjglxfFxOzI9zG+3HSg/YSGqo//8Fqw6u6iuIamZb4JCorbA3JLaeWOpfKTAPi2UJwaispOXWx14dUqcGz4A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "qbZLvLsoTdArSloEnSxs21P781YUmwVmHc5NJPQD/ezAreQ7884z+6QfAZVKi86WAZtzx83jK2uC4itxOM44gQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "64dimvyyKk0dbUbrLg/YCv4ugJ4sVz2aXLwfvZwR1EC4tJqW9ru/oVRcXwoJRa2lQGXtYtlpk4maWOeIb48tQw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "YqVIICoIdl0016wkeO2WQS+uEbEXbUhMLKdC5rZNl1X3nu59F+nwaAHdHjq/4OK+Cx31DYmNUSFh+MUot8qSDw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Json": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "8.0.2",
+ "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "l+smp1qPlU0OUXD0OGfdp7OUFrbdq7ZaP5T7m2WpfZ4RFKD7iG73BAT7tjSMxNmbSXkhAn1jYHOAqzYG1r9sNg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "uJ9JP677y+uy+C0vtaSfi7XXgFAdz8DhU3M9lwwIXDfQKcyQ0yxM9DVYa0NXDtdVTYA2eBUtVFZ8LY0GCdeE/w==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "teioDgVpi8L186wUfrXQV1YuBt6lCSPmFZiMZo53+FZxHFjOV+f4GXo4LXgJ273Mku9//AdXWVjk9J7eJP6inw==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "zhgWg/i0ECj5v0jLFBSZHplvc5ygCI91DR4nne+BP4XAKF5ycz0pEKnFiTw8C1jCABJEZsnBZh6pXAvn71kFmw==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "NTUspqB+vH9g4wAD6KPOBx01xqYuKXR/cHXm449zpbq1GqfjdAxBmg7eJXrNsPw7SKwIdT2cJ05GxYVvc+lvsA=="
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "M/vBpfWcschvS2EUeq7cHfscsxabiGTptXwV7GeSueovGiSoNjyo1j5PMcWuOAAQrRW3nRqxZk8NeumrmpzUBg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.7",
+ "Microsoft.Extensions.Configuration.CommandLine": "10.0.7",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.7",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Json": "10.0.7",
+ "Microsoft.Extensions.Configuration.UserSecrets": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Diagnostics": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.7",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.7",
+ "Microsoft.Extensions.Logging.Console": "10.0.7",
+ "Microsoft.Extensions.Logging.Debug": "10.0.7",
+ "Microsoft.Extensions.Logging.EventLog": "10.0.7",
+ "Microsoft.Extensions.Logging.EventSource": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "5s8d6qC6EA8UOI4wR/+zlsq7SXttJMRb9d7zvVZ7+bE3CQEfVtC9ITUDCommm87R1zzj6WJBbCnztuIJXnP3DA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "1wbd+RPhRo3hJKNJhdGEO5ls0LGe55Ho4BUjlFtRUrWxDVVBd7g0Ydq9fbNy86pmvx/j7AGcSPo7YNCo1IRI6Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Diagnostics": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Http.Polly": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "pcUsPoqMHvOp+QJsLA/Hlg/W+IBnAoUXKEBc7FqMcY0sUez15DOKXtbEo81TvHL9xwjWQcF3ZMayNpcvpI7Bqg==",
+ "dependencies": {
+ "Microsoft.Extensions.Http": "10.0.7",
+ "Polly": "7.2.4",
+ "Polly.Extensions.Http": "3.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "hOeRIQ63GkgiYCB/MIFp+LQs8aXpJXpB55t6Aj37ab7t2/6WeFcPXxYM9hdy/o5tffzwf8mhqzLJP6mjGYCxjw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "tIEcQ2gvERrH2KiCjdsVcHGhXt9lIsuDStfOIeZWr7/fP8IXhGiYfx0/80PNI7WPO2IYuFtlZLSlnTS8+/Mchw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "7BBnoGF37USiu7j434put9mDp7EjdlNDIZsR4vHfC1FbLZeLqiWjgJbeEtF0p59Ryqt8AtraHawf0ZKbe5jibg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "DA++Es6v6W0HfrOrw+K8WyN6jNnZHp640PDdEvl8yfeVmgflKdn6vSSFvufNUSOuY+M2ZaSUgfY+jUKtNpXcCw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "Y6DSt/JZApunYWKqTtqbdsR6iqAvHx3D0tavbNJ1rnC24MUpF+3XO/VKgFi+9PFqMyvQ2GHBBGb8H3cLSw7rDg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "1C8eTuxF6BLncNSJ1HCfmaBcjpUSqQDPlBVdYTlet9oldHTPpNh9iatxSJLs8TOqdp/FOpH+nSLdBve7fu9mTQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "System.Diagnostics.EventLog": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "YWfndnDX1jVMGCN8d5T+rO+BO8sDw6BkYlUk0BYui+WP7+HhlWx8QLdA4yUDjrkGVb3AQxIWWEPVKw5Nnfj5GQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Options.DataAnnotations": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "KWepqdSD4PxhFvVh3mckkvJ03u3q/VChkr6nT3nf5mm2XBk8ojxt2E4It0RMblb3GE7hJ0zQzFzxGKL0d6TfXA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw=="
+ },
+ "Microsoft.Identity.Client": {
+ "type": "Transitive",
+ "resolved": "4.83.1",
+ "contentHash": "jOLIrZ3cynoqHLLO1cXplFFabrhrMEYs/EuKHvmCyrOm1axqiVFT6nCSnHxk7w5+d2BeQfCdM12Yf/0X7OeS1g==",
+ "dependencies": {
+ "Microsoft.IdentityModel.Abstractions": "8.14.0"
+ }
+ },
+ "Microsoft.Identity.Client.Extensions.Msal": {
+ "type": "Transitive",
+ "resolved": "4.83.1",
+ "contentHash": "I3k4J4Hj4KbLEFanjeUzzDOVecukETaTgEkJ7h2pP/Yazs6SLp6TVUTo/Eo+ptPXMwvc+iX7rBFtMSUrA7R+Mg==",
+ "dependencies": {
+ "Microsoft.Identity.Client": "4.83.1",
+ "System.Security.Cryptography.ProtectedData": "4.5.0"
+ }
+ },
+ "Microsoft.IdentityModel.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.14.0",
+ "contentHash": "iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ=="
+ },
+ "Microsoft.Testing.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "2.2.2",
+ "contentHash": "qKRghdaDiC88N1s3LDJO7zW74QNZu/ErnTxuG7R9u9UORn6pTwdqbi7X+eY4UQb+7YV2gR2yz8eRelvOWQVxhA==",
+ "dependencies": {
+ "Microsoft.ApplicationInsights": "2.23.0",
+ "Microsoft.Testing.Platform": "2.2.2"
+ }
+ },
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": {
+ "type": "Transitive",
+ "resolved": "2.2.2",
+ "contentHash": "MuOC3Be70FPysaPxaO0f3GFoXU49UwnKCVDWfFrOZ93h955KZ6MKiJ6vwt/2r4e1wkLDoJFbkQzi/MNbpe4oXQ==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "2.2.2"
+ }
+ },
+ "Microsoft.Testing.Extensions.VSTestBridge": {
+ "type": "Transitive",
+ "resolved": "2.2.2",
+ "contentHash": "dyo49lXzY3seyfEgv7qrkIqdvrMAjdJjmY0VDPE//UPK89c+65cqQm8m+FO5XbRpr8gB6AUi5KCRbEl1eRlwQA==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "18.3.0",
+ "Microsoft.Testing.Extensions.Telemetry": "2.2.2",
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.2",
+ "Microsoft.Testing.Platform": "2.2.2"
+ }
+ },
+ "Microsoft.Testing.Platform": {
+ "type": "Transitive",
+ "resolved": "2.2.2",
+ "contentHash": "9mUsTOri0aVqBX7/EJwqVJxVwdOzGUVJqK1H2EMfIl9xxJuSdqhfAlJbukl/iNugvi4+cmQs/LI8PLTDUT9P1A=="
+ },
+ "Microsoft.Testing.Platform.MSBuild": {
+ "type": "Transitive",
+ "resolved": "2.2.2",
+ "contentHash": "acgkTLYA8C39oe5b5ISmydBshR0XO6v8z3/CXAsLmPQ3xAiomHuPoTAgY28tjQLcwPZOu4GX034BXWvmsVpzIg==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "2.2.2"
+ }
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "AEIEX2aWdPO9XbtR96eBaJxmXRD9vaI9uQ1T/JbPEKlTAZwYx0ZrMzKyULMdh/HH9Sg03kXCoN7LszQ90o6nPQ=="
+ },
+ "MSTest.Analyzers": {
+ "type": "Transitive",
+ "resolved": "4.2.2",
+ "contentHash": "0VUx09Q6MdPlTCG+xTqEoXIrjr32F1Ya5EI/hfQdRSczZh61AWWtCdGXRCe3DDfUUbPVvFBZTJcrlTT1Cv25Dg=="
+ },
+ "Polly": {
+ "type": "Transitive",
+ "resolved": "7.2.4",
+ "contentHash": "bw00Ck5sh6ekduDE3mnCo1ohzuad946uslCDEENu3091+6UKnBuKLo4e+yaNcCzXxOZCXWY2gV4a35+K1d4LDA=="
+ },
+ "Polly.Contrib.WaitAndRetry": {
+ "type": "Transitive",
+ "resolved": "1.1.1",
+ "contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA=="
+ },
+ "Polly.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "3.0.0",
+ "contentHash": "drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==",
+ "dependencies": {
+ "Polly": "7.1.0"
+ }
+ },
+ "System.ClientModel": {
+ "type": "Transitive",
+ "resolved": "1.10.0",
+ "contentHash": "lBEWs54F5Y5pZ9hC+8z4S/X76957ex+DPk7WecRHlbIHtrPfbRMMlOgI3iDn4Jpb3bSxvBnKaaHoD59auFjlBA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.3",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.3",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.3",
+ "System.Memory.Data": "10.0.3"
+ }
+ },
+ "System.CommandLine": {
+ "type": "Transitive",
+ "resolved": "2.0.7",
+ "contentHash": "ih4yNLLF2Ebz85xJJBaPeddLa4d1AekYId7Y1g8oSsEaBHHd/CtyeBJ+tDvQadqeXz7i591K5ry/td+4aaHnQA=="
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "WbmDLeTPYhEzXhvYVioTVn/D1XX6bovyny9n5p8Zxtf03+eY385RB818teZm6n+fA63iZNvng0/Np4tLuhkMhQ=="
+ },
+ "System.Interactive.Async": {
+ "type": "Transitive",
+ "resolved": "7.0.1",
+ "contentHash": "oL1iox7sAJL8i+muGzVMQjDB0axQgOoT5CkwYdap8cQJMkWDWMRErNqhOcZkn31+aKr/uCfgMEdhUARCU4G7gg=="
+ },
+ "System.IO.Hashing": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "Dy6ULPb2S0GmNndjKrEIpfibNsc8+FTOoZnqygtFDuyun8vWboQbfMpQtKUXpgTxokR5E4zFHETpNnGfeWY6NA=="
+ },
+ "System.Linq.Async": {
+ "type": "Transitive",
+ "resolved": "7.0.1",
+ "contentHash": "gwQtBHVY/WgqWgAYSe4JspXR+f1FvMbVIW4ixsJpGV/Kj8Nun3zp1ajIdvCWfmac8ektJGVLiJ/OR8JU9nZnMg==",
+ "dependencies": {
+ "System.Interactive.Async": "7.0.1"
+ }
+ },
+ "System.Memory.Data": {
+ "type": "Transitive",
+ "resolved": "10.0.3",
+ "contentHash": "MaGhRfGunmrj/nHjtsi9XkhlYJ/ERGWrbA+BiSKNtGnAjc9XlG5EhAvak6VRcX5LYzPF6pBO8nJ613dTgzabig=="
+ },
+ "System.Security.Cryptography.ProtectedData": {
+ "type": "Transitive",
+ "resolved": "4.5.0",
+ "contentHash": "wLBKzFnDCxP12VL9ANydSYhk59fC4cvOr9ypYQLPnAj48NQIhqnjdD2yhP8yEKyBJEjERWS9DisKL7rX5eU25Q=="
+ },
+ "PackageUploader": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Binder": "[10.0.7, )",
+ "Microsoft.Extensions.Configuration.Json": "[10.0.7, )",
+ "Microsoft.Extensions.Hosting": "[10.0.7, )",
+ "Microsoft.Extensions.Options.DataAnnotations": "[10.0.7, )",
+ "PackageUploader.ClientApi": "[1.0.0, )",
+ "PackageUploader.FileLogger": "[1.0.0, )",
+ "System.CommandLine": "[2.0.7, )"
+ }
+ },
+ "packageuploader.clientapi": {
+ "type": "Project",
+ "dependencies": {
+ "Azure.Core": "[1.54.0, )",
+ "Azure.Identity": "[1.21.0, )",
+ "Azure.Storage.Blobs": "[12.27.0, )",
+ "Microsoft.Extensions.Configuration.Binder": "[10.0.7, )",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.7, )",
+ "Microsoft.Extensions.Http": "[10.0.7, )",
+ "Microsoft.Extensions.Http.Polly": "[10.0.7, )",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "[10.0.7, )",
+ "Microsoft.Extensions.Options.DataAnnotations": "[10.0.7, )",
+ "Polly.Contrib.WaitAndRetry": "[1.1.1, )",
+ "System.Linq.Async": "[7.0.1, )"
+ }
+ },
+ "packageuploader.filelogger": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Logging": "[10.0.7, )",
+ "Microsoft.Extensions.Logging.Configuration": "[10.0.7, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/PackageUploader.sln b/src/PackageUploader.sln
index aad6a3ac..d58c7843 100644
--- a/src/PackageUploader.sln
+++ b/src/PackageUploader.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
-VisualStudioVersion = 18.3.11527.330 d18.3
+VisualStudioVersion = 18.3.11527.330
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PackageUploader.Application", "PackageUploader.Application\PackageUploader.Application.csproj", "{30F60E9E-6744-45F3-A9CD-5E19D6E121E8}"
EndProject
@@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageUploader.UI.Test", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageUploader.Application.Test", "PackageUploader.Application.Test\PackageUploader.Application.Test.csproj", "{0D81241E-788D-E5AB-551B-EE8C34B20BA3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageUploader.IntegrationTest", "PackageUploader.IntegrationTest\PackageUploader.IntegrationTest.csproj", "{5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -115,6 +117,18 @@ Global
{0D81241E-788D-E5AB-551B-EE8C34B20BA3}.Release|x64.Build.0 = Release|Any CPU
{0D81241E-788D-E5AB-551B-EE8C34B20BA3}.Release|x86.ActiveCfg = Release|Any CPU
{0D81241E-788D-E5AB-551B-EE8C34B20BA3}.Release|x86.Build.0 = Release|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Debug|x64.Build.0 = Debug|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Debug|x86.Build.0 = Debug|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Release|x64.ActiveCfg = Release|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Release|x64.Build.0 = Release|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Release|x86.ActiveCfg = Release|Any CPU
+ {5F5C031C-2F8C-4953-AEAB-B3C6EE7C0A59}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE