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