diff --git a/source/Helpers/SourceFileWatcher.cs b/source/Helpers/SourceFileWatcher.cs
index d26b5c235a6b458734c224bda5ae2ec789618ed7..3129fcdd0dd346530297e6975fc98a67a5c8fd1c 100644
--- a/source/Helpers/SourceFileWatcher.cs
+++ b/source/Helpers/SourceFileWatcher.cs
@@ -44,7 +44,7 @@ public class SourceFileWatcher : IFileWatcher
fileWatcher = new FileSystemWatcher
{
Path = SourceAbsolutePath,
- NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
+ NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName,
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
diff --git a/source/Models/IFile.cs b/source/Models/IFile.cs
index d3f007c6ca9ef53ae2cbd08c7aeff5349b30b4e0..c7cf50fec6fb6f93e61343c7826fd68c80460f13 100644
--- a/source/Models/IFile.cs
+++ b/source/Models/IFile.cs
@@ -1,5 +1,7 @@
using System.IO;
-using Microsoft.AspNetCore.StaticFiles;
+using FolkerKinzel.MimeTypes;
+
+// using Microsoft.AspNetCore.StaticFiles;
using SuCoS.Helpers;
namespace SuCoS.Models;
@@ -42,18 +44,7 @@ public interface IFile
///
/// File MIME type.
///
- string MimeType
- {
- get
- {
- var provider = new FileExtensionContentTypeProvider();
- if (!provider.TryGetContentType(SourceFullPath, out var contentType))
- {
- contentType = "application/octet-stream";
- }
- return contentType;
- }
- }
+ string MimeType => MimeString.FromFileName(SourceFullPath) ?? "application/octet-stream";
///
/// File size in bytes.
diff --git a/source/Program.cs b/source/Program.cs
index d067a46651f1c4c968cbee67c4d888a55e462a94..a85f7da06f7b5bd0734ddf34d18c172241a5d5ff 100644
--- a/source/Program.cs
+++ b/source/Program.cs
@@ -115,7 +115,7 @@ public class Program
};
var serveCommand = new ServeCommand(serverOptions, logger, new SourceFileWatcher());
- await serveCommand.RunServer();
+ serveCommand.StartServer();
await Task.Delay(-1); // Wait forever.
},
sourceOption, draftOption, futureOption, expiredOption, verboseOption);
diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs
index eba8335bfd4785109d866453d6a841658998c4d5..e0b66684283d15f7001d41542a1ddcfd2ff3be2e 100644
--- a/source/ServeCommand.cs
+++ b/source/ServeCommand.cs
@@ -1,10 +1,10 @@
using System;
using System.IO;
+using System.Net;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
+using Microsoft.VisualBasic;
using Serilog;
using SuCoS.Helpers;
using SuCoS.Models.CommandLineOptions;
@@ -28,23 +28,6 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable
///
private readonly ServeOptions options;
- ///
- /// The global base URL for the server, combined with the port number.
- /// This is constructed in the ServeCommand constructor using the provided base URL and port number,
- /// and is subsequently used when starting or restarting the server.
- ///
- private readonly string baseURLGlobal;
-
- ///
- /// An instance of IWebHost, which represents the running web server.
- /// This instance is created every time the server starts or restarts and is used to manage
- /// the lifecycle of the server. It's nullable because there may be periods when there is no running server,
- /// such as during a restart operation or before the server has been initially started.
- ///
- private IWebHost? host;
-
- private IServerHandlers[]? handlers;
-
///
/// The FileSystemWatcher object that monitors the source directory for file changes.
/// When a change is detected, this triggers a server restart to ensure the served content
@@ -61,29 +44,37 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable
///
private Timer? debounceTimer;
- ///
- /// A boolean flag indicating whether a server restart is currently in progress.
- /// This is used to prevent overlapping restarts when file changes are detected in quick succession.
- ///
- private volatile bool restartInProgress;
-
///
/// A SemaphoreSlim used to ensure that server restarts due to file changes occur sequentially,
/// not concurrently. This is necessary because a restart involves stopping the current server
/// and starting a new one, which would not be thread-safe without some form of synchronization.
///
- private readonly SemaphoreSlim restartServerLock = new(1, 1);
+ // private readonly SemaphoreSlim restartServerLock = new(1, 1);
+ private Task lastRestartTask = Task.CompletedTask;
+
+ private HttpListener? listener;
+
+ private IServerHandlers[]? handlers;
private DateTime serverStartTime;
+ private Task? loop;
+
///
- /// Method to start the server explicitly
+ /// Constructor for the ServeCommand class.
///
- ///
- public async Task RunServer()
+ /// ServeOptions object specifying the serve options.
+ /// The logger instance. Injectable for testing
+ ///
+ public ServeCommand(ServeOptions options, ILogger logger, IFileWatcher fileWatcher) : base(options, logger)
{
- // Start the server!
- await StartServer("http://localhost", 1122);
+ this.options = options ?? throw new ArgumentNullException(nameof(options));
+ this.fileWatcher = fileWatcher ?? throw new ArgumentNullException(nameof(fileWatcher));
+
+ // Watch for file changes in the specified path
+ var SourceAbsolutePath = Path.GetFullPath(options.Source);
+ logger.Information("Watching for file changes in {SourceAbsolutePath}", SourceAbsolutePath);
+ fileWatcher.Start(SourceAbsolutePath, OnSourceFileChanged);
}
///
@@ -92,7 +83,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable
/// The base URL for the server.
/// The port number for the server.
/// A Task representing the asynchronous operation.
- private async Task StartServer(string baseURL, int port)
+ public void StartServer(string baseURL = baseURLDefault, int port = portDefault)
{
logger.Information("Starting server...");
@@ -108,99 +99,120 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable
new RegisteredPageRequest(site),
new RegisteredPageResourceRequest(site)
};
+ listener = new HttpListener();
+ listener.Prefixes.Add($"{baseURL}:{port}/");
+ listener.Start();
- host = new WebHostBuilder()
- .UseKestrel()
- .UseUrls(baseURLGlobal)
- .UseContentRoot(options.Source) // Set the content root path
- .UseEnvironment("Debug") // Set the hosting environment to Debug
- .Configure(app =>
- {
- app.Run(HandleRequest); // Call the custom method for handling requests
- })
- .Build();
-
- await host.StartAsync();
logger.Information("You site is live: {baseURL}:{port}", baseURL, port);
+ loop = Task.Run(async () =>
+ {
+ while (listener is not null && listener.IsListening)
+ {
+ try
+ {
+ var context = await listener.GetContextAsync();
+ await HandleRequest(context);
+ }
+ catch (HttpListenerException ex)
+ {
+ if (listener.IsListening)
+ {
+ logger.Error(ex, "Unexpected listener error.");
+ }
+ break;
+ }
+ catch (Exception ex)
+ {
+ if (listener.IsListening)
+ {
+ logger.Error(ex, "Error processing request.");
+ }
+ break;
+ }
+ }
+ });
}
///
public void Dispose()
{
- host?.Dispose();
+ listener?.Stop();
+ listener?.Close();
fileWatcher.Stop();
debounceTimer?.Dispose();
GC.SuppressFinalize(this);
}
- ///
- /// Constructor for the ServeCommand class.
- ///
- /// ServeOptions object specifying the serve options.
- /// The logger instance. Injectable for testing
- ///
- public ServeCommand(ServeOptions options, ILogger logger, IFileWatcher fileWatcher) : base(options, logger)
- {
- this.options = options ?? throw new ArgumentNullException(nameof(options));
- this.fileWatcher = fileWatcher ?? throw new ArgumentNullException(nameof(fileWatcher));
- baseURLGlobal = $"{baseURLDefault}:{portDefault}";
-
- // Watch for file changes in the specified path
- var SourceAbsolutePath = Path.GetFullPath(options.Source);
- logger.Information("Watching for file changes in {SourceAbsolutePath}", SourceAbsolutePath);
- fileWatcher.Start(SourceAbsolutePath, OnSourceFileChanged);
- }
-
///
/// Restarts the server asynchronously.
///
private async Task RestartServer()
{
- // Check if another restart is already in progress
- await restartServerLock.WaitAsync();
-
- try
+ await lastRestartTask.ContinueWith(async _ =>
{
- site = SiteHelper.Init(configFile, options, frontMatterParser, WhereParamsFilter, logger, stopwatch);
+ logger.Information($"Restarting server...");
- // Stop the server
- if (host != null)
+ if (listener != null && listener.IsListening)
{
- await host.StopAsync(TimeSpan.FromSeconds(1));
- host.Dispose();
+ listener.Stop();
+ listener.Close();
+
+ if (loop is not null)
+ {
+ // Wait for the loop to finish processing any ongoing requests.
+ await loop;
+ loop.Dispose();
+ }
}
- await StartServer("http://localhost", 1122);
- }
- finally
- {
- _ = restartServerLock.Release();
- }
+
+ // Reinitialize the site
+ site = SiteHelper.Init(configFile, options, frontMatterParser, WhereParamsFilter, logger, stopwatch);
+
+ StartServer(baseURLDefault, portDefault);
+ });
+
+ lastRestartTask = lastRestartTask.ContinueWith(t => t.Exception != null
+ ? throw t.Exception
+ : Task.CompletedTask);
}
///
/// Handles the HTTP request asynchronously.
///
/// The HttpContext representing the current request.
- private async Task HandleRequest(HttpContext context)
+ private async Task HandleRequest(HttpListenerContext context)
{
- var requestPath = context.Request.Path.Value;
- if (string.IsNullOrEmpty(Path.GetExtension(context.Request.Path.Value)) && requestPath.Length > 1)
- {
- requestPath = requestPath.TrimEnd('/');
- }
+ var requestPath = context.Request.Url?.AbsolutePath ?? string.Empty;
string? resultType = null;
if (handlers is not null)
{
- foreach (var item in handlers)
+ try
{
- if (item.Check(requestPath))
+ var response = new HttpListenerResponseWrapper(context.Response);
+ foreach (var item in handlers)
{
- resultType = await item.Handle(context, requestPath, serverStartTime);
+ if (!item.Check(requestPath))
+ {
+ continue;
+ }
+
+ try
+ {
+ resultType = await item.Handle(response, requestPath, serverStartTime);
+ }
+ catch (Exception ex)
+ {
+ logger.Debug(ex, "Error handling the request.");
+ }
break;
}
}
+ finally
+ {
+ context.Response.OutputStream.Close();
+ }
}
if (resultType is null)
@@ -211,10 +223,11 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable
logger.Debug("Request {type}\tfor {RequestPath}", resultType, requestPath);
}
- private static async Task HandleNotFoundRequest(HttpContext context)
+ private static async Task HandleNotFoundRequest(HttpListenerContext context)
{
context.Response.StatusCode = 404;
- await context.Response.WriteAsync("404 - File Not Found");
+ using var writer = new StreamWriter(context.Response.OutputStream);
+ await writer.WriteAsync("404 - File Not Found");
}
///
@@ -224,43 +237,16 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable
/// The FileSystemEventArgs containing information about the file change.
private void OnSourceFileChanged(object sender, FileSystemEventArgs e)
{
+ if (e.FullPath.Contains("\\.git\\", StringComparison.InvariantCulture)) return;
+
// File changes are firing multiple events in a short time.
// Debounce the event handler to prevent multiple events from firing in a short time
debounceTimer?.Dispose();
debounceTimer = new Timer(DebounceCallback, e, TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan);
}
- private void DebounceCallback(object? state)
- {
- if (state is not FileSystemEventArgs e)
- {
- return;
- }
- HandleFileChangeAsync(e).GetAwaiter().GetResult();
- }
-
- private async Task HandleFileChangeAsync(FileSystemEventArgs e)
+ private async void DebounceCallback(object? state)
{
- if (restartInProgress)
- {
- return;
- }
-
- logger.Information("File change detected: {FullPath}", e.FullPath);
-
- restartInProgress = true;
- try
- {
- await RestartServer();
- }
- catch (Exception ex)
- {
- logger.Error(ex, "Failed to restart server.");
- throw;
- }
- finally
- {
- restartInProgress = false;
- }
+ await RestartServer();
}
}
diff --git a/source/ServerHandlers/HttpListenerResponseWrapper.cs b/source/ServerHandlers/HttpListenerResponseWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5bd1cd697c36517ed0e2ec369ff1c7f52c7c2e1e
--- /dev/null
+++ b/source/ServerHandlers/HttpListenerResponseWrapper.cs
@@ -0,0 +1,38 @@
+using System.IO;
+using System.Net;
+
+namespace SuCoS.ServerHandlers;
+
+///
+/// Provides a wrapper for the .
+///
+public class HttpListenerResponseWrapper : IHttpListenerResponse
+{
+ private readonly HttpListenerResponse response;
+
+ ///
+ public Stream OutputStream => response.OutputStream;
+
+ ///
+ public string? ContentType
+ {
+ get => response.ContentType;
+ set => response.ContentType = value;
+ }
+
+ ///
+ public long ContentLength64
+ {
+ get => response.ContentLength64;
+ set => response.ContentLength64 = value;
+ }
+
+ ///
+ /// Wrap the HttpListenerResponse
+ ///
+ ///
+ public HttpListenerResponseWrapper(HttpListenerResponse response)
+ {
+ this.response = response;
+ }
+}
diff --git a/source/ServerHandlers/IHttpListenerResponse.cs b/source/ServerHandlers/IHttpListenerResponse.cs
new file mode 100644
index 0000000000000000000000000000000000000000..34f1a8228a00ad3518bd5bb29525268fa9a96b30
--- /dev/null
+++ b/source/ServerHandlers/IHttpListenerResponse.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace SuCoS.ServerHandlers;
+
+///
+/// Defines the contract for an HTTP listener response.
+///
+public interface IHttpListenerResponse
+{
+ ///
+ /// Gets the output stream for writing the response body.
+ ///
+ Stream OutputStream { get; }
+
+ ///
+ /// Gets or sets the content type of the response.
+ ///
+ string? ContentType { get; set; }
+
+ ///
+ /// Gets or sets the length of the content to be sent.
+ ///
+ long ContentLength64 { get; set; }
+}
diff --git a/source/ServerHandlers/IServerHandlers.cs b/source/ServerHandlers/IServerHandlers.cs
index 38887009030528fe97c885ebbd6a017a36a71272..a354f51cfec78e39c9685da578c6aab1ab85350a 100644
--- a/source/ServerHandlers/IServerHandlers.cs
+++ b/source/ServerHandlers/IServerHandlers.cs
@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
namespace SuCoS.ServerHandlers;
@@ -19,9 +18,9 @@ public interface IServerHandlers
///
/// Process the request
///
- ///
+ ///
///
///
///
- Task Handle(HttpContext context, string requestPath, DateTime serverStartTime);
+ Task Handle(IHttpListenerResponse response, string requestPath, DateTime serverStartTime);
}
diff --git a/source/ServerHandlers/PingRequests.cs b/source/ServerHandlers/PingRequests.cs
index 4ae76ff54e46b19677ed2efa86d57c4c38202788..74655850a09f20597d3ca45c1d3b2e3241b8a025 100644
--- a/source/ServerHandlers/PingRequests.cs
+++ b/source/ServerHandlers/PingRequests.cs
@@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
+using System.IO;
namespace SuCoS.ServerHandlers;
@@ -16,14 +16,17 @@ public class PingRequests : IServerHandlers
}
///
- public async Task Handle(HttpContext context, string requestPath, DateTime serverStartTime)
+ public async Task Handle(IHttpListenerResponse response, string requestPath, DateTime serverStartTime)
{
- if (context is null)
+ if (response is null)
{
- throw new ArgumentNullException(nameof(context));
+ throw new ArgumentNullException(nameof(response));
}
var content = serverStartTime.ToString("o");
- await context.Response.WriteAsync(content);
+
+ using var writer = new StreamWriter(response.OutputStream, leaveOpen: true);
+ await writer.WriteAsync(content);
+ await writer.FlushAsync();
return "ping";
}
diff --git a/source/ServerHandlers/RegisteredPageRequest.cs b/source/ServerHandlers/RegisteredPageRequest.cs
index b98870c6e461eb501f3c9e57a9a8b58b17456c04..ad6e48e3a269adccf93d7f7a1d1d7a8fcd97c3c8 100644
--- a/source/ServerHandlers/RegisteredPageRequest.cs
+++ b/source/ServerHandlers/RegisteredPageRequest.cs
@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
using SuCoS.Models;
namespace SuCoS.ServerHandlers;
@@ -34,18 +33,18 @@ public class RegisteredPageRequest : IServerHandlers
}
///
- public async Task Handle(HttpContext context, string requestPath, DateTime serverStartTime)
+ public async Task Handle(IHttpListenerResponse response, string requestPath, DateTime serverStartTime)
{
- if (context is null)
+ if (response is null)
{
- throw new ArgumentNullException(nameof(context));
+ throw new ArgumentNullException(nameof(response));
}
-
if (site.OutputReferences.TryGetValue(requestPath, out var output) && output is IPage page)
{
var content = page.CompleteContent;
content = InjectReloadScript(content);
- await context.Response.WriteAsync(content);
+ using var writer = new StreamWriter(response.OutputStream, leaveOpen: true);
+ await writer.WriteAsync(content);
return "dict";
}
else
diff --git a/source/ServerHandlers/RegisteredPageResourceRequest.cs b/source/ServerHandlers/RegisteredPageResourceRequest.cs
index 595c02e27771f3e0c9f2a43cafed5a69421e634a..c61183a2e66d582fb6e7d65f6d7f9a142d093f33 100644
--- a/source/ServerHandlers/RegisteredPageResourceRequest.cs
+++ b/source/ServerHandlers/RegisteredPageResourceRequest.cs
@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
using SuCoS.Models;
namespace SuCoS.ServerHandlers;
@@ -34,18 +33,18 @@ public class RegisteredPageResourceRequest : IServerHandlers
}
///
- public async Task Handle(HttpContext context, string requestPath, DateTime serverStartTime)
+ public async Task Handle(IHttpListenerResponse response, string requestPath, DateTime serverStartTime)
{
- if (context is null)
+ if (response is null)
{
- throw new ArgumentNullException(nameof(context));
+ throw new ArgumentNullException(nameof(response));
}
if (site.OutputReferences.TryGetValue(requestPath, out var output) && output is IResource resource)
{
- context.Response.ContentType = resource.MimeType;
+ response.ContentType = resource.MimeType;
await using var fileStream = new FileStream(resource.SourceFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- await fileStream.CopyToAsync(context.Response.Body);
+ await fileStream.CopyToAsync(response.OutputStream);
return "resource";
}
else
diff --git a/source/ServerHandlers/StaticFileRequest.cs b/source/ServerHandlers/StaticFileRequest.cs
index 9cf0e9100c9215c940fd635b47c37775a763638c..16e6a0b7eebe3a8df3a34c7569a458eb00a79d2f 100644
--- a/source/ServerHandlers/StaticFileRequest.cs
+++ b/source/ServerHandlers/StaticFileRequest.cs
@@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.StaticFiles;
+using System.Net;
+using FolkerKinzel.MimeTypes;
namespace SuCoS.ServerHandlers;
@@ -37,21 +37,22 @@ public class StaticFileRequest : IServerHandlers
}
///
- public async Task Handle(HttpContext context, string requestPath, DateTime serverStartTime)
+ public async Task Handle(IHttpListenerResponse response, string requestPath, DateTime serverStartTime)
{
if (requestPath is null)
{
throw new ArgumentNullException(nameof(requestPath));
}
-
- var fileAbsolutePath = Path.Combine(basePath, requestPath.TrimStart('/'));
- if (context is null)
+ if (response is null)
{
- throw new ArgumentNullException(nameof(context));
+ throw new ArgumentNullException(nameof(response));
}
- context.Response.ContentType = GetContentType(fileAbsolutePath!);
+
+ var fileAbsolutePath = Path.Combine(basePath, requestPath.TrimStart('/'));
+ response.ContentType = GetContentType(fileAbsolutePath!);
await using var fileStream = new FileStream(fileAbsolutePath!, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- await fileStream.CopyToAsync(context.Response.Body);
+ response.ContentLength64 = fileStream.Length;
+ await fileStream.CopyToAsync(response.OutputStream);
return inTheme ? "themeSt" : "static";
}
@@ -61,13 +62,6 @@ public class StaticFileRequest : IServerHandlers
///
/// The path of the file.
/// The content type of the file.
- private static string GetContentType(string filePath)
- {
- var provider = new FileExtensionContentTypeProvider();
- if (!provider.TryGetContentType(filePath, out var contentType))
- {
- contentType = "application/octet-stream";
- }
- return contentType ?? "application/octet-stream";
- }
+ private static string GetContentType(string filePath) =>
+ MimeString.FromFileName(filePath) ?? "application/octet-stream";
}
diff --git a/source/SuCoS.csproj b/source/SuCoS.csproj
index 62593b26997502326da0825e40badc200c69ab85..18fec0545ce78ee952d0679284e0826c2483c27f 100644
--- a/source/SuCoS.csproj
+++ b/source/SuCoS.csproj
@@ -14,13 +14,14 @@
+
-
-
+
-
+
+
diff --git a/test/ServerHandlers/PingRequestHandlerTests.cs b/test/ServerHandlers/PingRequestHandlerTests.cs
index af5cfb2d3321f850b639ad8cf90fbd90b23524ce..0d7ad20da875dce888e30772c2173babd433c888 100644
--- a/test/ServerHandlers/PingRequestHandlerTests.cs
+++ b/test/ServerHandlers/PingRequestHandlerTests.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Http;
+using NSubstitute;
using SuCoS.ServerHandlers;
using Xunit;
@@ -10,15 +10,14 @@ public class PingRequestHandlerTests : TestSetup
public async Task Handle_ReturnsServerStartupTimestamp()
{
// Arrange
- var context = new DefaultHttpContext();
-
+ var response = Substitute.For();
var stream = new MemoryStream();
- context.Response.Body = stream;
+ response.OutputStream.Returns(stream);
var pingRequests = new PingRequests();
// Act
- await pingRequests.Handle(context, "ping", todayDate);
+ var code = await pingRequests.Handle(response, "ping", todayDate);
// Assert
stream.Seek(0, SeekOrigin.Begin);
@@ -26,8 +25,9 @@ public class PingRequestHandlerTests : TestSetup
var content = await reader.ReadToEndAsync();
Assert.Equal(todayDate.ToString("o"), content);
- }
+ Assert.Equal("ping", code);
+ }
[Theory]
[InlineData("/ping", true)]
diff --git a/test/ServerHandlers/RegisteredPageRequestHandlerTests.cs b/test/ServerHandlers/RegisteredPageRequestHandlerTests.cs
index eb2a175675819cb9594e9060d79671b7bb2adb71..bce4b153dbdb7b7372738bea8eeb2d3fecb7e8e6 100644
--- a/test/ServerHandlers/RegisteredPageRequestHandlerTests.cs
+++ b/test/ServerHandlers/RegisteredPageRequestHandlerTests.cs
@@ -1,12 +1,15 @@
-using Microsoft.AspNetCore.Http;
+using NSubstitute;
using SuCoS.Models.CommandLineOptions;
using SuCoS.ServerHandlers;
+using System.Net;
using Xunit;
namespace Tests.ServerHandlers;
public class RegisteredPageRequestHandlerTests : TestSetup
{
+ private readonly HttpClient _httpClient = new HttpClient();
+
[Theory]
[InlineData("/", true)]
[InlineData("/testPage", false)]
@@ -40,20 +43,23 @@ public class RegisteredPageRequestHandlerTests : TestSetup
};
var registeredPageRequest = new RegisteredPageRequest(site);
- var context = new DefaultHttpContext();
+ var response = Substitute.For();
var stream = new MemoryStream();
- context.Response.Body = stream;
+ response.OutputStream.Returns(stream);
// Act
site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content"));
registeredPageRequest.Check(requestPath);
- await registeredPageRequest.Handle(context, requestPath, DateTime.Now);
+ var code = await registeredPageRequest.Handle(response, requestPath, DateTime.Now);
// Assert
stream.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
+ Assert.Equal("dict", code);
+
+ // Assert
// You may want to adjust this assertion depending on the actual format of your injected script
if (contains)
{
@@ -66,7 +72,5 @@ public class RegisteredPageRequestHandlerTests : TestSetup
Assert.DoesNotContain("", content, StringComparison.InvariantCulture);
}
Assert.Contains("Index Content", content, StringComparison.InvariantCulture);
-
}
-
}
diff --git a/test/ServerHandlers/StaticFileRequestHandlerTests.cs b/test/ServerHandlers/StaticFileRequestHandlerTests.cs
index eea246a045f4bdfe54c154edb00e50c96adef2ef..6e6183922ad401f8b27c8ab175b1f20a3f223129 100644
--- a/test/ServerHandlers/StaticFileRequestHandlerTests.cs
+++ b/test/ServerHandlers/StaticFileRequestHandlerTests.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Http;
+using NSubstitute;
using SuCoS.ServerHandlers;
using Xunit;
@@ -7,6 +7,7 @@ namespace Tests.ServerHandlers;
public class StaticFileRequestHandlerTests : TestSetup, IDisposable
{
private readonly string tempFilePath;
+ private readonly HttpClient _httpClient = new HttpClient();
public StaticFileRequestHandlerTests() : base()
{
@@ -26,7 +27,7 @@ public class StaticFileRequestHandlerTests : TestSetup, IDisposable
var staticFileRequest = new StaticFileRequest(basePath, false);
// Act
- var result = staticFileRequest.Check(requestPath);
+ var result = staticFileRequest.Check(requestPath: requestPath);
// Assert
Assert.True(result);
@@ -41,13 +42,14 @@ public class StaticFileRequestHandlerTests : TestSetup, IDisposable
?? throw new InvalidOperationException("Unable to determine directory of temporary file.");
var staticFileRequest = new StaticFileRequest(basePath, true);
- var context = new DefaultHttpContext();
+
+ var response = Substitute.For();
var stream = new MemoryStream();
- context.Response.Body = stream;
+ response.OutputStream.Returns(stream);
// Act
staticFileRequest.Check(requestPath);
- await staticFileRequest.Handle(context, requestPath, DateTime.Now);
+ var code = await staticFileRequest.Handle(response, requestPath, DateTime.Now);
// Assert
stream.Seek(0, SeekOrigin.Begin);
@@ -55,6 +57,8 @@ public class StaticFileRequestHandlerTests : TestSetup, IDisposable
var content = await reader.ReadToEndAsync();
Assert.Equal("test", content);
+
+ Assert.Equal("themeSt", code);
}
public void Dispose()