From e23fd047b4baf3480ce93d7643a897732f960557 Mon Sep 17 00:00:00 2001
From: Doug Bunting <6431421+dougbu@users.noreply.github.com>
Date: Thu, 9 Sep 2021 17:11:31 -0700
Subject: [PATCH] Handle shortened JSON file in `dotnet openapi` (#36171)

* Handle shortened JSON file in `dotnet openapi`
  - #35767
  - an existing JSON file must be truncated
* !fixup! Address nits in changed file
  - take VS suggestions
* Stop skipping `dotnet openapi` tests
  - .NET SDKs and Visual Studio's `msbuild` avoid #32686 already
* !fixup! Address nits in `dotnet openapi` test files
  - take VS suggestions
* Extend `dotnet openapi refresh` tests
  - include regression test for #35767
---
 .../src/Commands/BaseCommand.cs               | 66 +++++++----------
 .../test/OpenApiAddFileTests.cs               | 28 ++++----
 .../test/OpenApiAddURLTests.cs                | 29 ++++----
 .../test/OpenApiRefreshTests.cs               | 72 ++++++++++++++++---
 .../test/OpenApiRemoveTests.cs                | 10 +--
 .../test/OpenApiTestBase.cs                   | 29 +++-----
 6 files changed, 124 insertions(+), 110 deletions(-)

diff --git a/src/Tools/Microsoft.dotnet-openapi/src/Commands/BaseCommand.cs b/src/Tools/Microsoft.dotnet-openapi/src/Commands/BaseCommand.cs
index 25e48d17990..fea939677f6 100644
--- a/src/Tools/Microsoft.dotnet-openapi/src/Commands/BaseCommand.cs
+++ b/src/Tools/Microsoft.dotnet-openapi/src/Commands/BaseCommand.cs
@@ -1,18 +1,13 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.Collections.Generic;
 using System.Diagnostics;
-using System.IO;
 using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Reflection;
 using System.Security.Cryptography;
 using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
 using Microsoft.Build.Evaluation;
 using Microsoft.DotNet.Openapi.Tools;
 using Microsoft.DotNet.Openapi.Tools.Internal;
@@ -46,9 +41,9 @@ namespace Microsoft.DotNet.OpenApi.Commands
 
             ProjectFileOption = Option("-p|--updateProject", "The project file update.", CommandOptionType.SingleValue);
 
-            if (Parent is Application)
+            if (Parent is Application application)
             {
-                WorkingDirectory = ((Application)Parent).WorkingDirectory;
+                WorkingDirectory = application.WorkingDirectory;
             }
             else
             {
@@ -89,10 +84,11 @@ namespace Microsoft.DotNet.OpenApi.Commands
         private Application GetApplication()
         {
             var parent = Parent;
-            while(!(parent is Application))
+            while(parent is not Application)
             {
                 parent = parent.Parent;
             }
+
             return (Application)parent;
         }
 
@@ -126,7 +122,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
             return new FileInfo(project);
         }
 
-        protected Project LoadProject(FileInfo projectFile)
+        protected static Project LoadProject(FileInfo projectFile)
         {
             var project = ProjectCollection.GlobalProjectCollection.LoadProject(
                 projectFile.FullName,
@@ -136,12 +132,12 @@ namespace Microsoft.DotNet.OpenApi.Commands
             return project;
         }
 
-        internal bool IsProjectFile(string file)
+        internal static bool IsProjectFile(string file)
         {
             return File.Exists(Path.GetFullPath(file)) && file.EndsWith(".csproj", StringComparison.Ordinal);
         }
 
-        internal bool IsUrl(string file)
+        internal static bool IsUrl(string file)
         {
             return Uri.TryCreate(file, UriKind.Absolute, out var _) && file.StartsWith("http", StringComparison.Ordinal);
         }
@@ -167,7 +163,8 @@ namespace Microsoft.DotNet.OpenApi.Commands
 
             if (sourceUrl != null)
             {
-                if (items.Any(i => string.Equals(i.GetMetadataValue(SourceUrlAttrName), sourceUrl)))
+                if (items.Any(
+                    i => string.Equals(i.GetMetadataValue(SourceUrlAttrName), sourceUrl, StringComparison.Ordinal)))
                 {
                     Warning.Write($"A reference to '{sourceUrl}' already exists in '{project.FullPath}'.");
                     return;
@@ -299,8 +296,8 @@ namespace Microsoft.DotNet.OpenApi.Commands
         /// <param name="retryCount"></param>
         private static async Task<IHttpResponseMessageWrapper> RetryRequest(
             Func<Task<IHttpResponseMessageWrapper>> retryBlock,
-            CancellationToken cancellationToken = default,
-            int retryCount = 60)
+            int retryCount = 60,
+            CancellationToken cancellationToken = default)
         {
             for (var retry = 0; retry < retryCount; retry++)
             {
@@ -331,7 +328,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
                     {
                         if (exception is HttpRequestException || exception is WebException)
                         {
-                            await Task.Delay(1 * 1000); //Wait for a while before retry.
+                            await Task.Delay(1 * 1000, cancellationToken); // Wait for a while before retry.
                         }
                     }
                 }
@@ -340,7 +337,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
             throw new OperationCanceledException("Failed to connect, retry limit exceeded.");
         }
 
-        private string GetUniqueFileName(string directory, string fileName, string extension)
+        private static string GetUniqueFileName(string directory, string fileName, string extension)
         {
             var uniqueName = fileName;
 
@@ -366,7 +363,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
             return uniqueName + extension;
         }
 
-        private string GetFileNameFromResponse(IHttpResponseMessageWrapper response, string url)
+        private static string GetFileNameFromResponse(IHttpResponseMessageWrapper response, string url)
         {
             var contentDisposition = response.ContentDisposition();
             string result;
@@ -396,22 +393,12 @@ namespace Microsoft.DotNet.OpenApi.Commands
                 else
                 {
                     var parts = uri.Host.Split('.');
-
-                    // There's no segment, use the domain name.
-                    string domain;
-                    switch (parts.Length)
+                    var domain = parts.Length switch
                     {
-                        case 1:
-                        case 2:
-                            // It's localhost if 1, no www if 2
-                            domain = parts.First();
-                            break;
-                        case 3:
-                            domain = parts[1];
-                            break;
-                        default:
-                            throw new NotImplementedException("We don't handle the case that the Host has more than three segments");
-                    }
+                        1 or 2 => parts.First(), // It's localhost or somewhere in an Intranet if 1; no www if 2.
+                        3 => parts[1],           // Grab XYZ in www.XYZ.domain.com or similar.
+                        _ => throw new NotImplementedException("We don't handle the case that the Host has more than three segments"),
+                    };
 
                     result = domain + DefaultExtension;
                 }
@@ -420,7 +407,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
             return result;
         }
 
-        internal CodeGenerator? GetCodeGenerator(CommandOption codeGeneratorOption)
+        internal static CodeGenerator? GetCodeGenerator(CommandOption codeGeneratorOption)
         {
             CodeGenerator? codeGenerator;
             if (codeGeneratorOption.HasValue())
@@ -435,7 +422,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
             return codeGenerator;
         }
 
-        internal void ValidateCodeGenerator(CommandOption codeGeneratorOption)
+        internal static void ValidateCodeGenerator(CommandOption codeGeneratorOption)
         {
             if (codeGeneratorOption.HasValue())
             {
@@ -494,7 +481,7 @@ namespace Microsoft.DotNet.OpenApi.Commands
 
         private static IDictionary<string, string> GetServicePackages(CodeGenerator? type)
         {
-            CodeGenerator generator = type ?? CodeGenerator.NSwagCSharp;
+            var generator = type ?? CodeGenerator.NSwagCSharp;
             var name = Enum.GetName(typeof(CodeGenerator), generator);
             var attributes = typeof(Program).Assembly.GetCustomAttributes<OpenApiDependencyAttribute>();
 
@@ -513,10 +500,8 @@ namespace Microsoft.DotNet.OpenApi.Commands
 
         private static byte[] GetHash(Stream stream)
         {
-            using (var algorithm = SHA256.Create())
-            {
-                return algorithm.ComputeHash(stream);
-            }
+            using var algorithm = SHA256.Create();
+            return algorithm.ComputeHash(stream);
         }
 
         private async Task WriteToFileAsync(Stream content, string destinationPath, bool overwrite)
@@ -572,12 +557,13 @@ namespace Microsoft.DotNet.OpenApi.Commands
 
                 // Create or overwrite the destination file.
                 reachedCopy = true;
-                using var fileStream = new FileStream(destinationPath, FileMode.OpenOrCreate, FileAccess.Write);
+                using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write);
                 fileStream.Seek(0, SeekOrigin.Begin);
                 if (content.CanSeek)
                 {
                     content.Seek(0, SeekOrigin.Begin);
                 }
+
                 await content.CopyToAsync(fileStream);
             }
             catch (Exception ex)
diff --git a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddFileTests.cs b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddFileTests.cs
index 4898225e314..30a147cd0ac 100644
--- a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddFileTests.cs
+++ b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddFileTests.cs
@@ -1,13 +1,9 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.IO;
 using System.Text.RegularExpressions;
-using System.Threading.Tasks;
 using System.Xml;
-using Microsoft.AspNetCore.Testing;
 using Microsoft.DotNet.OpenApi.Tests;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Microsoft.DotNet.OpenApi.Add.Tests
@@ -16,18 +12,18 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
     {
         public OpenApiAddFileTests(ITestOutputHelper output) : base(output) { }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_Empty_ShowsHelp()
         {
             var app = GetApplication();
-            var run = app.Execute(new string[] { });
+            var run = app.Execute(Array.Empty<string>());
 
             AssertNoErrors(run);
 
             Assert.Contains("Usage: openapi ", _output.ToString());
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_NoProjectExists()
         {
             var app = GetApplication();
@@ -38,7 +34,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Equal(1, run);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_ExplicitProject_Missing()
         {
             var app = GetApplication();
@@ -50,7 +46,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Equal(1, run);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_Add_Empty_ShowsHelp()
         {
             var app = GetApplication();
@@ -61,7 +57,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Contains("Usage: openapi add", _output.ToString());
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_Add_File_Empty_ShowsHelp()
         {
             var app = GetApplication();
@@ -72,7 +68,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Contains("Usage: openapi ", _output.ToString());
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_ReuseItemGroup()
         {
             var project = CreateBasicProject(withOpenApi: true);
@@ -101,7 +97,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Same(openApiRefs[0].ParentNode, openApiRefs[1].ParentNode);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_Add_File_EquivilentPaths()
         {
             var project = CreateBasicProject(withOpenApi: true);
@@ -126,7 +122,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Single(openApiRefs);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_NSwagTypeScript()
         {
             var project = CreateBasicProject(withOpenApi: true);
@@ -146,7 +142,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Contains($"<OpenApiReference Include=\"{nswagJsonFile}\" CodeGenerator=\"NSwagTypeScript\" />", content);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_FromJson()
         {
             var project = CreateBasicProject(withOpenApi: true);
@@ -166,7 +162,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Contains($"<OpenApiReference Include=\"{nswagJsonFile}\"", content);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_File_UseProjectOption()
         {
             var project = CreateBasicProject(withOpenApi: true);
@@ -186,7 +182,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Contains($"<OpenApiReference Include=\"{nswagJsonFIle}\"", content);
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_MultipleTimes_OnlyOneReference()
         {
             var project = CreateBasicProject(withOpenApi: true);
diff --git a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddURLTests.cs b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddURLTests.cs
index 954e5daad08..03efe5a6230 100644
--- a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddURLTests.cs
+++ b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddURLTests.cs
@@ -1,13 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.IO;
 using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Testing;
 using Microsoft.DotNet.OpenApi.Tests;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Microsoft.DotNet.OpenApi.Add.Tests
@@ -16,7 +11,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
     {
         public OpenApiAddURLTests(ITestOutputHelper output) : base(output){ }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url_WithContentDisposition()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -48,7 +43,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenAPI_Add_Url_NoContentDisposition()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -81,7 +76,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenAPI_Add_Url_NoExtension_AssumesJson()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -114,7 +109,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url_NoSegment()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -147,7 +142,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -179,7 +174,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url_SameName_UniqueFile()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -239,7 +234,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url_NSwagCSharp()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -271,7 +266,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url_NSwagTypeScript()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -303,7 +298,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_Url_OutputFile()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -335,7 +330,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Add_URL_FileAlreadyExists_Fail()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -393,7 +388,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public void OpenApi_Add_URL_MultipleTimes_OnlyOneReference()
         {
             var project = CreateBasicProject(withOpenApi: false);
@@ -419,7 +414,7 @@ namespace Microsoft.DotNet.OpenApi.Add.Tests
             Assert.Single(Regex.Matches(content, escapedApiRef));
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenAPi_Add_URL_InvalidUrl()
         {
             var project = CreateBasicProject(withOpenApi: false);
diff --git a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRefreshTests.cs b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRefreshTests.cs
index 4ce1bf49f9b..4a6faf6c397 100644
--- a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRefreshTests.cs
+++ b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRefreshTests.cs
@@ -1,12 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
 using Microsoft.DotNet.OpenApi.Tests;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Microsoft.DotNet.OpenApi.Refresh.Tests
@@ -15,24 +10,24 @@ namespace Microsoft.DotNet.OpenApi.Refresh.Tests
     {
         public OpenApiRefreshTests(ITestOutputHelper output) : base(output) { }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Refresh_Basic()
         {
             CreateBasicProject(withOpenApi: false);
 
+            // Add <OpenApiReference/> to the project. Ignore initial filename.json content.
             var app = GetApplication();
             var run = app.Execute(new[] { "add", "url", FakeOpenApiUrl });
 
             AssertNoErrors(run);
 
+            // File will grow after the refresh.
             var expectedJsonPath = Path.Combine(_tempDir.Root, "filename.json");
-            var json = await File.ReadAllTextAsync(expectedJsonPath);
-            json += "trash";
-            await File.WriteAllTextAsync(expectedJsonPath, json);
+            await File.WriteAllTextAsync(expectedJsonPath, "trash");
 
             var firstWriteTime = File.GetLastWriteTime(expectedJsonPath);
 
-            Thread.Sleep(TimeSpan.FromSeconds(1));
+            await Task.Delay(TimeSpan.FromSeconds(1));
 
             app = GetApplication();
             run = app.Execute(new[] { "refresh", FakeOpenApiUrl });
@@ -41,6 +36,63 @@ namespace Microsoft.DotNet.OpenApi.Refresh.Tests
 
             var secondWriteTime = File.GetLastWriteTime(expectedJsonPath);
             Assert.True(firstWriteTime < secondWriteTime, $"File wasn't updated! {firstWriteTime} {secondWriteTime}");
+            Assert.Equal(Content, await File.ReadAllTextAsync(expectedJsonPath), ignoreLineEndingDifferences: true);
+        }
+
+        // Regression test for #35767 scenario.
+        [Fact]
+        public async Task OpenApi_Refresh_MuchShorterFile()
+        {
+            CreateBasicProject(withOpenApi: false);
+
+            // Add <OpenApiReference/> to the project. Ignore initial filename.json content.
+            var app = GetApplication();
+            var run = app.Execute(new[] { "add", "url", FakeOpenApiUrl });
+
+            AssertNoErrors(run);
+
+            // File will shrink after the refresh.
+            var expectedJsonPath = Path.Combine(_tempDir.Root, "filename.json");
+            await File.WriteAllTextAsync(expectedJsonPath, PackageUrlContent);
+
+            var firstWriteTime = File.GetLastWriteTime(expectedJsonPath);
+
+            await Task.Delay(TimeSpan.FromSeconds(1));
+
+            app = GetApplication();
+            run = app.Execute(new[] { "refresh", FakeOpenApiUrl });
+
+            AssertNoErrors(run);
+
+            var secondWriteTime = File.GetLastWriteTime(expectedJsonPath);
+            Assert.True(firstWriteTime < secondWriteTime, $"File wasn't updated! {firstWriteTime} {secondWriteTime}");
+            Assert.Equal(Content, await File.ReadAllTextAsync(expectedJsonPath), ignoreLineEndingDifferences: true);
+        }
+
+        [Fact]
+        public async Task OpenApi_Refresh_UnchangedFile()
+        {
+            CreateBasicProject(withOpenApi: false);
+
+            // Add <OpenApiReference/> to the project and write the filename.json file.
+            var app = GetApplication();
+            var run = app.Execute(new[] { "add", "url", FakeOpenApiUrl });
+
+            AssertNoErrors(run);
+
+            var expectedJsonPath = Path.Combine(_tempDir.Root, "filename.json");
+            var firstWriteTime = File.GetLastWriteTime(expectedJsonPath);
+
+            await Task.Delay(TimeSpan.FromSeconds(1));
+
+            app = GetApplication();
+            run = app.Execute(new[] { "refresh", FakeOpenApiUrl });
+
+            AssertNoErrors(run);
+
+            var secondWriteTime = File.GetLastWriteTime(expectedJsonPath);
+            Assert.Equal(firstWriteTime, secondWriteTime);
+            Assert.Equal(Content, await File.ReadAllTextAsync(expectedJsonPath));
         }
     }
 }
diff --git a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRemoveTests.cs b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRemoveTests.cs
index 7b98e7f9570..3530e039b57 100644
--- a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRemoveTests.cs
+++ b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiRemoveTests.cs
@@ -1,12 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.IO;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Testing;
 using Microsoft.DotNet.OpenApi.Tests;
 using Microsoft.Extensions.Tools.Internal;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Microsoft.DotNet.OpenApi.Remove.Tests
@@ -15,7 +11,7 @@ namespace Microsoft.DotNet.OpenApi.Remove.Tests
     {
         public OpenApiRemoveTests(ITestOutputHelper output) : base(output) { }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Remove_File()
         {
             var nswagJsonFile = "openapi.json";
@@ -60,7 +56,7 @@ namespace Microsoft.DotNet.OpenApi.Remove.Tests
             Assert.False(File.Exists(Path.Combine(_tempDir.Root, nswagJsonFile)));
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Remove_ViaUrl()
         {
             _tempDir
@@ -148,7 +144,7 @@ namespace Microsoft.DotNet.OpenApi.Remove.Tests
             }
         }
 
-        [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
+        [Fact]
         public async Task OpenApi_Remove_Multiple()
         {
             var nswagJsonFile = "openapi.json";
diff --git a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs
index 8aaefa51b93..bc7409f2cd7 100644
--- a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs
+++ b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs
@@ -1,17 +1,12 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.Collections.Generic;
-using System.IO;
 using System.Net;
 using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Text;
-using System.Threading.Tasks;
 using Microsoft.DotNet.Openapi.Tools;
 using Microsoft.Extensions.Tools.Internal;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Microsoft.DotNet.OpenApi.Tests
@@ -92,7 +87,7 @@ namespace Microsoft.DotNet.OpenApi.Tests
                 _tempDir.Root, wrapper, _output, _error);
         }
 
-        private IDictionary<string, Tuple<string, ContentDispositionHeaderValue>> DownloadMock()
+        private static IDictionary<string, Tuple<string, ContentDispositionHeaderValue>> DownloadMock()
         {
             var noExtension = new ContentDispositionHeaderValue("attachment");
             noExtension.Parameters.Add(new NameValueHeaderValue("filename", "filename"));
@@ -113,7 +108,7 @@ namespace Microsoft.DotNet.OpenApi.Tests
 
         protected void AssertNoErrors(int appExitCode)
         {
-            Assert.True(string.IsNullOrEmpty(_error.ToString()), $"Threw error: {_error.ToString()}");
+            Assert.True(string.IsNullOrEmpty(_error.ToString()), $"Threw error: {_error}");
             Assert.Equal(0, appExitCode);
         }
 
@@ -124,7 +119,7 @@ namespace Microsoft.DotNet.OpenApi.Tests
         }
     }
 
-    public class TestHttpClientWrapper : IHttpClientWrapper
+    public sealed class TestHttpClientWrapper : IHttpClientWrapper
     {
         private readonly IDictionary<string, Tuple<string, ContentDispositionHeaderValue>> _results;
 
@@ -143,7 +138,7 @@ namespace Microsoft.DotNet.OpenApi.Tests
             MemoryStream stream = null;
             if(result != null)
             {
-                byte[] byteArray = Encoding.ASCII.GetBytes(result.Item1);
+                var byteArray = Encoding.ASCII.GetBytes(result.Item1);
                 stream = new MemoryStream(byteArray);
             }
 
@@ -151,7 +146,7 @@ namespace Microsoft.DotNet.OpenApi.Tests
         }
     }
 
-    public class TestHttpResponseMessageWrapper : IHttpResponseMessageWrapper
+    public sealed class TestHttpResponseMessageWrapper : IHttpResponseMessageWrapper
     {
         public Task<Stream> Stream { get; }
 
@@ -159,17 +154,11 @@ namespace Microsoft.DotNet.OpenApi.Tests
 
         public bool IsSuccessCode()
         {
-            switch(StatusCode)
+            return StatusCode switch
             {
-                case HttpStatusCode.OK:
-                case HttpStatusCode.Created:
-                case HttpStatusCode.NoContent:
-                case HttpStatusCode.Accepted:
-                    return true;
-                case HttpStatusCode.NotFound:
-                default:
-                    return false;
-            }
+                HttpStatusCode.OK or HttpStatusCode.Created or HttpStatusCode.NoContent or HttpStatusCode.Accepted => true,
+                _ => false,
+            };
         }
 
         private readonly ContentDispositionHeaderValue _contentDisposition;
-- 
GitLab