diff --git a/README.md b/README.md index bc147ef..549632c 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,21 @@ [![NuGet](https://img.shields.io/nuget/v/Hangfire.Console.svg)](https://www.nuget.org/packages/Hangfire.Console/) ![MIT License](https://img.shields.io/badge/license-MIT-orange.svg) -Inspired by AppVeyor, Hangfire.Console provides a console-like logging experience for your jobs. +Inspired by AppVeyor, Hangfire.Console provides a console-like logging experience for your jobs. ![dashboard](dashboard.png) ## Features - - **Provider-agnostic**: (allegedly) works with any job storage provider (currently tested with SqlServer and MongoDB). - - **100% Safe**: no Hangfire-managed data (e.g. jobs, states) is ever updated, hence there's no risk to corrupt it. - - **With Live Updates**: new messages will appear as they're logged, as if you're looking at real console. - - (blah-blah-blah) +- **Provider-agnostic**: (allegedly) works with any job storage provider (currently tested with SqlServer and MongoDB). +- **100% Safe**: no Hangfire-managed data (e.g. jobs, states) is ever updated, hence there's no risk to corrupt it. +- **With Live Updates**: new messages will appear as they're logged, as if you're looking at real console. +- (blah-blah-blah) ## Setup In .NET Core's Startup.cs: + ```c# public void ConfigureServices(IServiceCollection services) { @@ -30,13 +31,14 @@ public void ConfigureServices(IServiceCollection services) ``` Otherwise, + ```c# GlobalConfiguration.Configuration .UseSqlServerStorage("connectionSting") .UseConsole(); ``` -**NOTE**: If you have Dashboard and Server running separately, +**NOTE**: If you have Dashboard and Server running separately, you'll need to call `UseConsole()` on both. ### Additional options @@ -44,6 +46,7 @@ you'll need to call `UseConsole()` on both. As usual, you may provide additional options for `UseConsole()` method. Here's what you can configure: + - **ExpireIn** – time to keep console sessions (default: 24 hours) - **FollowJobRetentionPolicy** – expire all console sessions along with parent job (default: true) - **PollInterval** – poll interval for live updates, ms (default: 1000) @@ -51,14 +54,16 @@ Here's what you can configure: - **TextColor** – console default text color (default: #ffffff) - **TimestampColor** – timestamp text color (default: #00aad7) -**NOTE**: After you initially add Hangfire.Console (or change the options above) you may need to clear browser cache, as generated CSS/JS can be cached by browser. +**NOTE**: After you initially add Hangfire.Console (or change the options above) you may need to clear browser cache, as +generated CSS/JS can be cached by browser. ## Log -Hangfire.Console provides extension methods on `PerformContext` object, -hence you'll need to add it as a job argument. +Hangfire.Console provides extension methods on `PerformContext` object, +hence you'll need to add it as a job argument. -**NOTE**: Like `IJobCancellationToken`, `PerformContext` is a special argument type which Hangfire will substitute automatically. You should pass `null` when enqueuing a job. +**NOTE**: Like `IJobCancellationToken`, `PerformContext` is a special argument type which Hangfire will substitute +automatically. You should pass `null` when enqueuing a job. Now you can write to console: @@ -99,11 +104,13 @@ public void TaskMethod(PerformContext context) You can create multiple progress bars and update them separately. -By default, progress bar is initialized with value `0`. You can specify initial value and progress bar color as optional arguments for `WriteProgressBar()`. +By default, progress bar is initialized with value `0`. You can specify initial value and progress bar color as optional +arguments for `WriteProgressBar()`. ### Enumeration progress -To easily track progress of enumeration over a collection in a for-each loop, library adds an extension method `WithProgress`: +To easily track progress of enumeration over a collection in a for-each loop, library adds an extension +method `WithProgress`: ```c# public void TaskMethod(PerformContext context) @@ -117,9 +124,12 @@ public void TaskMethod(PerformContext context) } ``` -It will automatically update progress bar during enumeration, and will set progress to 100% if for-each loop was interrupted with a `break` instruction. +It will automatically update progress bar during enumeration, and will set progress to 100% if for-each loop was +interrupted with a `break` instruction. -**NOTE**: If the number of items in the collection cannot be determined automatically (e.g. collection doesn't implement `ICollection`/`ICollection`/`IReadOnlyCollection`, you'll need to pass additional argument `count` to the extension method). +**NOTE**: If the number of items in the collection cannot be determined automatically (e.g. collection doesn't +implement `ICollection`/`ICollection`/`IReadOnlyCollection`, you'll need to pass additional argument `count` to +the extension method). ## License diff --git a/src/Hangfire.Console/ConsoleExtensions.cs b/src/Hangfire.Console/ConsoleExtensions.cs index debb6dc..ef99f61 100644 --- a/src/Hangfire.Console/ConsoleExtensions.cs +++ b/src/Hangfire.Console/ConsoleExtensions.cs @@ -1,201 +1,202 @@ -using Hangfire.Console.Progress; +using System; +using Hangfire.Console.Progress; using Hangfire.Console.Server; using Hangfire.Server; -using System; using JetBrains.Annotations; -namespace Hangfire.Console +namespace Hangfire.Console; + +/// +/// Provides extension methods for writing to console. +/// +[PublicAPI] +public static class ConsoleExtensions { /// - /// Provides extension methods for writing to console. + /// Sets text color for next console lines. /// - [PublicAPI] - public static class ConsoleExtensions + /// Context + /// Text color to use + public static void SetTextColor(this PerformContext context, ConsoleTextColor color) { - /// - /// Sets text color for next console lines. - /// - /// Context - /// Text color to use - public static void SetTextColor(this PerformContext context, ConsoleTextColor color) + if (color == null) { - if (color == null) - throw new ArgumentNullException(nameof(color)); - - var consoleContext = ConsoleContext.FromPerformContext(context); - if (consoleContext == null) return; - - consoleContext.TextColor = color; + throw new ArgumentNullException(nameof(color)); } - /// - /// Resets text color for next console lines. - /// - /// Context - public static void ResetTextColor(this PerformContext context) + var consoleContext = ConsoleContext.FromPerformContext(context); + if (consoleContext == null) { - var consoleContext = ConsoleContext.FromPerformContext(context); - if (consoleContext == null) return; - - consoleContext.TextColor = null; + return; } - /// - /// Adds an updateable progress bar to console. - /// - /// Context - /// Initial value - /// Progress bar color - public static IProgressBar WriteProgressBar(this PerformContext context, int value = 0, ConsoleTextColor color = null) - { - return ConsoleContext.FromPerformContext(context)?.WriteProgressBar(null, value, color) ?? new NoOpProgressBar(); - } + consoleContext.TextColor = color; + } - /// - /// Adds an updateable named progress bar to console. - /// - /// Context - /// Name - /// Initial value - /// Progress bar color - public static IProgressBar WriteProgressBar(this PerformContext context, string name, double value = 0, ConsoleTextColor color = null) + /// + /// Resets text color for next console lines. + /// + /// Context + public static void ResetTextColor(this PerformContext context) + { + var consoleContext = ConsoleContext.FromPerformContext(context); + if (consoleContext == null) { - return ConsoleContext.FromPerformContext(context)?.WriteProgressBar(name, value, color) ?? new NoOpProgressBar(); + return; } - /// - /// Adds a string to console. - /// - /// Context - /// String - public static void WriteLine(this PerformContext context, string value) - { - ConsoleContext.FromPerformContext(context)?.WriteLine(value, null); - } + consoleContext.TextColor = null; + } - /// - /// Adds a string to console. - /// - /// Context - /// Text color - /// String - public static void WriteLine(this PerformContext context, ConsoleTextColor color, string value) - { - ConsoleContext.FromPerformContext(context)?.WriteLine(value, color); - } + /// + /// Adds an updateable progress bar to console. + /// + /// Context + /// Initial value + /// Progress bar color + public static IProgressBar WriteProgressBar(this PerformContext context, int value = 0, ConsoleTextColor color = null) => ConsoleContext.FromPerformContext(context)?.WriteProgressBar(null, value, color) ?? new NoOpProgressBar(); + + /// + /// Adds an updateable named progress bar to console. + /// + /// Context + /// Name + /// Initial value + /// Progress bar color + public static IProgressBar WriteProgressBar(this PerformContext context, string name, double value = 0, ConsoleTextColor color = null) => ConsoleContext.FromPerformContext(context)?.WriteProgressBar(name, value, color) ?? new NoOpProgressBar(); - /// - /// Adds an empty line to console. - /// - /// Context - public static void WriteLine(this PerformContext context) - => WriteLine(context, ""); - - /// - /// Adds a value to a console. - /// - /// Context - /// Value - public static void WriteLine(this PerformContext context, object value) - => WriteLine(context, value?.ToString()); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Format string - /// Argument - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, string format, object arg0) - => WriteLine(context, string.Format(format, arg0)); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Format string - /// Argument - /// Argument - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, string format, object arg0, object arg1) - => WriteLine(context, string.Format(format, arg0, arg1)); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Format string - /// Argument - /// Argument - /// Argument - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, string format, object arg0, object arg1, object arg2) - => WriteLine(context, string.Format(format, arg0, arg1, arg2)); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Format string - /// Arguments - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, string format, params object[] args) - => WriteLine(context, string.Format(format, args)); - - /// - /// Adds a value to a console. - /// - /// Context - /// Text color - /// Value - public static void WriteLine(this PerformContext context, ConsoleTextColor color, object value) - => WriteLine(context, color, value?.ToString()); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Text color - /// Format string - /// Argument - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, object arg0) - => WriteLine(context, color, string.Format(format, arg0)); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Text color - /// Format string - /// Argument - /// Argument - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, object arg0, object arg1) - => WriteLine(context, color, string.Format(format, arg0, arg1)); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Text color - /// Format string - /// Argument - /// Argument - /// Argument - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, object arg0, object arg1, object arg2) - => WriteLine(context, color, string.Format(format, arg0, arg1, arg2)); - - /// - /// Adds a formatted string to a console. - /// - /// Context - /// Text color - /// Format string - /// Arguments - [StringFormatMethod("format")] - public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, params object[] args) - => WriteLine(context, color, string.Format(format, args)); + /// + /// Adds a string to console. + /// + /// Context + /// String + public static void WriteLine(this PerformContext context, string value) + { + ConsoleContext.FromPerformContext(context)?.WriteLine(value, null); } + + /// + /// Adds a string to console. + /// + /// Context + /// Text color + /// String + public static void WriteLine(this PerformContext context, ConsoleTextColor color, string value) + { + ConsoleContext.FromPerformContext(context)?.WriteLine(value, color); + } + + /// + /// Adds an empty line to console. + /// + /// Context + public static void WriteLine(this PerformContext context) + => WriteLine(context, ""); + + /// + /// Adds a value to a console. + /// + /// Context + /// Value + public static void WriteLine(this PerformContext context, object value) + => WriteLine(context, value?.ToString()); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Format string + /// Argument + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, string format, object arg0) + => WriteLine(context, string.Format(format, arg0)); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Format string + /// Argument + /// Argument + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, string format, object arg0, object arg1) + => WriteLine(context, string.Format(format, arg0, arg1)); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Format string + /// Argument + /// Argument + /// Argument + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, string format, object arg0, object arg1, object arg2) + => WriteLine(context, string.Format(format, arg0, arg1, arg2)); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Format string + /// Arguments + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, string format, params object[] args) + => WriteLine(context, string.Format(format, args)); + + /// + /// Adds a value to a console. + /// + /// Context + /// Text color + /// Value + public static void WriteLine(this PerformContext context, ConsoleTextColor color, object value) + => WriteLine(context, color, value?.ToString()); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Text color + /// Format string + /// Argument + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, object arg0) + => WriteLine(context, color, string.Format(format, arg0)); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Text color + /// Format string + /// Argument + /// Argument + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, object arg0, object arg1) + => WriteLine(context, color, string.Format(format, arg0, arg1)); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Text color + /// Format string + /// Argument + /// Argument + /// Argument + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, object arg0, object arg1, object arg2) + => WriteLine(context, color, string.Format(format, arg0, arg1, arg2)); + + /// + /// Adds a formatted string to a console. + /// + /// Context + /// Text color + /// Format string + /// Arguments + [StringFormatMethod("format")] + public static void WriteLine(this PerformContext context, ConsoleTextColor color, string format, params object[] args) + => WriteLine(context, color, string.Format(format, args)); } diff --git a/src/Hangfire.Console/ConsoleOptions.cs b/src/Hangfire.Console/ConsoleOptions.cs index ed0fe1a..5a9ff40 100644 --- a/src/Hangfire.Console/ConsoleOptions.cs +++ b/src/Hangfire.Console/ConsoleOptions.cs @@ -1,52 +1,55 @@ using System; using JetBrains.Annotations; -namespace Hangfire.Console +namespace Hangfire.Console; + +/// +/// Configuration options for console. +/// +[PublicAPI] +public class ConsoleOptions { /// - /// Configuration options for console. + /// Gets or sets expiration time for console messages. + /// + public TimeSpan ExpireIn { get; set; } = TimeSpan.FromDays(1); + + /// + /// Gets or sets if console messages should follow the same retention policy as the parent job. + /// When set to true, parameter is ignored. + /// + public bool FollowJobRetentionPolicy { get; set; } = true; + + /// + /// Gets or sets console poll interval (in ms). + /// + public int PollInterval { get; set; } = 1000; + + /// + /// Gets or sets background color for console. /// - [PublicAPI] - public class ConsoleOptions + public string BackgroundColor { get; set; } = "#0d3163"; + + /// + /// Gets or sets text color for console. + /// + public string TextColor { get; set; } = "#ffffff"; + + /// + /// Gets or sets timestamp color for console. + /// + public string TimestampColor { get; set; } = "#00aad7"; + + internal void Validate(string paramName) { - /// - /// Gets or sets expiration time for console messages. - /// - public TimeSpan ExpireIn { get; set; } = TimeSpan.FromDays(1); - - /// - /// Gets or sets if console messages should follow the same retention policy as the parent job. - /// When set to true, parameter is ignored. - /// - public bool FollowJobRetentionPolicy { get; set; } = true; - - /// - /// Gets or sets console poll interval (in ms). - /// - public int PollInterval { get; set; } = 1000; - - /// - /// Gets or sets background color for console. - /// - public string BackgroundColor { get; set; } = "#0d3163"; - - /// - /// Gets or sets text color for console. - /// - public string TextColor { get; set; } = "#ffffff"; - - /// - /// Gets or sets timestamp color for console. - /// - public string TimestampColor { get; set; } = "#00aad7"; - - internal void Validate(string paramName) + if (ExpireIn < TimeSpan.FromMinutes(1)) { - if (ExpireIn < TimeSpan.FromMinutes(1)) - throw new ArgumentException("ExpireIn shouldn't be less than 1 minute", paramName); + throw new ArgumentException("ExpireIn shouldn't be less than 1 minute", paramName); + } - if (PollInterval < 100) - throw new ArgumentException("PollInterval shouldn't be less than 100 ms", paramName); + if (PollInterval < 100) + { + throw new ArgumentException("PollInterval shouldn't be less than 100 ms", paramName); } } } diff --git a/src/Hangfire.Console/ConsoleTextColor.cs b/src/Hangfire.Console/ConsoleTextColor.cs index b985cbf..068c11d 100644 --- a/src/Hangfire.Console/ConsoleTextColor.cs +++ b/src/Hangfire.Console/ConsoleTextColor.cs @@ -1,109 +1,105 @@ using JetBrains.Annotations; -namespace Hangfire.Console +namespace Hangfire.Console; + +/// +/// Text color values +/// +[PublicAPI] +public class ConsoleTextColor { /// - /// Text color values + /// The color black. + /// + public static readonly ConsoleTextColor Black = new("#000000"); + + /// + /// The color dark blue. + /// + public static readonly ConsoleTextColor DarkBlue = new("#000080"); + + /// + /// The color dark green. + /// + public static readonly ConsoleTextColor DarkGreen = new("#008000"); + + /// + /// The color dark cyan (dark blue-green). + /// + public static readonly ConsoleTextColor DarkCyan = new("#008080"); + + /// + /// The color dark red. + /// + public static readonly ConsoleTextColor DarkRed = new("#800000"); + + /// + /// The color dark magenta (dark purplish-red). + /// + public static readonly ConsoleTextColor DarkMagenta = new("#800080"); + + /// + /// The color dark yellow (ochre). + /// + public static readonly ConsoleTextColor DarkYellow = new("#808000"); + + /// + /// The color gray. + /// + public static readonly ConsoleTextColor Gray = new("#c0c0c0"); + + /// + /// The color dark gray. + /// + public static readonly ConsoleTextColor DarkGray = new("#808080"); + + /// + /// The color blue. + /// + public static readonly ConsoleTextColor Blue = new("#0000ff"); + + /// + /// The color green. + /// + public static readonly ConsoleTextColor Green = new("#00ff00"); + + /// + /// The color cyan (blue-green). + /// + public static readonly ConsoleTextColor Cyan = new("#00ffff"); + + /// + /// The color red. + /// + public static readonly ConsoleTextColor Red = new("#ff0000"); + + /// + /// The color magenta (purplish-red). + /// + public static readonly ConsoleTextColor Magenta = new("#ff00ff"); + + /// + /// The color yellow. + /// + public static readonly ConsoleTextColor Yellow = new("#ffff00"); + + /// + /// The color white. /// - [PublicAPI] - public class ConsoleTextColor + public static readonly ConsoleTextColor White = new("#ffffff"); + + private readonly string _color; + + private ConsoleTextColor(string color) { - /// - /// The color black. - /// - public static readonly ConsoleTextColor Black = new("#000000"); - - /// - /// The color dark blue. - /// - public static readonly ConsoleTextColor DarkBlue = new("#000080"); - - /// - /// The color dark green. - /// - public static readonly ConsoleTextColor DarkGreen = new("#008000"); - - /// - /// The color dark cyan (dark blue-green). - /// - public static readonly ConsoleTextColor DarkCyan = new("#008080"); - - /// - /// The color dark red. - /// - public static readonly ConsoleTextColor DarkRed = new("#800000"); - - /// - /// The color dark magenta (dark purplish-red). - /// - public static readonly ConsoleTextColor DarkMagenta = new("#800080"); - - /// - /// The color dark yellow (ochre). - /// - public static readonly ConsoleTextColor DarkYellow = new("#808000"); - - /// - /// The color gray. - /// - public static readonly ConsoleTextColor Gray = new("#c0c0c0"); - - /// - /// The color dark gray. - /// - public static readonly ConsoleTextColor DarkGray = new("#808080"); - - /// - /// The color blue. - /// - public static readonly ConsoleTextColor Blue = new("#0000ff"); - - /// - /// The color green. - /// - public static readonly ConsoleTextColor Green = new("#00ff00"); - - /// - /// The color cyan (blue-green). - /// - public static readonly ConsoleTextColor Cyan = new("#00ffff"); - - /// - /// The color red. - /// - public static readonly ConsoleTextColor Red = new("#ff0000"); - - /// - /// The color magenta (purplish-red). - /// - public static readonly ConsoleTextColor Magenta = new("#ff00ff"); - - /// - /// The color yellow. - /// - public static readonly ConsoleTextColor Yellow = new("#ffff00"); - - /// - /// The color white. - /// - public static readonly ConsoleTextColor White = new("#ffffff"); - - private readonly string _color; - - private ConsoleTextColor(string color) - { - _color = color; - } - - /// - public override string ToString() - { - return _color; - } - - /// - /// Implicitly converts to . - /// - public static implicit operator string(ConsoleTextColor color) => color?._color; + _color = color; } + + /// + public override string ToString() => _color; + + /// + /// Implicitly converts to . + /// + public static implicit operator string(ConsoleTextColor color) => color?._color; } diff --git a/src/Hangfire.Console/Dashboard/ConsoleDispatcher.cs b/src/Hangfire.Console/Dashboard/ConsoleDispatcher.cs index 6deaf83..6bca3fd 100644 --- a/src/Hangfire.Console/Dashboard/ConsoleDispatcher.cs +++ b/src/Hangfire.Console/Dashboard/ConsoleDispatcher.cs @@ -1,41 +1,42 @@ -using Hangfire.Dashboard; -using System.Threading.Tasks; +using System; using System.Text; +using System.Threading.Tasks; using Hangfire.Console.Serialization; -using System; using Hangfire.Console.Storage; +using Hangfire.Dashboard; + +namespace Hangfire.Console.Dashboard; -namespace Hangfire.Console.Dashboard +/// +/// Provides incremental updates for a console. +/// +internal class ConsoleDispatcher : IDashboardDispatcher { - /// - /// Provides incremental updates for a console. - /// - internal class ConsoleDispatcher : IDashboardDispatcher + public Task Dispatch(DashboardContext context) { - public Task Dispatch(DashboardContext context) + if (context == null) { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - var consoleId = ConsoleId.Parse(context.UriMatch.Groups[1].Value); + throw new ArgumentNullException(nameof(context)); + } - var startArg = context.Request.GetQuery("start"); + var consoleId = ConsoleId.Parse(context.UriMatch.Groups[1].Value); - // try to parse offset at which we should start returning requests - if (string.IsNullOrEmpty(startArg) || !int.TryParse(startArg, out var start)) - { - // if not provided or invalid, fetch records from the very start - start = 0; - } + var startArg = context.Request.GetQuery("start"); - var buffer = new StringBuilder(); - using (var storage = new ConsoleStorage(context.Storage.GetConnection())) - { - ConsoleRenderer.RenderLineBuffer(buffer, storage, consoleId, start); - } + // try to parse offset at which we should start returning requests + if (string.IsNullOrEmpty(startArg) || !int.TryParse(startArg, out var start)) + { + // if not provided or invalid, fetch records from the very start + start = 0; + } - context.Response.ContentType = "text/html"; - return context.Response.WriteAsync(buffer.ToString()); + var buffer = new StringBuilder(); + using (var storage = new ConsoleStorage(context.Storage.GetConnection())) + { + ConsoleRenderer.RenderLineBuffer(buffer, storage, consoleId, start); } + + context.Response.ContentType = "text/html"; + return context.Response.WriteAsync(buffer.ToString()); } } diff --git a/src/Hangfire.Console/Dashboard/ConsoleRenderer.cs b/src/Hangfire.Console/Dashboard/ConsoleRenderer.cs index 0a6862c..c6ac037 100644 --- a/src/Hangfire.Console/Dashboard/ConsoleRenderer.cs +++ b/src/Hangfire.Console/Dashboard/ConsoleRenderer.cs @@ -1,238 +1,254 @@ -using Hangfire.Common; -using Hangfire.Console.Serialization; -using Hangfire.Console.Storage; -using Hangfire.Dashboard; -using Hangfire.States; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Text.RegularExpressions; +using Hangfire.Common; +using Hangfire.Console.Serialization; +using Hangfire.Console.Storage; +using Hangfire.Dashboard; +using Hangfire.States; + +namespace Hangfire.Console.Dashboard; -namespace Hangfire.Console.Dashboard +/// +/// Helper methods to render console shared between +/// and . +/// +internal static class ConsoleRenderer { - /// - /// Helper methods to render console shared between - /// and . - /// - internal static class ConsoleRenderer - { - private static readonly HtmlHelper Helper = new HtmlHelper(new DummyPage()); + private static readonly HtmlHelper Helper = new(new DummyPage()); - // Reference: http://www.regexguru.com/2008/11/detecting-urls-in-a-block-of-text/ - private static readonly Regex LinkDetector = new Regex(@" + // Reference: http://www.regexguru.com/2008/11/detecting-urls-in-a-block-of-text/ + private static readonly Regex LinkDetector = new(@" \b(?:(?(?:f|ht)tps?://)|www\.|ftp\.) (?:\([-\w+&@#/%=~|$?!:,.]*\)|[-\w+&@#/%=~|$?!:,.])* (?:\([-\w+&@#/%=~|$?!:,.]*\)|[\w+&@#/%=~|$])", - RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled); + RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled); - private class DummyPage : RazorPage + /// + /// Renders text string (with possible hyperlinks) into buffer + /// + /// Buffer + /// Text to render + public static void RenderText(StringBuilder buffer, string text) + { + if (string.IsNullOrEmpty(text)) { - public override void Execute() - { - } + return; } - /// - /// Renders text string (with possible hyperlinks) into buffer - /// - /// Buffer - /// Text to render - public static void RenderText(StringBuilder buffer, string text) - { - if (string.IsNullOrEmpty(text)) return; + var start = 0; - var start = 0; - - foreach (Match m in LinkDetector.Matches(text)) + foreach (Match m in LinkDetector.Matches(text)) + { + if (m.Index > start) { - if (m.Index > start) - { - buffer.Append(Helper.HtmlEncode(text.Substring(start, m.Index - start))); - } - - var schema = ""; - if (!m.Groups["schema"].Success) - { - // force schema for links without one (like www.google.com) - schema = m.Value.StartsWith("ftp.", StringComparison.OrdinalIgnoreCase) ? "ftp://" : "https://"; - } - - buffer.Append("") - .Append(Helper.HtmlEncode(m.Value)) - .Append(""); - - start = m.Index + m.Length; + buffer.Append(Helper.HtmlEncode(text.Substring(start, m.Index - start))); } - if (start < text.Length) + var schema = ""; + if (!m.Groups["schema"].Success) { - buffer.Append(Helper.HtmlEncode(text.Substring(start))); + // force schema for links without one (like www.google.com) + schema = m.Value.StartsWith("ftp.", StringComparison.OrdinalIgnoreCase) ? "ftp://" : "https://"; } + + buffer.Append("") + .Append(Helper.HtmlEncode(m.Value)) + .Append(""); + + start = m.Index + m.Length; } - /// - /// Renders a single to a buffer. - /// - /// Buffer - /// Line - /// Reference timestamp for time offset - public static void RenderLine(StringBuilder builder, ConsoleLine line, DateTime timestamp) + if (start < text.Length) { - var offset = TimeSpan.FromSeconds(line.TimeOffset); - var isProgressBar = line.ProgressValue.HasValue; + buffer.Append(Helper.HtmlEncode(text.Substring(start))); + } + } - builder.Append("
+ /// Renders a single to a buffer. + /// + /// Buffer + /// Line + /// Reference timestamp for time offset + public static void RenderLine(StringBuilder builder, ConsoleLine line, DateTime timestamp) + { + var offset = TimeSpan.FromSeconds(line.TimeOffset); + var isProgressBar = line.ProgressValue.HasValue; - if (line.TextColor != null) - { - builder.Append(" style=\"color:").Append(line.TextColor).Append("\""); - } + builder.Append("
"); + if (isProgressBar) + { + builder.Append(" data-id=\"").Append(line.Message).Append("\""); + } - if (isProgressBar && !string.IsNullOrWhiteSpace(line.ProgressName)) - { - builder.Append(Helper.MomentTitle(timestamp + offset, Helper.HtmlEncode(line.ProgressName))); - } - else - { - builder.Append(Helper.MomentTitle(timestamp + offset, Helper.ToHumanDuration(offset))); - } + builder.Append(">"); - if (isProgressBar) - { - builder.AppendFormat(CultureInfo.InvariantCulture, "
", line.ProgressValue.Value); - } - else - { - RenderText(builder, line.Message); - } + if (isProgressBar && !string.IsNullOrWhiteSpace(line.ProgressName)) + { + builder.Append(Helper.MomentTitle(timestamp + offset, Helper.HtmlEncode(line.ProgressName))); + } + else + { + builder.Append(Helper.MomentTitle(timestamp + offset, Helper.ToHumanDuration(offset))); + } - builder.Append("
"); + if (isProgressBar) + { + builder.AppendFormat(CultureInfo.InvariantCulture, "
", line.ProgressValue.Value); + } + else + { + RenderText(builder, line.Message); } - /// - /// Renders a collection of to a buffer. - /// - /// Buffer - /// Lines - /// Reference timestamp for time offset - public static void RenderLines(StringBuilder builder, IEnumerable lines, DateTime timestamp) + builder.Append("
"); + } + + /// + /// Renders a collection of to a buffer. + /// + /// Buffer + /// Lines + /// Reference timestamp for time offset + public static void RenderLines(StringBuilder builder, IEnumerable lines, DateTime timestamp) + { + if (builder == null) { - if (builder == null) - throw new ArgumentNullException(nameof(builder)); + throw new ArgumentNullException(nameof(builder)); + } - if (lines == null) return; + if (lines == null) + { + return; + } - foreach (var line in lines) - { - RenderLine(builder, line, timestamp); - } + foreach (var line in lines) + { + RenderLine(builder, line, timestamp); } + } - /// - /// Fetches and renders console line buffer. - /// - /// Buffer - /// Console data accessor - /// Console identifier - /// Offset to read lines from - public static void RenderLineBuffer(StringBuilder builder, IConsoleStorage storage, ConsoleId consoleId, int start) + /// + /// Fetches and renders console line buffer. + /// + /// Buffer + /// Console data accessor + /// Console identifier + /// Offset to read lines from + public static void RenderLineBuffer(StringBuilder builder, IConsoleStorage storage, ConsoleId consoleId, int start) + { + if (builder == null) { - if (builder == null) - throw new ArgumentNullException(nameof(builder)); - if (storage == null) - throw new ArgumentNullException(nameof(storage)); - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + throw new ArgumentNullException(nameof(builder)); + } - var items = ReadLines(storage, consoleId, ref start); + if (storage == null) + { + throw new ArgumentNullException(nameof(storage)); + } - builder.AppendFormat("
", start); - RenderLines(builder, items, consoleId.DateValue); - builder.Append("
"); + if (consoleId == null) + { + throw new ArgumentNullException(nameof(consoleId)); } - /// - /// Fetches console lines from storage. - /// - /// Console data accessor - /// Console identifier - /// Offset to read lines from - /// - /// On completion, is set to the end of the current batch, - /// and can be used for next requests (or set to -1, if the job has finished processing). - /// - private static IEnumerable ReadLines(IConsoleStorage storage, ConsoleId consoleId, ref int start) + var items = ReadLines(storage, consoleId, ref start); + + builder.AppendFormat("
", start); + RenderLines(builder, items, consoleId.DateValue); + builder.Append("
"); + } + + /// + /// Fetches console lines from storage. + /// + /// Console data accessor + /// Console identifier + /// Offset to read lines from + /// + /// On completion, is set to the end of the current batch, + /// and can be used for next requests (or set to -1, if the job has finished processing). + /// + private static IEnumerable ReadLines(IConsoleStorage storage, ConsoleId consoleId, ref int start) + { + if (start < 0) { - if (start < 0) return null; + return null; + } - var count = storage.GetLineCount(consoleId); - var result = new List(Math.Max(1, count - start)); + var count = storage.GetLineCount(consoleId); + var result = new List(Math.Max(1, count - start)); - if (count > start) - { - // has some new items to fetch + if (count > start) + { + // has some new items to fetch - Dictionary progressBars = null; + Dictionary progressBars = null; - foreach (var entry in storage.GetLines(consoleId, start, count - 1)) + foreach (var entry in storage.GetLines(consoleId, start, count - 1)) + { + if (entry.ProgressValue.HasValue) { - if (entry.ProgressValue.HasValue) - { - // aggregate progress value updates into single record + // aggregate progress value updates into single record - if (progressBars != null) - { - if (progressBars.TryGetValue(entry.Message, out var prev)) - { - prev.ProgressValue = entry.ProgressValue; - prev.TextColor = entry.TextColor; - continue; - } - } - else + if (progressBars != null) + { + if (progressBars.TryGetValue(entry.Message, out var prev)) { - progressBars = new Dictionary(); + prev.ProgressValue = entry.ProgressValue; + prev.TextColor = entry.TextColor; + continue; } - - progressBars.Add(entry.Message, entry); + } + else + { + progressBars = new Dictionary(); } - result.Add(entry); + progressBars.Add(entry.Message, entry); } + + result.Add(entry); } + } - if (count <= start || start == 0) - { - // no new items or initial load, check if the job is still performing + if (count <= start || start == 0) + { + // no new items or initial load, check if the job is still performing - var state = storage.GetState(consoleId); - if (state == null) - { - // No state found for a job, probably it was deleted - count = -2; - } - else + var state = storage.GetState(consoleId); + if (state == null) + { + // No state found for a job, probably it was deleted + count = -2; + } + else + { + if (!string.Equals(state.Name, ProcessingState.StateName, StringComparison.OrdinalIgnoreCase) || + !consoleId.Equals(new ConsoleId(consoleId.JobId, JobHelper.DeserializeDateTime(state.Data["StartedAt"])))) { - if (!string.Equals(state.Name, ProcessingState.StateName, StringComparison.OrdinalIgnoreCase) || - !consoleId.Equals(new ConsoleId(consoleId.JobId, JobHelper.DeserializeDateTime(state.Data["StartedAt"])))) - { - // Job state has changed (either not Processing, or another Processing with different console id) - count = -1; - } + // Job state has changed (either not Processing, or another Processing with different console id) + count = -1; } } - - start = count; - return result; } + + start = count; + return result; + } + + private class DummyPage : RazorPage + { + public override void Execute() { } } } diff --git a/src/Hangfire.Console/Dashboard/DynamicCssDispatcher.cs b/src/Hangfire.Console/Dashboard/DynamicCssDispatcher.cs index b230999..1c14565 100644 --- a/src/Hangfire.Console/Dashboard/DynamicCssDispatcher.cs +++ b/src/Hangfire.Console/Dashboard/DynamicCssDispatcher.cs @@ -1,44 +1,43 @@ -using Hangfire.Dashboard; -using System; +using System; using System.Text; using System.Threading.Tasks; +using Hangfire.Dashboard; -namespace Hangfire.Console.Dashboard +namespace Hangfire.Console.Dashboard; + +/// +/// Dispatcher for configured styles +/// +internal class DynamicCssDispatcher : IDashboardDispatcher { - /// - /// Dispatcher for configured styles - /// - internal class DynamicCssDispatcher : IDashboardDispatcher + private readonly ConsoleOptions _options; + + public DynamicCssDispatcher(ConsoleOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public Task Dispatch(DashboardContext context) { - private readonly ConsoleOptions _options; - - public DynamicCssDispatcher(ConsoleOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public Task Dispatch(DashboardContext context) - { - var builder = new StringBuilder(); - - builder.AppendLine(".console, .console .line-buffer {") - .Append(" background-color: ").Append(_options.BackgroundColor).AppendLine(";") - .Append(" color: ").Append(_options.TextColor).AppendLine(";") - .AppendLine("}"); - - builder.AppendLine(".console .line > span[data-moment-title] {") - .Append(" color: ").Append(_options.TimestampColor).AppendLine(";") - .AppendLine("}"); - - builder.AppendLine(".console .line > a, .console.line > a:visited, .console.line > a:hover {") - .Append(" color: ").Append(_options.TextColor).AppendLine(";") - .AppendLine("}"); - - builder.AppendLine(".console .line.pb > .pv:before {") - .Append(" color: ").Append(_options.BackgroundColor).AppendLine(";") - .AppendLine("}"); - - return context.Response.WriteAsync(builder.ToString()); - } + var builder = new StringBuilder(); + + builder.AppendLine(".console, .console .line-buffer {") + .Append(" background-color: ").Append(_options.BackgroundColor).AppendLine(";") + .Append(" color: ").Append(_options.TextColor).AppendLine(";") + .AppendLine("}"); + + builder.AppendLine(".console .line > span[data-moment-title] {") + .Append(" color: ").Append(_options.TimestampColor).AppendLine(";") + .AppendLine("}"); + + builder.AppendLine(".console .line > a, .console.line > a:visited, .console.line > a:hover {") + .Append(" color: ").Append(_options.TextColor).AppendLine(";") + .AppendLine("}"); + + builder.AppendLine(".console .line.pb > .pv:before {") + .Append(" color: ").Append(_options.BackgroundColor).AppendLine(";") + .AppendLine("}"); + + return context.Response.WriteAsync(builder.ToString()); } } diff --git a/src/Hangfire.Console/Dashboard/DynamicJsDispatcher.cs b/src/Hangfire.Console/Dashboard/DynamicJsDispatcher.cs index 01e4241..48ce721 100644 --- a/src/Hangfire.Console/Dashboard/DynamicJsDispatcher.cs +++ b/src/Hangfire.Console/Dashboard/DynamicJsDispatcher.cs @@ -1,34 +1,33 @@ -using Hangfire.Dashboard; -using System; +using System; using System.Text; using System.Threading.Tasks; +using Hangfire.Dashboard; -namespace Hangfire.Console.Dashboard +namespace Hangfire.Console.Dashboard; + +/// +/// Dispatcher for configured script +/// +internal class DynamicJsDispatcher : IDashboardDispatcher { - /// - /// Dispatcher for configured script - /// - internal class DynamicJsDispatcher : IDashboardDispatcher - { - private readonly ConsoleOptions _options; + private readonly ConsoleOptions _options; - public DynamicJsDispatcher(ConsoleOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } + public DynamicJsDispatcher(ConsoleOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } - public Task Dispatch(DashboardContext context) - { - var builder = new StringBuilder(); + public Task Dispatch(DashboardContext context) + { + var builder = new StringBuilder(); - builder.Append("(function (hangfire) {") - .Append("hangfire.config = hangfire.config || {};") - .AppendFormat("hangfire.config.consolePollInterval = {0};", _options.PollInterval) - .AppendFormat("hangfire.config.consolePollUrl = '{0}/console/';", context.Request.PathBase) - .Append("})(window.Hangfire = window.Hangfire || {});") - .AppendLine(); + builder.Append("(function (hangfire) {") + .Append("hangfire.config = hangfire.config || {};") + .AppendFormat("hangfire.config.consolePollInterval = {0};", _options.PollInterval) + .AppendFormat("hangfire.config.consolePollUrl = '{0}/console/';", context.Request.PathBase) + .Append("})(window.Hangfire = window.Hangfire || {});") + .AppendLine(); - return context.Response.WriteAsync(builder.ToString()); - } + return context.Response.WriteAsync(builder.ToString()); } } diff --git a/src/Hangfire.Console/Dashboard/JobProgressDispatcher.cs b/src/Hangfire.Console/Dashboard/JobProgressDispatcher.cs index b7aa0df..26ebc18 100644 --- a/src/Hangfire.Console/Dashboard/JobProgressDispatcher.cs +++ b/src/Hangfire.Console/Dashboard/JobProgressDispatcher.cs @@ -10,67 +10,68 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace Hangfire.Console.Dashboard +namespace Hangfire.Console.Dashboard; + +/// +/// Provides progress for jobs. +/// +internal class JobProgressDispatcher : IDashboardDispatcher { - /// - /// Provides progress for jobs. - /// - internal class JobProgressDispatcher : IDashboardDispatcher + internal static readonly JsonSerializerSettings JsonSettings = new() { - internal static readonly JsonSerializerSettings JsonSettings = new() - { - ContractResolver = new DefaultContractResolver() - }; + ContractResolver = new DefaultContractResolver() + }; + + // ReSharper disable once NotAccessedField.Local + private readonly ConsoleOptions _options; - // ReSharper disable once NotAccessedField.Local - private readonly ConsoleOptions _options; + public JobProgressDispatcher(ConsoleOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } - public JobProgressDispatcher(ConsoleOptions options) + public async Task Dispatch(DashboardContext context) + { + if (!"POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase)) { - _options = options ?? throw new ArgumentNullException(nameof(options)); + context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; + return; } - public async Task Dispatch(DashboardContext context) + var result = new Dictionary(); + + var jobIds = await context.Request.GetFormValuesAsync("jobs[]"); + if (jobIds.Count > 0) { - if (!"POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase)) - { - context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; - return; - } + // there are some jobs to process - var result = new Dictionary(); + using var connection = context.Storage.GetConnection(); + using var storage = new ConsoleStorage(connection); - var jobIds = await context.Request.GetFormValuesAsync("jobs[]"); - if (jobIds.Count > 0) + foreach (var jobId in jobIds) { - // there are some jobs to process - - using var connection = context.Storage.GetConnection(); - using var storage = new ConsoleStorage(connection); - - foreach (var jobId in jobIds) + var state = connection.GetStateData(jobId); + if (state != null && string.Equals(state.Name, ProcessingState.StateName, StringComparison.OrdinalIgnoreCase)) { - var state = connection.GetStateData(jobId); - if (state != null && string.Equals(state.Name, ProcessingState.StateName, StringComparison.OrdinalIgnoreCase)) - { - var consoleId = new ConsoleId(jobId, JobHelper.DeserializeDateTime(state.Data["StartedAt"])); + var consoleId = new ConsoleId(jobId, JobHelper.DeserializeDateTime(state.Data["StartedAt"])); - var progress = storage.GetProgress(consoleId); - if (progress.HasValue) - result[jobId] = progress.Value; - } - else + var progress = storage.GetProgress(consoleId); + if (progress.HasValue) { - // return -1 to indicate the job is not in Processing state - result[jobId] = -1; + result[jobId] = progress.Value; } } + else + { + // return -1 to indicate the job is not in Processing state + result[jobId] = -1; + } } + } - var serialized = JsonConvert.SerializeObject(result, JsonSettings); + var serialized = JsonConvert.SerializeObject(result, JsonSettings); - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(serialized); - } + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(serialized); } } diff --git a/src/Hangfire.Console/Dashboard/ProcessingStateRenderer.cs b/src/Hangfire.Console/Dashboard/ProcessingStateRenderer.cs index fa93684..8d1addf 100644 --- a/src/Hangfire.Console/Dashboard/ProcessingStateRenderer.cs +++ b/src/Hangfire.Console/Dashboard/ProcessingStateRenderer.cs @@ -1,76 +1,75 @@ -using Hangfire.Common; +using System; +using System.Collections.Generic; +using System.Text; +using Hangfire.Common; using Hangfire.Console.Serialization; using Hangfire.Console.Storage; using Hangfire.Dashboard; using Hangfire.Dashboard.Extensions; -using System; -using System.Collections.Generic; -using System.Text; -namespace Hangfire.Console.Dashboard +namespace Hangfire.Console.Dashboard; + +/// +/// Replacement renderer for Processing state. +/// +internal class ProcessingStateRenderer { - /// - /// Replacement renderer for Processing state. - /// - internal class ProcessingStateRenderer + // ReSharper disable once NotAccessedField.Local + private readonly ConsoleOptions _options; + + public ProcessingStateRenderer(ConsoleOptions options) { - // ReSharper disable once NotAccessedField.Local - private readonly ConsoleOptions _options; + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public NonEscapedString Render(HtmlHelper helper, IDictionary stateData) + { + var builder = new StringBuilder(); - public ProcessingStateRenderer(ConsoleOptions options) + builder.Append("
"); + + if (stateData.TryGetValue("ServerId", out var serverId) || stateData.TryGetValue("ServerName", out serverId)) { - _options = options ?? throw new ArgumentNullException(nameof(options)); + builder.Append("
Server:
"); + builder.Append($"
{helper.ServerId(serverId)}
"); } - public NonEscapedString Render(HtmlHelper helper, IDictionary stateData) + if (stateData.TryGetValue("WorkerId", out var workerId)) { - var builder = new StringBuilder(); - - builder.Append("
"); - - if (stateData.TryGetValue("ServerId", out var serverId) || stateData.TryGetValue("ServerName", out serverId)) - { - builder.Append("
Server:
"); - builder.Append($"
{helper.ServerId(serverId)}
"); - } - - if (stateData.TryGetValue("WorkerId", out var workerId)) - { - builder.Append("
Worker:
"); - builder.Append($"
{workerId.Substring(0, 8)}
"); - } - else if (stateData.TryGetValue("WorkerNumber", out var workerNumber)) - { - builder.Append("
Worker:
"); - builder.Append($"
#{workerNumber}
"); - } + builder.Append("
Worker:
"); + builder.Append($"
{workerId.Substring(0, 8)}
"); + } + else if (stateData.TryGetValue("WorkerNumber", out var workerNumber)) + { + builder.Append("
Worker:
"); + builder.Append($"
#{workerNumber}
"); + } - builder.Append("
"); + builder.Append("
"); - var page = helper.GetPage(); - if (!page.RequestPath.StartsWith("/jobs/details/")) - { - return new NonEscapedString(builder.ToString()); - } + var page = helper.GetPage(); + if (!page.RequestPath.StartsWith("/jobs/details/")) + { + return new NonEscapedString(builder.ToString()); + } - // We cannot cast page to an internal type JobDetailsPage to get jobId :( - var jobId = page.RequestPath.Substring("/jobs/details/".Length); + // We cannot cast page to an internal type JobDetailsPage to get jobId :( + var jobId = page.RequestPath.Substring("/jobs/details/".Length); - var startedAt = JobHelper.DeserializeDateTime(stateData["StartedAt"]); - var consoleId = new ConsoleId(jobId, startedAt); + var startedAt = JobHelper.DeserializeDateTime(stateData["StartedAt"]); + var consoleId = new ConsoleId(jobId, startedAt); - builder.Append("
"); - builder.AppendFormat("
", consoleId); + builder.Append("
"); + builder.AppendFormat("
", consoleId); - using (var storage = new ConsoleStorage(page.Storage.GetConnection())) - { - ConsoleRenderer.RenderLineBuffer(builder, storage, consoleId, 0); - } + using (var storage = new ConsoleStorage(page.Storage.GetConnection())) + { + ConsoleRenderer.RenderLineBuffer(builder, storage, consoleId, 0); + } - builder.Append("
"); - builder.Append("
"); + builder.Append("
"); + builder.Append("
"); - return new NonEscapedString(builder.ToString()); - } + return new NonEscapedString(builder.ToString()); } } diff --git a/src/Hangfire.Console/EnumerableExtensions.cs b/src/Hangfire.Console/EnumerableExtensions.cs index 8354835..1c0de37 100644 --- a/src/Hangfire.Console/EnumerableExtensions.cs +++ b/src/Hangfire.Console/EnumerableExtensions.cs @@ -1,113 +1,100 @@ -using Hangfire.Console.Progress; -using Hangfire.Server; -using System; +using System; using System.Collections; using System.Collections.Generic; +using Hangfire.Console.Progress; +using Hangfire.Server; using JetBrains.Annotations; -namespace Hangfire.Console +namespace Hangfire.Console; + +/// +/// Provides a set of extension methods to enumerate collections with progress. +/// +[PublicAPI] +public static class EnumerableExtensions { /// - /// Provides a set of extension methods to enumerate collections with progress. + /// Returns an reporting enumeration progress. /// - [PublicAPI] - public static class EnumerableExtensions + /// Item type + /// Source enumerable + /// Progress bar + /// Item count + public static IEnumerable WithProgress(this IEnumerable enumerable, IProgressBar progressBar, int count = -1) { - /// - /// Returns an reporting enumeration progress. - /// - /// Item type - /// Source enumerable - /// Progress bar - /// Item count - public static IEnumerable WithProgress(this IEnumerable enumerable, IProgressBar progressBar, int count = -1) + if (enumerable is ICollection collection) { - if (enumerable is ICollection collection) - { - count = collection.Count; - } - else if (enumerable is IReadOnlyCollection readOnlyCollection) - { - count = readOnlyCollection.Count; - } - else if (count < 0) - { - throw new ArgumentException("Count is required when enumerable is not a collection", nameof(count)); - } - - return new ProgressEnumerable(enumerable, progressBar, count); + count = collection.Count; } - - /// - /// Returns an reporting enumeration progress. - /// - /// Source enumerable - /// Progress bar - /// Item count - public static IEnumerable WithProgress(this IEnumerable enumerable, IProgressBar progressBar, int count = -1) + else if (enumerable is IReadOnlyCollection readOnlyCollection) { - if (enumerable is ICollection collection) - { - count = collection.Count; - } - else if (count < 0) - { - throw new ArgumentException("Count is required when enumerable is not a collection", nameof(count)); - } - - return new ProgressEnumerable(enumerable, progressBar, count); + count = readOnlyCollection.Count; } - - /// - /// Returns an reporting enumeration progress. - /// - /// Item type - /// Source enumerable - /// Perform context - /// Progress bar color - /// Item count - public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, ConsoleTextColor color = null, int count = -1) + else if (count < 0) { - return WithProgress(enumerable, context.WriteProgressBar(0, color), count); + throw new ArgumentException("Count is required when enumerable is not a collection", nameof(count)); } - /// - /// Returns ab reporting enumeration progress. - /// - /// Source enumerable - /// Perform context - /// Progress bar color - /// Item count - public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, ConsoleTextColor color = null, int count = -1) - { - return WithProgress(enumerable, context.WriteProgressBar(0, color), count); - } + return new ProgressEnumerable(enumerable, progressBar, count); + } - /// - /// Returns an reporting enumeration progress. - /// - /// Item type - /// Source enumerable - /// Perform context - /// Progress bar name - /// Progress bar color - /// Item count - public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, string name, ConsoleTextColor color = null, int count = -1) + /// + /// Returns an reporting enumeration progress. + /// + /// Source enumerable + /// Progress bar + /// Item count + public static IEnumerable WithProgress(this IEnumerable enumerable, IProgressBar progressBar, int count = -1) + { + if (enumerable is ICollection collection) { - return WithProgress(enumerable, context.WriteProgressBar(name, 0, color), count); + count = collection.Count; } - - /// - /// Returns ab reporting enumeration progress. - /// - /// Source enumerable - /// Perform context - /// Progress bar name - /// Progress bar color - /// Item count - public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, string name, ConsoleTextColor color = null, int count = -1) + else if (count < 0) { - return WithProgress(enumerable, context.WriteProgressBar(name, 0, color), count); + throw new ArgumentException("Count is required when enumerable is not a collection", nameof(count)); } + + return new ProgressEnumerable(enumerable, progressBar, count); } + + /// + /// Returns an reporting enumeration progress. + /// + /// Item type + /// Source enumerable + /// Perform context + /// Progress bar color + /// Item count + public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, ConsoleTextColor color = null, int count = -1) => WithProgress(enumerable, context.WriteProgressBar(0, color), count); + + /// + /// Returns ab reporting enumeration progress. + /// + /// Source enumerable + /// Perform context + /// Progress bar color + /// Item count + public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, ConsoleTextColor color = null, int count = -1) => WithProgress(enumerable, context.WriteProgressBar(0, color), count); + + /// + /// Returns an reporting enumeration progress. + /// + /// Item type + /// Source enumerable + /// Perform context + /// Progress bar name + /// Progress bar color + /// Item count + public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, string name, ConsoleTextColor color = null, int count = -1) => WithProgress(enumerable, context.WriteProgressBar(name, 0, color), count); + + /// + /// Returns ab reporting enumeration progress. + /// + /// Source enumerable + /// Perform context + /// Progress bar name + /// Progress bar color + /// Item count + public static IEnumerable WithProgress(this IEnumerable enumerable, PerformContext context, string name, ConsoleTextColor color = null, int count = -1) => WithProgress(enumerable, context.WriteProgressBar(name, 0, color), count); } diff --git a/src/Hangfire.Console/GlobalConfigurationExtensions.cs b/src/Hangfire.Console/GlobalConfigurationExtensions.cs index a7f8e6f..33b3f87 100644 --- a/src/Hangfire.Console/GlobalConfigurationExtensions.cs +++ b/src/Hangfire.Console/GlobalConfigurationExtensions.cs @@ -1,65 +1,68 @@ -using Hangfire.Console.Dashboard; +using System; +using System.Reflection; +using Hangfire.Console.Dashboard; using Hangfire.Console.Server; using Hangfire.Console.States; using Hangfire.Dashboard; using Hangfire.Dashboard.Extensions; using Hangfire.States; -using System; -using System.Reflection; using JetBrains.Annotations; -namespace Hangfire.Console +namespace Hangfire.Console; + +/// +/// Provides extension methods to setup Hangfire.Console. +/// +[PublicAPI] +public static class GlobalConfigurationExtensions { /// - /// Provides extension methods to setup Hangfire.Console. + /// Configures Hangfire to use Console. /// - [PublicAPI] - public static class GlobalConfigurationExtensions + /// Global configuration + /// Options for console + public static IGlobalConfiguration UseConsole(this IGlobalConfiguration configuration, ConsoleOptions options = null) { - /// - /// Configures Hangfire to use Console. - /// - /// Global configuration - /// Options for console - public static IGlobalConfiguration UseConsole(this IGlobalConfiguration configuration, ConsoleOptions options = null) + if (configuration == null) { - if (configuration == null) - throw new ArgumentNullException(nameof(configuration)); + throw new ArgumentNullException(nameof(configuration)); + } - options = options ?? new ConsoleOptions(); + options = options ?? new ConsoleOptions(); - options.Validate(nameof(options)); + options.Validate(nameof(options)); - if (DashboardRoutes.Routes.Contains("/console/([0-9a-f]{11}.+)")) - throw new InvalidOperationException("Console is already initialized"); + if (DashboardRoutes.Routes.Contains("/console/([0-9a-f]{11}.+)")) + { + throw new InvalidOperationException("Console is already initialized"); + } - // register server filter for jobs - GlobalJobFilters.Filters.Add(new ConsoleServerFilter(options)); + // register server filter for jobs + GlobalJobFilters.Filters.Add(new ConsoleServerFilter(options)); - // register apply state filter for jobs - // (context may be altered by other state filters, so make it the very last filter in chain to use final context values) - GlobalJobFilters.Filters.Add(new ConsoleApplyStateFilter(options), int.MaxValue); + // register apply state filter for jobs + // (context may be altered by other state filters, so make it the very last filter in chain to use final context values) + GlobalJobFilters.Filters.Add(new ConsoleApplyStateFilter(options), int.MaxValue); - // replace renderer for Processing state - JobHistoryRenderer.Register(ProcessingState.StateName, new ProcessingStateRenderer(options).Render); + // replace renderer for Processing state + JobHistoryRenderer.Register(ProcessingState.StateName, new ProcessingStateRenderer(options).Render); - // register dispatchers to serve console data - DashboardRoutes.Routes.Add("/console/progress", new JobProgressDispatcher(options)); - DashboardRoutes.Routes.Add("/console/([0-9a-f]{11}.+)", new ConsoleDispatcher()); + // register dispatchers to serve console data + DashboardRoutes.Routes.Add("/console/progress", new JobProgressDispatcher(options)); + DashboardRoutes.Routes.Add("/console/([0-9a-f]{11}.+)", new ConsoleDispatcher()); - // register additional dispatchers for CSS and JS - var assembly = typeof(ConsoleRenderer).GetTypeInfo().Assembly; + // register additional dispatchers for CSS and JS + var assembly = typeof(ConsoleRenderer).GetTypeInfo().Assembly; - var jsPath = DashboardRoutes.Routes.Contains("/js[0-9]+") ? "/js[0-9]+" : "/js[0-9]{3}"; - DashboardRoutes.Routes.Append(jsPath, new EmbeddedResourceDispatcher(assembly, "Hangfire.Console.Resources.resize.min.js")); - DashboardRoutes.Routes.Append(jsPath, new DynamicJsDispatcher(options)); - DashboardRoutes.Routes.Append(jsPath, new EmbeddedResourceDispatcher(assembly, "Hangfire.Console.Resources.script.js")); + var jsPath = DashboardRoutes.Routes.Contains("/js[0-9]+") ? "/js[0-9]+" : "/js[0-9]{3}"; + DashboardRoutes.Routes.Append(jsPath, new EmbeddedResourceDispatcher(assembly, "Hangfire.Console.Resources.resize.min.js")); + DashboardRoutes.Routes.Append(jsPath, new DynamicJsDispatcher(options)); + DashboardRoutes.Routes.Append(jsPath, new EmbeddedResourceDispatcher(assembly, "Hangfire.Console.Resources.script.js")); - var cssPath = DashboardRoutes.Routes.Contains("/css[0-9]+") ? "/css[0-9]+" : "/css[0-9]{3}"; - DashboardRoutes.Routes.Append(cssPath, new EmbeddedResourceDispatcher(assembly, "Hangfire.Console.Resources.style.css")); - DashboardRoutes.Routes.Append(cssPath, new DynamicCssDispatcher(options)); + var cssPath = DashboardRoutes.Routes.Contains("/css[0-9]+") ? "/css[0-9]+" : "/css[0-9]{3}"; + DashboardRoutes.Routes.Append(cssPath, new EmbeddedResourceDispatcher(assembly, "Hangfire.Console.Resources.style.css")); + DashboardRoutes.Routes.Append(cssPath, new DynamicCssDispatcher(options)); - return configuration; - } + return configuration; } } diff --git a/src/Hangfire.Console/Hangfire.Console.csproj b/src/Hangfire.Console/Hangfire.Console.csproj index f5d68db..aaaa07e 100644 --- a/src/Hangfire.Console/Hangfire.Console.csproj +++ b/src/Hangfire.Console/Hangfire.Console.csproj @@ -1,123 +1,123 @@  - - Job console for Hangfire - Hangfire.Console - 1.4.2 - Alexey Skalozub - netstandard2.0 - true - true - Hangfire.Console - Hangfire.Console - hangfire;console;logging - History: - v1.4.2: - • Added StringFormatMethod attributes on WriteLine methods (for better intellisense) - - v1.4.1: - • Fix job progress style - • Use explicit json serializer settings - • Remove ConsoleContext from Items in OnPerformed - - v1.4.0: - • Show job progress at Processing Jobs page - - v1.3.10: - • Fix expiration issues (#47) - - v1.3.9: - • Relax Newtonsoft.Json dependency version for .NET 4.5 - - v1.3.8: - • Fix WriteLine thread-safety issues - - v1.3.7: - • Prevent calling UseConsole() twice - • Collapse outdated consoles - - v1.3.6: - • Make progress bars' SetValue thread-safe - • Add support for named progress bars - - v1.3.5: - • Add more overloads for WriteLine and WithProgress extension methods - - v1.3.4: - • Fixed hyperlink detection for urls with query string parameters (#37) - • Fixed loading dots indicator position on tiny screens - - v1.3.3: - • Eliminated unnecessary state filter executions - - v1.3.2: - • Fixed console expiration for some storages (e.g. Hangfire.Redis.StackExchange) - - v1.3.1: - • Fixed compatibility with Hangfire 1.6.11+ - - v1.3.0: - • Consoles are now expired along with parent job by default! - • Added **FollowJobRetentionPolicy** option to switch between old/new expiration modes - - v1.2.1: - • Added Monitoring API - - v1.2.0: - • Added hyperlink detection - - v1.1.7: - • Fixed line ordering issue - - v1.1.6: - • Changed key format to support single-keyspace storages, like Hangfire.Redis - - v1.1.5: - • Allow WriteLine/WriteProgressBar calls with a null PerformContext - - v1.1.4: - • Added support of fractional progress values - • Added WithProgress() extension methods for tracking enumeration progress in for-each loops - - v1.1.3: - • Fixed ugly font on OS X - • Fixed animation lags on all major browsers - - v1.1.2: - • Added support for long messages - • Refactor for better testability - - v1.1.1: - • Don't show current console while there's no lines - - v1.1.0: - • Added progress bars - - v1.0.2: - • Added some more configuration options - • Fixed occasional duplicate lines collapsing - - v1.0.1: - • Fixed compatibility issues with storages losing DateTime precision (like MongoDB) - • Improved client-side experience - - v1.0.0: - • Initial release - - https://raw.githubusercontent.com/pieceofsummer/Hangfire.Console/master/hangfire.console.png - https://raw.githubusercontent.com/pieceofsummer/Hangfire.Console/master/LICENSE.md - git - https://github.com/pieceofsummer/Hangfire.Console - false - false - false - + + Job console for Hangfire + Hangfire.Console + 1.4.2 + Alexey Skalozub + netstandard2.0 + true + true + Hangfire.Console + Hangfire.Console + hangfire;console;logging + History: + v1.4.2: + • Added StringFormatMethod attributes on WriteLine methods (for better intellisense) + + v1.4.1: + • Fix job progress style + • Use explicit json serializer settings + • Remove ConsoleContext from Items in OnPerformed + + v1.4.0: + • Show job progress at Processing Jobs page + + v1.3.10: + • Fix expiration issues (#47) + + v1.3.9: + • Relax Newtonsoft.Json dependency version for .NET 4.5 + + v1.3.8: + • Fix WriteLine thread-safety issues + + v1.3.7: + • Prevent calling UseConsole() twice + • Collapse outdated consoles + + v1.3.6: + • Make progress bars' SetValue thread-safe + • Add support for named progress bars + + v1.3.5: + • Add more overloads for WriteLine and WithProgress extension methods + + v1.3.4: + • Fixed hyperlink detection for urls with query string parameters (#37) + • Fixed loading dots indicator position on tiny screens + + v1.3.3: + • Eliminated unnecessary state filter executions + + v1.3.2: + • Fixed console expiration for some storages (e.g. Hangfire.Redis.StackExchange) + + v1.3.1: + • Fixed compatibility with Hangfire 1.6.11+ + + v1.3.0: + • Consoles are now expired along with parent job by default! + • Added **FollowJobRetentionPolicy** option to switch between old/new expiration modes + + v1.2.1: + • Added Monitoring API + + v1.2.0: + • Added hyperlink detection + + v1.1.7: + • Fixed line ordering issue + + v1.1.6: + • Changed key format to support single-keyspace storages, like Hangfire.Redis + + v1.1.5: + • Allow WriteLine/WriteProgressBar calls with a null PerformContext + + v1.1.4: + • Added support of fractional progress values + • Added WithProgress() extension methods for tracking enumeration progress in for-each loops + + v1.1.3: + • Fixed ugly font on OS X + • Fixed animation lags on all major browsers + + v1.1.2: + • Added support for long messages + • Refactor for better testability + + v1.1.1: + • Don't show current console while there's no lines + + v1.1.0: + • Added progress bars + + v1.0.2: + • Added some more configuration options + • Fixed occasional duplicate lines collapsing + + v1.0.1: + • Fixed compatibility issues with storages losing DateTime precision (like MongoDB) + • Improved client-side experience + + v1.0.0: + • Initial release + + https://raw.githubusercontent.com/pieceofsummer/Hangfire.Console/master/hangfire.console.png + https://raw.githubusercontent.com/pieceofsummer/Hangfire.Console/master/LICENSE.md + git + https://github.com/pieceofsummer/Hangfire.Console + false + false + false + - - - + + + - - - - + + + + diff --git a/src/Hangfire.Console/JobStorageExtensions.cs b/src/Hangfire.Console/JobStorageExtensions.cs index 0500b82..15cba10 100644 --- a/src/Hangfire.Console/JobStorageExtensions.cs +++ b/src/Hangfire.Console/JobStorageExtensions.cs @@ -1,26 +1,27 @@ -using Hangfire.Console.Monitoring; -using System; +using System; +using Hangfire.Console.Monitoring; using JetBrains.Annotations; -namespace Hangfire.Console +namespace Hangfire.Console; + +/// +/// Provides extension methods for . +/// +[PublicAPI] +public static class JobStorageExtensions { /// - /// Provides extension methods for . + /// Returns an instance of . /// - [PublicAPI] - public static class JobStorageExtensions + /// Job storage instance + /// Console API instance + public static IConsoleApi GetConsoleApi(this JobStorage storage) { - /// - /// Returns an instance of . - /// - /// Job storage instance - /// Console API instance - public static IConsoleApi GetConsoleApi(this JobStorage storage) + if (storage == null) { - if (storage == null) - throw new ArgumentNullException(nameof(storage)); - - return new ConsoleApi(storage.GetConnection()); + throw new ArgumentNullException(nameof(storage)); } + + return new ConsoleApi(storage.GetConnection()); } } diff --git a/src/Hangfire.Console/Monitoring/ConsoleApi.cs b/src/Hangfire.Console/Monitoring/ConsoleApi.cs index fc9b294..43b2b9e 100644 --- a/src/Hangfire.Console/Monitoring/ConsoleApi.cs +++ b/src/Hangfire.Console/Monitoring/ConsoleApi.cs @@ -1,76 +1,83 @@ -using Hangfire.Console.Serialization; +using System; +using System.Collections.Generic; +using Hangfire.Console.Serialization; using Hangfire.Console.Storage; using Hangfire.Storage; -using System; -using System.Collections.Generic; -namespace Hangfire.Console.Monitoring +namespace Hangfire.Console.Monitoring; + +internal class ConsoleApi : IConsoleApi { - internal class ConsoleApi : IConsoleApi - { - private readonly IConsoleStorage _storage; + private readonly IConsoleStorage _storage; - public ConsoleApi(IStorageConnection connection) + public ConsoleApi(IStorageConnection connection) + { + if (connection == null) { - if (connection == null) - throw new ArgumentNullException(nameof(connection)); - - _storage = new ConsoleStorage(connection); + throw new ArgumentNullException(nameof(connection)); } - public void Dispose() - { - _storage.Dispose(); - } + _storage = new ConsoleStorage(connection); + } - public IList GetLines(string jobId, DateTime timestamp, LineType type = LineType.Any) - { - var consoleId = new ConsoleId(jobId, timestamp); + public void Dispose() + { + _storage.Dispose(); + } - var count = _storage.GetLineCount(consoleId); - var result = new List(count); + public IList GetLines(string jobId, DateTime timestamp, LineType type = LineType.Any) + { + var consoleId = new ConsoleId(jobId, timestamp); - if (count > 0) - { - Dictionary progressBars = null; + var count = _storage.GetLineCount(consoleId); + var result = new List(count); + + if (count > 0) + { + Dictionary progressBars = null; - foreach (var entry in _storage.GetLines(consoleId, 0, count)) + foreach (var entry in _storage.GetLines(consoleId, 0, count)) + { + if (entry.ProgressValue.HasValue) { - if (entry.ProgressValue.HasValue) + if (type == LineType.Text) { - if (type == LineType.Text) continue; + continue; + } - // aggregate progress value updates into single record + // aggregate progress value updates into single record - if (progressBars != null) - { - if (progressBars.TryGetValue(entry.Message, out var prev)) - { - prev.Progress = entry.ProgressValue.Value; - prev.Color = entry.TextColor; - continue; - } - } - else + if (progressBars != null) + { + if (progressBars.TryGetValue(entry.Message, out var prev)) { - progressBars = new Dictionary(); + prev.Progress = entry.ProgressValue.Value; + prev.Color = entry.TextColor; + continue; } - - var line = new ProgressBarDto(entry, timestamp); - - progressBars.Add(entry.Message, line); - result.Add(line); } else { - if (type == LineType.ProgressBar) continue; - - result.Add(new TextLineDto(entry, timestamp)); + progressBars = new Dictionary(); + } + + var line = new ProgressBarDto(entry, timestamp); + + progressBars.Add(entry.Message, line); + result.Add(line); + } + else + { + if (type == LineType.ProgressBar) + { + continue; } + + result.Add(new TextLineDto(entry, timestamp)); } } - - return result; } + + return result; } } diff --git a/src/Hangfire.Console/Monitoring/IConsoleApi.cs b/src/Hangfire.Console/Monitoring/IConsoleApi.cs index d351858..3ba7d61 100644 --- a/src/Hangfire.Console/Monitoring/IConsoleApi.cs +++ b/src/Hangfire.Console/Monitoring/IConsoleApi.cs @@ -1,23 +1,22 @@ -using Hangfire.Storage.Monitoring; -using System; +using System; using System.Collections.Generic; using Hangfire.Annotations; +using Hangfire.Storage.Monitoring; -namespace Hangfire.Console.Monitoring +namespace Hangfire.Console.Monitoring; + +/// +/// Console monitoring API interface +/// +[PublicAPI] +public interface IConsoleApi : IDisposable { /// - /// Console monitoring API interface + /// Returns lines for the console session /// - [PublicAPI] - public interface IConsoleApi : IDisposable - { - /// - /// Returns lines for the console session - /// - /// Job identifier - /// Time the processing was started (like, ) - /// Type of lines to return - /// List of console lines - IList GetLines(string jobId, DateTime timestamp, LineType type = LineType.Any); - } + /// Job identifier + /// Time the processing was started (like, ) + /// Type of lines to return + /// List of console lines + IList GetLines(string jobId, DateTime timestamp, LineType type = LineType.Any); } diff --git a/src/Hangfire.Console/Monitoring/LineDto.cs b/src/Hangfire.Console/Monitoring/LineDto.cs index 3958c39..c8cccc0 100644 --- a/src/Hangfire.Console/Monitoring/LineDto.cs +++ b/src/Hangfire.Console/Monitoring/LineDto.cs @@ -1,34 +1,33 @@ -using Hangfire.Console.Serialization; -using System; +using System; +using Hangfire.Console.Serialization; using JetBrains.Annotations; -namespace Hangfire.Console.Monitoring +namespace Hangfire.Console.Monitoring; + +/// +/// Base class for console lines +/// +[PublicAPI] +public abstract class LineDto { - /// - /// Base class for console lines - /// - [PublicAPI] - public abstract class LineDto + internal LineDto(ConsoleLine line, DateTime referenceTimestamp) { - internal LineDto(ConsoleLine line, DateTime referenceTimestamp) - { - Timestamp = referenceTimestamp.AddSeconds(line.TimeOffset); - Color = line.TextColor; - } + Timestamp = referenceTimestamp.AddSeconds(line.TimeOffset); + Color = line.TextColor; + } - /// - /// Returns type of this line - /// - public abstract LineType Type { get; } + /// + /// Returns type of this line + /// + public abstract LineType Type { get; } - /// - /// Returns timestamp for the console line - /// - public DateTime Timestamp { get; } + /// + /// Returns timestamp for the console line + /// + public DateTime Timestamp { get; } - /// - /// Returns HTML color for the console line - /// - public string Color { get; internal set; } - } + /// + /// Returns HTML color for the console line + /// + public string Color { get; internal set; } } diff --git a/src/Hangfire.Console/Monitoring/LineType.cs b/src/Hangfire.Console/Monitoring/LineType.cs index 1ba6a1b..6bed4bf 100644 --- a/src/Hangfire.Console/Monitoring/LineType.cs +++ b/src/Hangfire.Console/Monitoring/LineType.cs @@ -1,24 +1,25 @@ using JetBrains.Annotations; -namespace Hangfire.Console.Monitoring +namespace Hangfire.Console.Monitoring; + +/// +/// Console line type +/// +[PublicAPI] +public enum LineType { /// - /// Console line type + /// Any type (only for filtering) + /// + Any, + + /// + /// Textual line + /// + Text, + + /// + /// Progress bar /// - [PublicAPI] - public enum LineType - { - /// - /// Any type (only for filtering) - /// - Any, - /// - /// Textual line - /// - Text, - /// - /// Progress bar - /// - ProgressBar - } + ProgressBar } diff --git a/src/Hangfire.Console/Monitoring/ProgressBarDto.cs b/src/Hangfire.Console/Monitoring/ProgressBarDto.cs index 8b549fc..06f3486 100644 --- a/src/Hangfire.Console/Monitoring/ProgressBarDto.cs +++ b/src/Hangfire.Console/Monitoring/ProgressBarDto.cs @@ -1,40 +1,40 @@ -using Hangfire.Console.Serialization; -using System; +using System; using System.Globalization; +using Hangfire.Console.Serialization; using JetBrains.Annotations; -namespace Hangfire.Console.Monitoring +namespace Hangfire.Console.Monitoring; + +/// +/// Progress bar line +/// +[PublicAPI] +public class ProgressBarDto : LineDto { - /// - /// Progress bar line - /// - [PublicAPI] - public class ProgressBarDto : LineDto + internal ProgressBarDto(ConsoleLine line, DateTime referenceTimestamp) : base(line, referenceTimestamp) { - internal ProgressBarDto(ConsoleLine line, DateTime referenceTimestamp) : base(line, referenceTimestamp) - { - Id = int.Parse(line.Message, CultureInfo.InvariantCulture); - Name = line.ProgressName; - // ReSharper disable once PossibleInvalidOperationException - Progress = line.ProgressValue.Value; - } + Id = int.Parse(line.Message, CultureInfo.InvariantCulture); + Name = line.ProgressName; - /// - public override LineType Type => LineType.ProgressBar; + // ReSharper disable once PossibleInvalidOperationException + Progress = line.ProgressValue.Value; + } + + /// + public override LineType Type => LineType.ProgressBar; - /// - /// Returns identifier for a progress bar - /// - public int Id { get; } + /// + /// Returns identifier for a progress bar + /// + public int Id { get; } - /// - /// Returns optional name for a progress bar - /// - public string Name { get; } + /// + /// Returns optional name for a progress bar + /// + public string Name { get; } - /// - /// Returns progress value for a progress bar - /// - public double Progress { get; internal set; } - } + /// + /// Returns progress value for a progress bar + /// + public double Progress { get; internal set; } } diff --git a/src/Hangfire.Console/Monitoring/TextLineDto.cs b/src/Hangfire.Console/Monitoring/TextLineDto.cs index 212c062..526b371 100644 --- a/src/Hangfire.Console/Monitoring/TextLineDto.cs +++ b/src/Hangfire.Console/Monitoring/TextLineDto.cs @@ -1,26 +1,25 @@ -using Hangfire.Console.Serialization; -using System; +using System; +using Hangfire.Console.Serialization; using JetBrains.Annotations; -namespace Hangfire.Console.Monitoring +namespace Hangfire.Console.Monitoring; + +/// +/// Text console line +/// +[PublicAPI] +public class TextLineDto : LineDto { - /// - /// Text console line - /// - [PublicAPI] - public class TextLineDto : LineDto + internal TextLineDto(ConsoleLine line, DateTime referenceTimestamp) : base(line, referenceTimestamp) { - internal TextLineDto(ConsoleLine line, DateTime referenceTimestamp) : base(line, referenceTimestamp) - { - Text = line.Message; - } + Text = line.Message; + } - /// - public override LineType Type => LineType.Text; + /// + public override LineType Type => LineType.Text; - /// - /// Returns text for the console line - /// - public string Text { get; } - } + /// + /// Returns text for the console line + /// + public string Text { get; } } diff --git a/src/Hangfire.Console/Progress/DefaultProgressBar.cs b/src/Hangfire.Console/Progress/DefaultProgressBar.cs index 7069e79..8459593 100644 --- a/src/Hangfire.Console/Progress/DefaultProgressBar.cs +++ b/src/Hangfire.Console/Progress/DefaultProgressBar.cs @@ -1,52 +1,62 @@ -using Hangfire.Console.Serialization; -using Hangfire.Console.Server; -using System; +using System; using System.Threading; +using Hangfire.Console.Serialization; +using Hangfire.Console.Server; + +namespace Hangfire.Console.Progress; -namespace Hangfire.Console.Progress +/// +/// Default progress bar. +/// +internal class DefaultProgressBar : IProgressBar { - /// - /// Default progress bar. - /// - internal class DefaultProgressBar : IProgressBar - { - private readonly ConsoleContext _context; - private readonly string _progressBarId; - private string _name; - private string _color; - private double _value; + private readonly ConsoleContext _context; + + private readonly string _progressBarId; - internal DefaultProgressBar(ConsoleContext context, string progressBarId, string name, string color) + private string _color; + + private string _name; + + private double _value; + + internal DefaultProgressBar(ConsoleContext context, string progressBarId, string name, string color) + { + if (string.IsNullOrEmpty(progressBarId)) { - if (string.IsNullOrEmpty(progressBarId)) - throw new ArgumentNullException(nameof(progressBarId)); - - _context = context ?? throw new ArgumentNullException(nameof(context)); - _progressBarId = progressBarId; - _name = name; - _color = color; - _value = -1; + throw new ArgumentNullException(nameof(progressBarId)); } - public void SetValue(int value) + _context = context ?? throw new ArgumentNullException(nameof(context)); + _progressBarId = progressBarId; + _name = name; + _color = color; + _value = -1; + } + + public void SetValue(int value) + { + SetValue((double)value); + } + + public void SetValue(double value) + { + value = Math.Round(value, 1); + + if (value < 0 || value > 100) { - SetValue((double)value); + throw new ArgumentOutOfRangeException(nameof(value), "Value should be in range 0..100"); } - public void SetValue(double value) + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (Interlocked.Exchange(ref _value, value) == value) { - value = Math.Round(value, 1); - - if (value < 0 || value > 100) - throw new ArgumentOutOfRangeException(nameof(value), "Value should be in range 0..100"); + return; + } - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (Interlocked.Exchange(ref _value, value) == value) return; - - _context.AddLine(new ConsoleLine() { Message = _progressBarId, ProgressName = _name, ProgressValue = value, TextColor = _color }); + _context.AddLine(new ConsoleLine { Message = _progressBarId, ProgressName = _name, ProgressValue = value, TextColor = _color }); - _name = null; // write name only once - _color = null; // write color only once - } + _name = null; // write name only once + _color = null; // write color only once } } diff --git a/src/Hangfire.Console/Progress/IProgressBar.cs b/src/Hangfire.Console/Progress/IProgressBar.cs index 9a4763a..895e2f9 100644 --- a/src/Hangfire.Console/Progress/IProgressBar.cs +++ b/src/Hangfire.Console/Progress/IProgressBar.cs @@ -1,20 +1,19 @@ -namespace Hangfire.Console.Progress +namespace Hangfire.Console.Progress; + +/// +/// Progress bar line inside console. +/// +public interface IProgressBar { /// - /// Progress bar line inside console. + /// Updates a value of a progress bar. /// - public interface IProgressBar - { - /// - /// Updates a value of a progress bar. - /// - /// New value - void SetValue(int value); + /// New value + void SetValue(int value); - /// - /// Updates a value of a progress bar. - /// - /// New value - void SetValue(double value); - } + /// + /// Updates a value of a progress bar. + /// + /// New value + void SetValue(double value); } diff --git a/src/Hangfire.Console/Progress/NoOpProgressBar.cs b/src/Hangfire.Console/Progress/NoOpProgressBar.cs index a432b90..1e5c66e 100644 --- a/src/Hangfire.Console/Progress/NoOpProgressBar.cs +++ b/src/Hangfire.Console/Progress/NoOpProgressBar.cs @@ -1,23 +1,24 @@ using System; -namespace Hangfire.Console.Progress +namespace Hangfire.Console.Progress; + +/// +/// No-op progress bar. +/// +internal class NoOpProgressBar : IProgressBar { - /// - /// No-op progress bar. - /// - internal class NoOpProgressBar : IProgressBar + public void SetValue(int value) { - public void SetValue(int value) - { - SetValue((double)value); - } + SetValue((double)value); + } - public void SetValue(double value) - { - value = Math.Round(value, 1); + public void SetValue(double value) + { + value = Math.Round(value, 1); - if (value < 0 || value > 100) - throw new ArgumentOutOfRangeException(nameof(value), "Value should be in range 0..100"); + if (value < 0 || value > 100) + { + throw new ArgumentOutOfRangeException(nameof(value), "Value should be in range 0..100"); } } } diff --git a/src/Hangfire.Console/Progress/ProgressEnumerable.cs b/src/Hangfire.Console/Progress/ProgressEnumerable.cs index 3043a1a..e682c19 100644 --- a/src/Hangfire.Console/Progress/ProgressEnumerable.cs +++ b/src/Hangfire.Console/Progress/ProgressEnumerable.cs @@ -2,169 +2,173 @@ using System.Collections; using System.Collections.Generic; -namespace Hangfire.Console.Progress +namespace Hangfire.Console.Progress; + +/// +/// Non-generic version of wrapper. +/// +internal class ProgressEnumerable : IEnumerable { - /// - /// Non-generic version of wrapper. - /// - internal class ProgressEnumerable : IEnumerable + private readonly int _count; + + private readonly IEnumerable _enumerable; + + private readonly IProgressBar _progressBar; + + public ProgressEnumerable(IEnumerable enumerable, IProgressBar progressBar, int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + _enumerable = enumerable ?? throw new ArgumentNullException(nameof(enumerable)); + _progressBar = progressBar ?? throw new ArgumentNullException(nameof(progressBar)); + _count = count; + } + + public IEnumerator GetEnumerator() => new Enumerator(_enumerable.GetEnumerator(), _progressBar, _count); + + private class Enumerator : IEnumerator, IDisposable { - private readonly IEnumerable _enumerable; + private readonly IEnumerator _enumerator; + private readonly IProgressBar _progressBar; - private readonly int _count; - public ProgressEnumerable(IEnumerable enumerable, IProgressBar progressBar, int count) - { - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count)); + private int _count, _index; - _enumerable = enumerable ?? throw new ArgumentNullException(nameof(enumerable)); - _progressBar = progressBar ?? throw new ArgumentNullException(nameof(progressBar)); + public Enumerator(IEnumerator enumerator, IProgressBar progressBar, int count) + { + _enumerator = enumerator; + _progressBar = progressBar; _count = count; + _index = -1; } - public IEnumerator GetEnumerator() + public void Dispose() { - return new Enumerator(_enumerable.GetEnumerator(), _progressBar, _count); + try + { + (_enumerator as IDisposable)?.Dispose(); + } + finally + { + _progressBar.SetValue(100); + } } - private class Enumerator : IEnumerator, IDisposable - { - private readonly IEnumerator _enumerator; - private readonly IProgressBar _progressBar; - private int _count, _index; + public object Current => _enumerator.Current; - public Enumerator(IEnumerator enumerator, IProgressBar progressBar, int count) + public bool MoveNext() + { + var r = _enumerator.MoveNext(); + if (r) { - _enumerator = enumerator; - _progressBar = progressBar; - _count = count; - _index = -1; - } - - public object Current => _enumerator.Current; + _index++; - public void Dispose() - { - try + if (_index >= _count) { - (_enumerator as IDisposable)?.Dispose(); - } - finally - { - _progressBar.SetValue(100); + // adjust maxCount if overrunned + _count = _index + 1; } + + _progressBar.SetValue(_index * 100.0 / _count); } - public bool MoveNext() - { - var r = _enumerator.MoveNext(); - if (r) - { - _index++; + return r; + } - if (_index >= _count) - { - // adjust maxCount if overrunned - _count = _index + 1; - } + public void Reset() + { + _enumerator.Reset(); + _index = -1; + } + } +} - _progressBar.SetValue(_index * 100.0 / _count); - } - return r; - } +/// +/// Generic version of wrapper. +/// +/// +internal class ProgressEnumerable : IEnumerable +{ + private readonly int _count; - public void Reset() - { - _enumerator.Reset(); - _index = -1; - } + private readonly IEnumerable _enumerable; + + private readonly IProgressBar _progressBar; + + public ProgressEnumerable(IEnumerable enumerable, IProgressBar progressBar, int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); } + + _enumerable = enumerable ?? throw new ArgumentNullException(nameof(enumerable)); + _progressBar = progressBar ?? throw new ArgumentNullException(nameof(progressBar)); + _count = count; } - /// - /// Generic version of wrapper. - /// - /// - internal class ProgressEnumerable : IEnumerable + public IEnumerator GetEnumerator() => new Enumerator(_enumerable.GetEnumerator(), _progressBar, _count); + + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_enumerable.GetEnumerator(), _progressBar, _count); + + private class Enumerator : IEnumerator { - private readonly IEnumerable _enumerable; + private readonly IEnumerator _enumerator; + private readonly IProgressBar _progressBar; - private readonly int _count; - public ProgressEnumerable(IEnumerable enumerable, IProgressBar progressBar, int count) - { - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count)); + private int _count, _index; - _enumerable = enumerable ?? throw new ArgumentNullException(nameof(enumerable)); - _progressBar = progressBar ?? throw new ArgumentNullException(nameof(progressBar)); + public Enumerator(IEnumerator enumerator, IProgressBar progressBar, int count) + { + _enumerator = enumerator; + _progressBar = progressBar; _count = count; + _index = -1; } - public IEnumerator GetEnumerator() - { - return new Enumerator(_enumerable.GetEnumerator(), _progressBar, _count); - } + public T Current => _enumerator.Current; - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_enumerable.GetEnumerator(), _progressBar, _count); - } + object IEnumerator.Current => ((IEnumerator)_enumerator).Current; - private class Enumerator : IEnumerator + public void Dispose() { - private readonly IEnumerator _enumerator; - private readonly IProgressBar _progressBar; - private int _count, _index; - - public Enumerator(IEnumerator enumerator, IProgressBar progressBar, int count) + try { - _enumerator = enumerator; - _progressBar = progressBar; - _count = count; - _index = -1; + _enumerator.Dispose(); } - - public T Current => _enumerator.Current; - - object IEnumerator.Current => ((IEnumerator)_enumerator).Current; - - public void Dispose() + finally { - try - { - _enumerator.Dispose(); - } - finally - { - _progressBar.SetValue(100); - } + _progressBar.SetValue(100); } + } - public bool MoveNext() + public bool MoveNext() + { + var r = _enumerator.MoveNext(); + if (r) { - var r = _enumerator.MoveNext(); - if (r) - { - _index++; + _index++; - if (_index >= _count) - { - // adjust maxCount if overrunned - _count = _index + 1; - } - - _progressBar.SetValue(_index * 100.0 / _count); + if (_index >= _count) + { + // adjust maxCount if overrunned + _count = _index + 1; } - return r; - } - public void Reset() - { - _enumerator.Reset(); - _index = -1; + _progressBar.SetValue(_index * 100.0 / _count); } + + return r; + } + + public void Reset() + { + _enumerator.Reset(); + _index = -1; } } } diff --git a/src/Hangfire.Console/Resources/script.js b/src/Hangfire.Console/Resources/script.js index 9885df4..acbb8de 100644 --- a/src/Hangfire.Console/Resources/script.js +++ b/src/Hangfire.Console/Resources/script.js @@ -10,7 +10,7 @@ var $this = $(this), time = moment($this.data('moment-title'), 'X'); $this.prop('title', time.format('llll')) - .attr('data-container', 'body'); + .attr('data-container', 'body'); }); } @@ -53,7 +53,7 @@ var $pv = $(".pv", $this); pv.attr("style", $pv.attr("style")) - .attr("data-value", $pv.attr("data-value")); + .attr("data-value", $pv.attr("data-value")); $this.addClass("ignore"); }); @@ -122,12 +122,14 @@ this._polling = true; this._el.addClass('active'); - resizeHandler( { target: this._buffer.getHTMLElement() } ); + resizeHandler({target: this._buffer.getHTMLElement()}); window.addResizeListener(this._buffer.getHTMLElement(), resizeHandler); console.log("polling was started"); - setTimeout(function () { self._poll(); }, pollInterval); + setTimeout(function () { + self._poll(); + }, pollInterval); }; Console.prototype._poll = function () { @@ -147,7 +149,7 @@ var self = this; - $.get(pollUrl + this._id, { start: next }, function (data) { + $.get(pollUrl + this._id, {start: next}, function (data) { var $data = $(data), buffer = new hangfire.LineBuffer($data), newLines = $(".line:not(.pb)", $data); @@ -155,9 +157,11 @@ self._el.toggleClass("waiting", newLines.length === 0); }, "html") - .always(function () { - setTimeout(function () { self._poll(); }, pollInterval); - }); + .always(function () { + setTimeout(function () { + self._poll(); + }, pollInterval); + }); }; Console.prototype._endPoll = function () { @@ -176,14 +180,14 @@ function JobProgress(row) { if (!row || row.length !== 1) throw new Error("JobProgress expects jQuery object with a single value"); - + this._row = row; this._progress = null; this._bar = null; this._value = null; } - JobProgress.prototype._create = function() { + JobProgress.prototype._create = function () { var parent = $('td:last-child', this._row); this._progress = $('
\n' + ' \n' + @@ -193,22 +197,22 @@ '
').prependTo(parent); this._bar = $('.bar', this._progress); }; - - JobProgress.prototype._destroy = function() { + + JobProgress.prototype._destroy = function () { if (this._progress) this._progress.remove(); - + this._progress = null; this._bar = null; this._value = null; }; - JobProgress.prototype.update = function(value) { + JobProgress.prototype.update = function (value) { if (typeof value !== 'number' || value < 0) { this._destroy(); return; } - + value = Math.min(Math.round(value), 100); if (!this._progress) { @@ -219,16 +223,16 @@ var r = this._bar.attr('r'), c = Math.PI * r * 2; - + this._bar.css('stroke-dashoffset', ((100 - value) / 100) * c); this._progress.attr('data-value', value); this._value = value; }; - + return JobProgress; })(); - hangfire.JobProgressPoller = (function() { + hangfire.JobProgressPoller = (function () { function JobProgressPoller() { var jobsProgress = {}; $(".js-jobs-list-row").each(function () { @@ -248,8 +252,8 @@ if (this._jobIds.length === 0) return; var self = this; - this._timerCallback = function() { - $.post(pollUrl + 'progress', { 'jobs[]': self._jobIds }, function(data) { + this._timerCallback = function () { + $.post(pollUrl + 'progress', {'jobs[]': self._jobIds}, function (data) { var jobsProgress = self._jobsProgress; Object.getOwnPropertyNames(data).forEach(function (jobId) { var progress = jobsProgress[jobId], @@ -265,7 +269,7 @@ } }); }; - + this._timerId = setTimeout(this._timerCallback, 50); }; @@ -279,15 +283,15 @@ return JobProgressPoller; })(); - + })(jQuery, window.Hangfire = window.Hangfire || {}); $(function () { var path = window.location.pathname; - + if (/\/jobs\/details\/([^/]+)$/.test(path)) { // execute scripts for /jobs/details/ - + $(".console").each(function (index) { var $this = $(this), c = new Hangfire.Console($this); @@ -306,11 +310,11 @@ $(function () { $(".container").on("click", ".console.collapsed", function () { $(this).removeClass("collapsed"); }); - + } else if (/\/jobs\/processing$/.test(path)) { // execute scripts for /jobs/processing - + Hangfire.page._jobProgressPoller = new Hangfire.JobProgressPoller(); Hangfire.page._jobProgressPoller.start(); } -}); \ No newline at end of file +}); diff --git a/src/Hangfire.Console/Resources/style.css b/src/Hangfire.Console/Resources/style.css index 3b42a82..56fb74f 100644 --- a/src/Hangfire.Console/Resources/style.css +++ b/src/Hangfire.Console/Resources/style.css @@ -139,13 +139,15 @@ body { opacity: 0; animation: fadeIn 0.4s ease-out forwards; - /* IE9 knows opacity (but not animation), and hides the line. + /* IE9 knows opacity (but not animation), and hides the line. As a workaround, override this with IE-specific attribute. */ filter: alpha(opacity=100); } @keyframes fadeIn { - to { opacity: 1; } + to { + opacity: 1; + } } /* !!! The following two blocks are important for smooth text animation !!! */ @@ -155,7 +157,7 @@ body { -webkit-font-smoothing: subpixel-antialiased; } -.console.active .line.new, +.console.active .line.new, .console.active .line.new > span[data-moment-title] { background-color: inherit; } @@ -188,7 +190,9 @@ body { } @keyframes loadingDots { - to { width: 27px; } + to { + width: 27px; + } } .progress-circle { @@ -237,4 +241,4 @@ body { .progress-circle .bar { stroke: currentColor; -} \ No newline at end of file +} diff --git a/src/Hangfire.Console/Serialization/ConsoleId.cs b/src/Hangfire.Console/Serialization/ConsoleId.cs index 5b03b36..49741b8 100644 --- a/src/Hangfire.Console/Serialization/ConsoleId.cs +++ b/src/Hangfire.Console/Serialization/ConsoleId.cs @@ -1,123 +1,140 @@ using System; -namespace Hangfire.Console.Serialization +namespace Hangfire.Console.Serialization; + +/// +/// Console identifier +/// +internal class ConsoleId : IEquatable { + private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private string _cachedString; + /// - /// Console identifier + /// Initializes an instance of /// - internal class ConsoleId : IEquatable + /// Job identifier + /// Timestamp + public ConsoleId(string jobId, DateTime timestamp) { - private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - private string _cachedString; - - /// - /// Job identifier - /// - public string JobId { get; } - - /// - /// Timestamp - /// - public long Timestamp { get; } - - /// - /// value as . - /// - public DateTime DateValue => UnixEpoch.AddMilliseconds(Timestamp); - - /// - /// Initializes an instance of - /// - /// Job identifier - /// Timestamp - public ConsoleId(string jobId, DateTime timestamp) + if (string.IsNullOrEmpty(jobId)) { - if (string.IsNullOrEmpty(jobId)) - throw new ArgumentNullException(nameof(jobId)); - - JobId = jobId; - Timestamp = (long)(timestamp - UnixEpoch).TotalMilliseconds; - - if (Timestamp <= 0 || Timestamp > int.MaxValue * 1000L) - throw new ArgumentOutOfRangeException(nameof(timestamp)); + throw new ArgumentNullException(nameof(jobId)); } - /// - /// Initializes an instance of . - /// - /// Job identifier - /// Timestamp - private ConsoleId(string jobId, long timestamp) + JobId = jobId; + Timestamp = (long)(timestamp - UnixEpoch).TotalMilliseconds; + + if (Timestamp <= 0 || Timestamp > int.MaxValue * 1000L) { - JobId = jobId; - Timestamp = timestamp; + throw new ArgumentOutOfRangeException(nameof(timestamp)); } + } - /// - /// Creates an instance of from string representation. - /// - /// String - public static ConsoleId Parse(string value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - if (value.Length < 12) - throw new ArgumentException("Invalid value", nameof(value)); + /// + /// Initializes an instance of . + /// + /// Job identifier + /// Timestamp + private ConsoleId(string jobId, long timestamp) + { + JobId = jobId; + Timestamp = timestamp; + } - // Timestamp is serialized in reverse order for better randomness! + /// + /// Job identifier + /// + public string JobId { get; } - long timestamp = 0; - for (var i = 10; i >= 0; i--) - { - var c = value[i] | 0x20; + /// + /// Timestamp + /// + public long Timestamp { get; } - var x = c is >= '0' and <= '9' ? (c - '0') : c is >= 'a' and <= 'f' ? (c - 'a' + 10) : -1; - if (x == -1) - throw new ArgumentException("Invalid value", nameof(value)); + /// + /// value as . + /// + public DateTime DateValue => UnixEpoch.AddMilliseconds(Timestamp); - timestamp = (timestamp << 4) + x; - } + /// + public bool Equals(ConsoleId other) + { + if (ReferenceEquals(other, null)) + { + return false; + } - return new ConsoleId(value.Substring(11), timestamp) { _cachedString = value }; + if (ReferenceEquals(other, this)) + { + return true; } - /// - public bool Equals(ConsoleId other) + return other.Timestamp == Timestamp + && other.JobId == JobId; + } + + /// + /// Creates an instance of from string representation. + /// + /// String + public static ConsoleId Parse(string value) + { + if (value == null) { - if (ReferenceEquals(other, null)) return false; - if (ReferenceEquals(other, this)) return true; + throw new ArgumentNullException(nameof(value)); + } - return other.Timestamp == Timestamp - && other.JobId == JobId; + if (value.Length < 12) + { + throw new ArgumentException("Invalid value", nameof(value)); } - /// - public override string ToString() + // Timestamp is serialized in reverse order for better randomness! + + long timestamp = 0; + for (var i = 10; i >= 0; i--) { - if (_cachedString == null) + var c = value[i] | 0x20; + + var x = c is >= '0' and <= '9' ? c - '0' : c is >= 'a' and <= 'f' ? c - 'a' + 10 : -1; + if (x == -1) { - var buffer = new char[11 + JobId.Length]; + throw new ArgumentException("Invalid value", nameof(value)); + } + + timestamp = (timestamp << 4) + x; + } - var timestamp = Timestamp; - for (var i = 0; i < 11; i++, timestamp >>= 4) - { - var c = timestamp & 0x0F; - buffer[i] = (c < 10) ? (char)(c + '0') : (char)(c - 10 + 'a'); - } + return new ConsoleId(value.Substring(11), timestamp) { _cachedString = value }; + } - JobId.CopyTo(0, buffer, 11, JobId.Length); + /// + public override string ToString() + { + if (_cachedString == null) + { + var buffer = new char[11 + JobId.Length]; - _cachedString = new string(buffer); + var timestamp = Timestamp; + for (var i = 0; i < 11; i++, timestamp >>= 4) + { + var c = timestamp & 0x0F; + buffer[i] = c < 10 ? (char)(c + '0') : (char)(c - 10 + 'a'); } - return _cachedString; - } + JobId.CopyTo(0, buffer, 11, JobId.Length); - /// - public override bool Equals(object obj) => Equals(obj as ConsoleId); + _cachedString = new string(buffer); + } - /// - public override int GetHashCode() => (JobId.GetHashCode() * 17) ^ Timestamp.GetHashCode(); + return _cachedString; } + + /// + public override bool Equals(object obj) => Equals(obj as ConsoleId); + + /// + public override int GetHashCode() => (JobId.GetHashCode() * 17) ^ Timestamp.GetHashCode(); } diff --git a/src/Hangfire.Console/Serialization/ConsoleLine.cs b/src/Hangfire.Console/Serialization/ConsoleLine.cs index 0a66cf3..28001da 100644 --- a/src/Hangfire.Console/Serialization/ConsoleLine.cs +++ b/src/Hangfire.Console/Serialization/ConsoleLine.cs @@ -1,43 +1,42 @@ using Newtonsoft.Json; -namespace Hangfire.Console.Serialization +namespace Hangfire.Console.Serialization; + +internal class ConsoleLine { - internal class ConsoleLine - { - /// - /// Time offset since console timestamp in fractional seconds - /// - [JsonProperty("t", Required = Required.Always)] - public double TimeOffset { get; set; } + /// + /// Time offset since console timestamp in fractional seconds + /// + [JsonProperty("t", Required = Required.Always)] + public double TimeOffset { get; set; } - /// - /// True if is a Hash reference. - /// - [JsonProperty("r", DefaultValueHandling = DefaultValueHandling.Ignore)] - public bool IsReference { get; set; } + /// + /// True if is a Hash reference. + /// + [JsonProperty("r", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsReference { get; set; } - /// - /// Message text, or message reference, or progress bar id - /// - [JsonProperty("s", Required = Required.Always)] - public string Message { get; set; } + /// + /// Message text, or message reference, or progress bar id + /// + [JsonProperty("s", Required = Required.Always)] + public string Message { get; set; } - /// - /// Text color for this message - /// - [JsonProperty("c", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string TextColor { get; set; } + /// + /// Text color for this message + /// + [JsonProperty("c", DefaultValueHandling = DefaultValueHandling.Ignore)] + public string TextColor { get; set; } - /// - /// Value update for a progress bar - /// - [JsonProperty("p", DefaultValueHandling = DefaultValueHandling.Ignore)] - public double? ProgressValue { get; set; } + /// + /// Value update for a progress bar + /// + [JsonProperty("p", DefaultValueHandling = DefaultValueHandling.Ignore)] + public double? ProgressValue { get; set; } - /// - /// Optional name for a progress bar - /// - [JsonProperty("n", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string ProgressName { get; set; } - } + /// + /// Optional name for a progress bar + /// + [JsonProperty("n", DefaultValueHandling = DefaultValueHandling.Ignore)] + public string ProgressName { get; set; } } diff --git a/src/Hangfire.Console/Server/ConsoleContext.cs b/src/Hangfire.Console/Server/ConsoleContext.cs index 9458c7c..ea6ab8c 100644 --- a/src/Hangfire.Console/Server/ConsoleContext.cs +++ b/src/Hangfire.Console/Server/ConsoleContext.cs @@ -6,99 +6,103 @@ using Hangfire.Console.Storage; using Hangfire.Server; -namespace Hangfire.Console.Server +namespace Hangfire.Console.Server; + +internal class ConsoleContext { - internal class ConsoleContext + private readonly ConsoleId _consoleId; + + private readonly IConsoleStorage _storage; + + private double _lastTimeOffset; + + private int _nextProgressBarId; + + public ConsoleContext(ConsoleId consoleId, IConsoleStorage storage) { - private readonly ConsoleId _consoleId; - private readonly IConsoleStorage _storage; - private double _lastTimeOffset; - private int _nextProgressBarId; + _consoleId = consoleId ?? throw new ArgumentNullException(nameof(consoleId)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - public static ConsoleContext FromPerformContext(PerformContext context) - { - if (context == null) - { - // PerformContext might be null because of refactoring, or during tests - return null; - } + _lastTimeOffset = 0; + _nextProgressBarId = 0; - if (!context.Items.ContainsKey("ConsoleContext")) - { - // Absence of ConsoleContext means ConsoleServerFilter was not properly added - return null; - } + _storage.InitConsole(_consoleId); + } + + public ConsoleTextColor TextColor { get; set; } - return (ConsoleContext)context.Items["ConsoleContext"]; + public static ConsoleContext FromPerformContext(PerformContext context) + { + if (context == null) + { + // PerformContext might be null because of refactoring, or during tests + return null; } - public ConsoleContext(ConsoleId consoleId, IConsoleStorage storage) + if (!context.Items.ContainsKey("ConsoleContext")) { - _consoleId = consoleId ?? throw new ArgumentNullException(nameof(consoleId)); - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + // Absence of ConsoleContext means ConsoleServerFilter was not properly added + return null; + } - _lastTimeOffset = 0; - _nextProgressBarId = 0; + return (ConsoleContext)context.Items["ConsoleContext"]; + } - _storage.InitConsole(_consoleId); + public void AddLine(ConsoleLine line) + { + if (line == null) + { + throw new ArgumentNullException(nameof(line)); } - public ConsoleTextColor TextColor { get; set; } - - public void AddLine(ConsoleLine line) + lock (this) { - if (line == null) - throw new ArgumentNullException(nameof(line)); + line.TimeOffset = Math.Round((DateTime.UtcNow - _consoleId.DateValue).TotalSeconds, 3); - lock (this) + if (_lastTimeOffset >= line.TimeOffset) { - line.TimeOffset = Math.Round((DateTime.UtcNow - _consoleId.DateValue).TotalSeconds, 3); - - if (_lastTimeOffset >= line.TimeOffset) - { - // prevent duplicate lines collapsing - line.TimeOffset = _lastTimeOffset + 0.0001; - } + // prevent duplicate lines collapsing + line.TimeOffset = _lastTimeOffset + 0.0001; + } - _lastTimeOffset = line.TimeOffset; + _lastTimeOffset = line.TimeOffset; - _storage.AddLine(_consoleId, line); - } + _storage.AddLine(_consoleId, line); } + } - public void WriteLine(string value, ConsoleTextColor color) - { - AddLine(new ConsoleLine { Message = value ?? "", TextColor = color ?? TextColor }); - } + public void WriteLine(string value, ConsoleTextColor color) + { + AddLine(new ConsoleLine { Message = value ?? "", TextColor = color ?? TextColor }); + } - public IProgressBar WriteProgressBar(string name, double value, ConsoleTextColor color) - { - var progressBarId = Interlocked.Increment(ref _nextProgressBarId); + public IProgressBar WriteProgressBar(string name, double value, ConsoleTextColor color) + { + var progressBarId = Interlocked.Increment(ref _nextProgressBarId); - var progressBar = new DefaultProgressBar(this, progressBarId.ToString(CultureInfo.InvariantCulture), name, color); + var progressBar = new DefaultProgressBar(this, progressBarId.ToString(CultureInfo.InvariantCulture), name, color); - // set initial value - progressBar.SetValue(value); + // set initial value + progressBar.SetValue(value); - return progressBar; - } + return progressBar; + } - public void Expire(TimeSpan expireIn) - { - _storage.Expire(_consoleId, expireIn); - } + public void Expire(TimeSpan expireIn) + { + _storage.Expire(_consoleId, expireIn); + } - public void FixExpiration() + public void FixExpiration() + { + var ttl = _storage.GetConsoleTtl(_consoleId); + if (ttl <= TimeSpan.Zero) { - var ttl = _storage.GetConsoleTtl(_consoleId); - if (ttl <= TimeSpan.Zero) - { - // ConsoleApplyStateFilter not called yet, or current job state is not final. - // Either way, there's no need to expire console here. - return; - } - - _storage.Expire(_consoleId, ttl); + // ConsoleApplyStateFilter not called yet, or current job state is not final. + // Either way, there's no need to expire console here. + return; } + + _storage.Expire(_consoleId, ttl); } } diff --git a/src/Hangfire.Console/Server/ConsoleServerFilter.cs b/src/Hangfire.Console/Server/ConsoleServerFilter.cs index df9532d..06e9248 100644 --- a/src/Hangfire.Console/Server/ConsoleServerFilter.cs +++ b/src/Hangfire.Console/Server/ConsoleServerFilter.cs @@ -1,68 +1,67 @@ -using Hangfire.Common; +using System; +using Hangfire.Common; using Hangfire.Console.Serialization; using Hangfire.Console.Storage; using Hangfire.Server; using Hangfire.States; -using System; -namespace Hangfire.Console.Server +namespace Hangfire.Console.Server; + +/// +/// Server filter to initialize and cleanup console environment. +/// +internal class ConsoleServerFilter : IServerFilter { - /// - /// Server filter to initialize and cleanup console environment. - /// - internal class ConsoleServerFilter : IServerFilter + private readonly ConsoleOptions _options; + + public ConsoleServerFilter(ConsoleOptions options) { - private readonly ConsoleOptions _options; + _options = options ?? throw new ArgumentNullException(nameof(options)); + } - public ConsoleServerFilter(ConsoleOptions options) + public void OnPerforming(PerformingContext filterContext) + { + var state = filterContext.Connection.GetStateData(filterContext.BackgroundJob.Id); + + if (state == null) { - _options = options ?? throw new ArgumentNullException(nameof(options)); + // State for job not found? + return; } - public void OnPerforming(PerformingContext filterContext) + if (!string.Equals(state.Name, ProcessingState.StateName, StringComparison.OrdinalIgnoreCase)) { - var state = filterContext.Connection.GetStateData(filterContext.BackgroundJob.Id); - - if (state == null) - { - // State for job not found? - return; - } + // Not in Processing state? Something is really off... + return; + } - if (!string.Equals(state.Name, ProcessingState.StateName, StringComparison.OrdinalIgnoreCase)) - { - // Not in Processing state? Something is really off... - return; - } - - var startedAt = JobHelper.DeserializeDateTime(state.Data["StartedAt"]); + var startedAt = JobHelper.DeserializeDateTime(state.Data["StartedAt"]); - filterContext.Items["ConsoleContext"] = new ConsoleContext( - new ConsoleId(filterContext.BackgroundJob.Id, startedAt), - new ConsoleStorage(filterContext.Connection)); - } + filterContext.Items["ConsoleContext"] = new ConsoleContext( + new ConsoleId(filterContext.BackgroundJob.Id, startedAt), + new ConsoleStorage(filterContext.Connection)); + } - public void OnPerformed(PerformedContext filterContext) + public void OnPerformed(PerformedContext filterContext) + { + if (_options.FollowJobRetentionPolicy) { - if (_options.FollowJobRetentionPolicy) - { - // Console sessions follow parent job expiration. - // Normally, ConsoleApplyStateFilter will update expiration for all consoles, no extra work needed. + // Console sessions follow parent job expiration. + // Normally, ConsoleApplyStateFilter will update expiration for all consoles, no extra work needed. - // If the running job is deleted from the Dashboard, ConsoleApplyStateFilter will be called immediately, - // but the job will continue running, unless IJobCancellationToken.ThrowIfCancellationRequested() is met. - // If anything is written to console after the job was deleted, it won't get a correct expiration assigned. + // If the running job is deleted from the Dashboard, ConsoleApplyStateFilter will be called immediately, + // but the job will continue running, unless IJobCancellationToken.ThrowIfCancellationRequested() is met. + // If anything is written to console after the job was deleted, it won't get a correct expiration assigned. - // Need to re-apply expiration to prevent those records from becoming eternal garbage. - ConsoleContext.FromPerformContext(filterContext)?.FixExpiration(); - } - else - { - ConsoleContext.FromPerformContext(filterContext)?.Expire(_options.ExpireIn); - } - - // remove console context to prevent further writes from filters - filterContext.Items.Remove("ConsoleContext"); + // Need to re-apply expiration to prevent those records from becoming eternal garbage. + ConsoleContext.FromPerformContext(filterContext)?.FixExpiration(); } + else + { + ConsoleContext.FromPerformContext(filterContext)?.Expire(_options.ExpireIn); + } + + // remove console context to prevent further writes from filters + filterContext.Items.Remove("ConsoleContext"); } } diff --git a/src/Hangfire.Console/States/ConsoleApplyStateFilter.cs b/src/Hangfire.Console/States/ConsoleApplyStateFilter.cs index 58f443b..828526b 100644 --- a/src/Hangfire.Console/States/ConsoleApplyStateFilter.cs +++ b/src/Hangfire.Console/States/ConsoleApplyStateFilter.cs @@ -1,60 +1,57 @@ -using Hangfire.States; -using System; +using System; using System.Linq; -using Hangfire.Storage; -using Hangfire.Console.Serialization; using Hangfire.Common; +using Hangfire.Console.Serialization; using Hangfire.Console.Storage; +using Hangfire.States; +using Hangfire.Storage; + +namespace Hangfire.Console.States; -namespace Hangfire.Console.States +internal class ConsoleApplyStateFilter : IApplyStateFilter { - internal class ConsoleApplyStateFilter : IApplyStateFilter + private readonly ConsoleOptions _options; + + public ConsoleApplyStateFilter(ConsoleOptions options) { - private readonly ConsoleOptions _options; + _options = options ?? throw new ArgumentNullException(nameof(options)); + } - public ConsoleApplyStateFilter(ConsoleOptions options) + public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) + { + if (!_options.FollowJobRetentionPolicy) { - _options = options ?? throw new ArgumentNullException(nameof(options)); + // Console sessions use their own expiration timeout. + // Do not expire here, will be expired by ConsoleServerFilter. + return; } - - public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) + + var jobDetails = context.Storage.GetMonitoringApi().JobDetails(context.BackgroundJob.Id); + if (jobDetails == null || jobDetails.History == null) { - if (!_options.FollowJobRetentionPolicy) - { - // Console sessions use their own expiration timeout. - // Do not expire here, will be expired by ConsoleServerFilter. - return; - } + // WTF?! + return; + } - var jobDetails = context.Storage.GetMonitoringApi().JobDetails(context.BackgroundJob.Id); - if (jobDetails == null || jobDetails.History == null) - { - // WTF?! - return; - } + var expiration = new ConsoleExpirationTransaction((JobStorageTransaction)transaction); - var expiration = new ConsoleExpirationTransaction((JobStorageTransaction)transaction); + foreach (var state in jobDetails.History.Where(x => x.StateName == ProcessingState.StateName)) + { + var consoleId = new ConsoleId(context.BackgroundJob.Id, JobHelper.DeserializeDateTime(state.Data["StartedAt"])); - foreach (var state in jobDetails.History.Where(x => x.StateName == ProcessingState.StateName)) + if (context.NewState.IsFinal) { - var consoleId = new ConsoleId(context.BackgroundJob.Id, JobHelper.DeserializeDateTime(state.Data["StartedAt"])); - - if (context.NewState.IsFinal) - { - // Job in final state is a subject for expiration. - // To keep storage clean, its console sessions should also be expired. - expiration.Expire(consoleId, context.JobExpirationTimeout); - } - else - { - // Job will be persisted, so should its console sessions. - expiration.Persist(consoleId); - } + // Job in final state is a subject for expiration. + // To keep storage clean, its console sessions should also be expired. + expiration.Expire(consoleId, context.JobExpirationTimeout); + } + else + { + // Job will be persisted, so should its console sessions. + expiration.Persist(consoleId); } - } - - public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction) - { } } + + public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction) { } } diff --git a/src/Hangfire.Console/Storage/ConsoleExpirationTransaction.cs b/src/Hangfire.Console/Storage/ConsoleExpirationTransaction.cs index 0aae43a..1a8076a 100644 --- a/src/Hangfire.Console/Storage/ConsoleExpirationTransaction.cs +++ b/src/Hangfire.Console/Storage/ConsoleExpirationTransaction.cs @@ -2,50 +2,53 @@ using Hangfire.Console.Serialization; using Hangfire.Storage; -namespace Hangfire.Console.Storage +namespace Hangfire.Console.Storage; + +internal class ConsoleExpirationTransaction : IDisposable { - internal class ConsoleExpirationTransaction : IDisposable + private readonly JobStorageTransaction _transaction; + + public ConsoleExpirationTransaction(JobStorageTransaction transaction) { - private readonly JobStorageTransaction _transaction; + _transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); + } - public ConsoleExpirationTransaction(JobStorageTransaction transaction) - { - _transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); - } + public void Dispose() + { + _transaction.Dispose(); + } - public void Dispose() + public void Expire(ConsoleId consoleId, TimeSpan expireIn) + { + if (consoleId == null) { - _transaction.Dispose(); + throw new ArgumentNullException(nameof(consoleId)); } - public void Expire(ConsoleId consoleId, TimeSpan expireIn) - { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); - - _transaction.ExpireSet(consoleId.GetSetKey(), expireIn); - _transaction.ExpireHash(consoleId.GetHashKey(), expireIn); + _transaction.ExpireSet(consoleId.GetSetKey(), expireIn); + _transaction.ExpireHash(consoleId.GetHashKey(), expireIn); - // After upgrading to Hangfire.Console version with new keys, - // there may be existing background jobs with console attached - // to the previous keys. We should expire them also. - _transaction.ExpireSet(consoleId.GetOldConsoleKey(), expireIn); - _transaction.ExpireHash(consoleId.GetOldConsoleKey(), expireIn); - } + // After upgrading to Hangfire.Console version with new keys, + // there may be existing background jobs with console attached + // to the previous keys. We should expire them also. + _transaction.ExpireSet(consoleId.GetOldConsoleKey(), expireIn); + _transaction.ExpireHash(consoleId.GetOldConsoleKey(), expireIn); + } - public void Persist(ConsoleId consoleId) + public void Persist(ConsoleId consoleId) + { + if (consoleId == null) { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + throw new ArgumentNullException(nameof(consoleId)); + } - _transaction.PersistSet(consoleId.GetSetKey()); - _transaction.PersistHash(consoleId.GetHashKey()); + _transaction.PersistSet(consoleId.GetSetKey()); + _transaction.PersistHash(consoleId.GetHashKey()); - // After upgrading to Hangfire.Console version with new keys, - // there may be existing background jobs with console attached - // to the previous keys. We should persist them also. - _transaction.PersistSet(consoleId.GetOldConsoleKey()); - _transaction.PersistHash(consoleId.GetOldConsoleKey()); - } + // After upgrading to Hangfire.Console version with new keys, + // there may be existing background jobs with console attached + // to the previous keys. We should persist them also. + _transaction.PersistSet(consoleId.GetOldConsoleKey()); + _transaction.PersistHash(consoleId.GetOldConsoleKey()); } } diff --git a/src/Hangfire.Console/Storage/ConsoleIdExtensions.cs b/src/Hangfire.Console/Storage/ConsoleIdExtensions.cs index 2937a74..ae1ff59 100644 --- a/src/Hangfire.Console/Storage/ConsoleIdExtensions.cs +++ b/src/Hangfire.Console/Storage/ConsoleIdExtensions.cs @@ -1,13 +1,12 @@ using Hangfire.Console.Serialization; -namespace Hangfire.Console.Storage +namespace Hangfire.Console.Storage; + +internal static class ConsoleIdExtensions { - internal static class ConsoleIdExtensions - { - public static string GetSetKey(this ConsoleId consoleId) => $"console:{consoleId}"; + public static string GetSetKey(this ConsoleId consoleId) => $"console:{consoleId}"; - public static string GetHashKey(this ConsoleId consoleId) => $"console:refs:{consoleId}"; + public static string GetHashKey(this ConsoleId consoleId) => $"console:refs:{consoleId}"; - public static string GetOldConsoleKey(this ConsoleId consoleId) => consoleId.ToString(); - } + public static string GetOldConsoleKey(this ConsoleId consoleId) => consoleId.ToString(); } diff --git a/src/Hangfire.Console/Storage/ConsoleStorage.cs b/src/Hangfire.Console/Storage/ConsoleStorage.cs index b67b640..0a82728 100644 --- a/src/Hangfire.Console/Storage/ConsoleStorage.cs +++ b/src/Hangfire.Console/Storage/ConsoleStorage.cs @@ -1,222 +1,249 @@ using System; using System.Collections.Generic; using System.Globalization; +using Hangfire.Common; using Hangfire.Console.Serialization; using Hangfire.Storage; -using Hangfire.Common; -namespace Hangfire.Console.Storage +namespace Hangfire.Console.Storage; + +internal class ConsoleStorage : IConsoleStorage { - internal class ConsoleStorage : IConsoleStorage - { - private const int ValueFieldLimit = 256; + private const int ValueFieldLimit = 256; - private readonly JobStorageConnection _connection; + private readonly JobStorageConnection _connection; - public ConsoleStorage(IStorageConnection connection) + public ConsoleStorage(IStorageConnection connection) + { + if (connection == null) { - if (connection == null) - throw new ArgumentNullException(nameof(connection)); - - if (!(connection is JobStorageConnection jobStorageConnection)) - throw new NotSupportedException("Storage connections must implement JobStorageConnection"); - - _connection = jobStorageConnection; + throw new ArgumentNullException(nameof(connection)); } - public void Dispose() + if (!(connection is JobStorageConnection jobStorageConnection)) { - _connection.Dispose(); + throw new NotSupportedException("Storage connections must implement JobStorageConnection"); } - public void InitConsole(ConsoleId consoleId) + _connection = jobStorageConnection; + } + + public void Dispose() + { + _connection.Dispose(); + } + + public void InitConsole(ConsoleId consoleId) + { + if (consoleId == null) { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + throw new ArgumentNullException(nameof(consoleId)); + } + + // We add an extra "jobId" record into Hash for console, + // to correctly track TTL even if console contains no lines - // We add an extra "jobId" record into Hash for console, - // to correctly track TTL even if console contains no lines + using var transaction = _connection.CreateWriteTransaction(); - using var transaction = _connection.CreateWriteTransaction(); + if (transaction is not JobStorageTransaction) + { + throw new NotSupportedException("Storage tranactions must implement JobStorageTransaction"); + } - if (transaction is not JobStorageTransaction) - throw new NotSupportedException("Storage tranactions must implement JobStorageTransaction"); + transaction.SetRangeInHash(consoleId.GetHashKey(), new[] { new KeyValuePair("jobId", consoleId.JobId) }); - transaction.SetRangeInHash(consoleId.GetHashKey(), new[] { new KeyValuePair("jobId", consoleId.JobId) }); + transaction.Commit(); + } - transaction.Commit(); + public void AddLine(ConsoleId consoleId, ConsoleLine line) + { + if (consoleId == null) + { + throw new ArgumentNullException(nameof(consoleId)); } - public void AddLine(ConsoleId consoleId, ConsoleLine line) + if (line == null) { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); - if (line == null) - throw new ArgumentNullException(nameof(line)); - if (line.IsReference) - throw new ArgumentException("Cannot add reference directly", nameof(line)); + throw new ArgumentNullException(nameof(line)); + } + + if (line.IsReference) + { + throw new ArgumentException("Cannot add reference directly", nameof(line)); + } + + using var tran = _connection.CreateWriteTransaction(); - using var tran = _connection.CreateWriteTransaction(); + // check if encoded message fits into Set's Value field - // check if encoded message fits into Set's Value field + string value; - string value; + if (line.Message.Length > ValueFieldLimit - 36) + { + // pretty sure it won't fit + // (36 is an upper bound for JSON formatting, TimeOffset and TextColor) + value = null; + } + else + { + // try to encode and see if it fits + value = SerializationHelper.Serialize(line); - if (line.Message.Length > ValueFieldLimit - 36) + if (value.Length > ValueFieldLimit) { - // pretty sure it won't fit - // (36 is an upper bound for JSON formatting, TimeOffset and TextColor) value = null; } - else - { - // try to encode and see if it fits - value = SerializationHelper.Serialize(line); + } - if (value.Length > ValueFieldLimit) - { - value = null; - } - } + if (value == null) + { + var referenceKey = Guid.NewGuid().ToString("N"); - if (value == null) - { - var referenceKey = Guid.NewGuid().ToString("N"); + tran.SetRangeInHash(consoleId.GetHashKey(), new[] { new KeyValuePair(referenceKey, line.Message) }); - tran.SetRangeInHash(consoleId.GetHashKey(), new[] { new KeyValuePair(referenceKey, line.Message) }); + line.Message = referenceKey; + line.IsReference = true; - line.Message = referenceKey; - line.IsReference = true; + value = SerializationHelper.Serialize(line); + } - value = SerializationHelper.Serialize(line); - } + tran.AddToSet(consoleId.GetSetKey(), value, line.TimeOffset); - tran.AddToSet(consoleId.GetSetKey(), value, line.TimeOffset); + if (line.ProgressValue.HasValue && line.Message == "1") + { + var progress = line.ProgressValue.Value.ToString(CultureInfo.InvariantCulture); - if (line.ProgressValue.HasValue && line.Message == "1") - { - var progress = line.ProgressValue.Value.ToString(CultureInfo.InvariantCulture); + tran.SetRangeInHash(consoleId.GetHashKey(), new[] { new KeyValuePair("progress", progress) }); + } - tran.SetRangeInHash(consoleId.GetHashKey(), new[] { new KeyValuePair("progress", progress) }); - } + tran.Commit(); + } - tran.Commit(); + public TimeSpan GetConsoleTtl(ConsoleId consoleId) + { + if (consoleId == null) + { + throw new ArgumentNullException(nameof(consoleId)); } - public TimeSpan GetConsoleTtl(ConsoleId consoleId) - { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + return _connection.GetHashTtl(consoleId.GetHashKey()); + } - return _connection.GetHashTtl(consoleId.GetHashKey()); + public void Expire(ConsoleId consoleId, TimeSpan expireIn) + { + if (consoleId == null) + { + throw new ArgumentNullException(nameof(consoleId)); } - public void Expire(ConsoleId consoleId, TimeSpan expireIn) - { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + using var tran = (JobStorageTransaction)_connection.CreateWriteTransaction(); + using var expiration = new ConsoleExpirationTransaction(tran); - using var tran = (JobStorageTransaction)_connection.CreateWriteTransaction(); - using var expiration = new ConsoleExpirationTransaction(tran); + expiration.Expire(consoleId, expireIn); - expiration.Expire(consoleId, expireIn); + tran.Commit(); + } - tran.Commit(); + public int GetLineCount(ConsoleId consoleId) + { + if (consoleId == null) + { + throw new ArgumentNullException(nameof(consoleId)); } - public int GetLineCount(ConsoleId consoleId) - { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + var result = (int)_connection.GetSetCount(consoleId.GetSetKey()); - var result = (int)_connection.GetSetCount(consoleId.GetSetKey()); + if (result == 0) + { + // Read operations should be backwards compatible and use + // old keys, if new one don't contain any data. + return (int)_connection.GetSetCount(consoleId.GetOldConsoleKey()); + } - if (result == 0) - { - // Read operations should be backwards compatible and use - // old keys, if new one don't contain any data. - return (int)_connection.GetSetCount(consoleId.GetOldConsoleKey()); - } + return result; + } - return result; + public IEnumerable GetLines(ConsoleId consoleId, int start, int end) + { + if (consoleId == null) + { + throw new ArgumentNullException(nameof(consoleId)); } - public IEnumerable GetLines(ConsoleId consoleId, int start, int end) - { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + var useOldKeys = false; + var items = _connection.GetRangeFromSet(consoleId.GetSetKey(), start, end); - var useOldKeys = false; - var items = _connection.GetRangeFromSet(consoleId.GetSetKey(), start, end); + if (items == null || items.Count == 0) + { + // Read operations should be backwards compatible and use + // old keys, if new one don't contain any data. + items = _connection.GetRangeFromSet(consoleId.GetOldConsoleKey(), start, end); + useOldKeys = true; + } - if (items == null || items.Count == 0) - { - // Read operations should be backwards compatible and use - // old keys, if new one don't contain any data. - items = _connection.GetRangeFromSet(consoleId.GetOldConsoleKey(), start, end); - useOldKeys = true; - } + foreach (var item in items) + { + var line = SerializationHelper.Deserialize(item); - foreach (var item in items) + if (line.IsReference) { - var line = SerializationHelper.Deserialize(item); - - if (line.IsReference) + if (useOldKeys) { - if (useOldKeys) + try { - try - { - line.Message = _connection.GetValueFromHash(consoleId.GetOldConsoleKey(), line.Message); - } - catch - { - // This may happen, when using Hangfire.Redis storage and having - // background job, whose console session was stored using old key - // format. - } + line.Message = _connection.GetValueFromHash(consoleId.GetOldConsoleKey(), line.Message); } - else + catch { - line.Message = _connection.GetValueFromHash(consoleId.GetHashKey(), line.Message); + // This may happen, when using Hangfire.Redis storage and having + // background job, whose console session was stored using old key + // format. } - - line.IsReference = false; + } + else + { + line.Message = _connection.GetValueFromHash(consoleId.GetHashKey(), line.Message); } - yield return line; + line.IsReference = false; } + + yield return line; } + } - public StateData GetState(ConsoleId consoleId) + public StateData GetState(ConsoleId consoleId) + { + if (consoleId == null) { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); - - return _connection.GetStateData(consoleId.JobId); + throw new ArgumentNullException(nameof(consoleId)); } - public double? GetProgress(ConsoleId consoleId) + return _connection.GetStateData(consoleId.JobId); + } + + public double? GetProgress(ConsoleId consoleId) + { + if (consoleId == null) { - if (consoleId == null) - throw new ArgumentNullException(nameof(consoleId)); + throw new ArgumentNullException(nameof(consoleId)); + } - var progress = _connection.GetValueFromHash(consoleId.GetHashKey(), "progress"); - if (string.IsNullOrEmpty(progress)) - { - // progress value is not set - return null; - } + var progress = _connection.GetValueFromHash(consoleId.GetHashKey(), "progress"); + if (string.IsNullOrEmpty(progress)) + { + // progress value is not set + return null; + } - try - { - return double.Parse(progress, CultureInfo.InvariantCulture); - } - catch (Exception) - { - // corrupted data? - return null; - } + try + { + return double.Parse(progress, CultureInfo.InvariantCulture); + } + catch (Exception) + { + // corrupted data? + return null; } } } diff --git a/src/Hangfire.Console/Storage/IConsoleStorage.cs b/src/Hangfire.Console/Storage/IConsoleStorage.cs index c54511a..9c83b91 100644 --- a/src/Hangfire.Console/Storage/IConsoleStorage.cs +++ b/src/Hangfire.Console/Storage/IConsoleStorage.cs @@ -1,60 +1,59 @@ -using Hangfire.Console.Serialization; -using Hangfire.Storage; -using System; +using System; using System.Collections.Generic; +using Hangfire.Console.Serialization; +using Hangfire.Storage; + +namespace Hangfire.Console.Storage; -namespace Hangfire.Console.Storage +/// +/// Abstraction over Hangfire's storage API +/// +internal interface IConsoleStorage : IDisposable { /// - /// Abstraction over Hangfire's storage API + /// Returns number of lines for console. + /// + /// Console identifier + int GetLineCount(ConsoleId consoleId); + + /// + /// Returns range of lines for console. + /// + /// Console identifier + /// Start index (inclusive) + /// End index (inclusive) + IEnumerable GetLines(ConsoleId consoleId, int start, int end); + + /// + /// Initializes console. + /// + /// Console identifier + void InitConsole(ConsoleId consoleId); + + /// + /// Adds line to console. + /// + /// Console identifier + /// Line to add + void AddLine(ConsoleId consoleId, ConsoleLine line); + + /// + /// Returns current expiration TTL for console. + /// If console is not expired, returns negative . + /// + /// Console identifier + TimeSpan GetConsoleTtl(ConsoleId consoleId); + + /// + /// Expire data for console. + /// + /// Console identifier + /// Expiration time + void Expire(ConsoleId consoleId, TimeSpan expireIn); + + /// + /// Returns last (current) state of the console's parent job. /// - internal interface IConsoleStorage : IDisposable - { - /// - /// Returns number of lines for console. - /// - /// Console identifier - int GetLineCount(ConsoleId consoleId); - - /// - /// Returns range of lines for console. - /// - /// Console identifier - /// Start index (inclusive) - /// End index (inclusive) - IEnumerable GetLines(ConsoleId consoleId, int start, int end); - - /// - /// Initializes console. - /// - /// Console identifier - void InitConsole(ConsoleId consoleId); - - /// - /// Adds line to console. - /// - /// Console identifier - /// Line to add - void AddLine(ConsoleId consoleId, ConsoleLine line); - - /// - /// Returns current expiration TTL for console. - /// If console is not expired, returns negative . - /// - /// Console identifier - TimeSpan GetConsoleTtl(ConsoleId consoleId); - - /// - /// Expire data for console. - /// - /// Console identifier - /// Expiration time - void Expire(ConsoleId consoleId, TimeSpan expireIn); - - /// - /// Returns last (current) state of the console's parent job. - /// - /// Console identifier - StateData GetState(ConsoleId consoleId); - } + /// Console identifier + StateData GetState(ConsoleId consoleId); } diff --git a/src/Hangfire.Console/Support/CompositeDispatcher.cs b/src/Hangfire.Console/Support/CompositeDispatcher.cs index 4f65f93..0503d35 100644 --- a/src/Hangfire.Console/Support/CompositeDispatcher.cs +++ b/src/Hangfire.Console/Support/CompositeDispatcher.cs @@ -3,41 +3,46 @@ using System.Threading.Tasks; // ReSharper disable once CheckNamespace -namespace Hangfire.Dashboard.Extensions +namespace Hangfire.Dashboard.Extensions; + +/// +/// Dispatcher that combines output from several other dispatchers. +/// Used internally by . +/// +internal class CompositeDispatcher : IDashboardDispatcher { - /// - /// Dispatcher that combines output from several other dispatchers. - /// Used internally by . - /// - internal class CompositeDispatcher : IDashboardDispatcher + private readonly List _dispatchers; + + public CompositeDispatcher(params IDashboardDispatcher[] dispatchers) { - private readonly List _dispatchers; + _dispatchers = new List(dispatchers); + } - public CompositeDispatcher(params IDashboardDispatcher[] dispatchers) + public async Task Dispatch(DashboardContext context) + { + if (context == null) { - _dispatchers = new List(dispatchers); + throw new ArgumentNullException(nameof(context)); } - public void AddDispatcher(IDashboardDispatcher dispatcher) + if (_dispatchers.Count == 0) { - if (dispatcher == null) - throw new ArgumentNullException(nameof(dispatcher)); - - _dispatchers.Add(dispatcher); + throw new InvalidOperationException("CompositeDispatcher should contain at least one dispatcher"); } - public async Task Dispatch(DashboardContext context) + foreach (var dispatcher in _dispatchers) { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - if (_dispatchers.Count == 0) - throw new InvalidOperationException("CompositeDispatcher should contain at least one dispatcher"); + await dispatcher.Dispatch(context); + } + } - foreach (var dispatcher in _dispatchers) - { - await dispatcher.Dispatch(context); - } + public void AddDispatcher(IDashboardDispatcher dispatcher) + { + if (dispatcher == null) + { + throw new ArgumentNullException(nameof(dispatcher)); } + + _dispatchers.Add(dispatcher); } } diff --git a/src/Hangfire.Console/Support/EmbeddedResourceDispatcher.cs b/src/Hangfire.Console/Support/EmbeddedResourceDispatcher.cs index ce56b3c..2ef285c 100644 --- a/src/Hangfire.Console/Support/EmbeddedResourceDispatcher.cs +++ b/src/Hangfire.Console/Support/EmbeddedResourceDispatcher.cs @@ -3,56 +3,61 @@ using System.Threading.Tasks; // ReSharper disable once CheckNamespace -namespace Hangfire.Dashboard.Extensions +namespace Hangfire.Dashboard.Extensions; + +/// +/// Alternative to built-in EmbeddedResourceDispatcher, which (for some reasons) is not public. +/// +internal class EmbeddedResourceDispatcher : IDashboardDispatcher { - /// - /// Alternative to built-in EmbeddedResourceDispatcher, which (for some reasons) is not public. - /// - internal class EmbeddedResourceDispatcher : IDashboardDispatcher - { - private readonly Assembly _assembly; - private readonly string _resourceName; - private readonly string _contentType; + private readonly Assembly _assembly; - public EmbeddedResourceDispatcher(Assembly assembly, string resourceName, string contentType = null) - { - if (string.IsNullOrEmpty(resourceName)) - throw new ArgumentNullException(nameof(resourceName)); + private readonly string _contentType; - _assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); - _resourceName = resourceName; - _contentType = contentType; + private readonly string _resourceName; + + public EmbeddedResourceDispatcher(Assembly assembly, string resourceName, string contentType = null) + { + if (string.IsNullOrEmpty(resourceName)) + { + throw new ArgumentNullException(nameof(resourceName)); } - public Task Dispatch(DashboardContext context) + _assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); + _resourceName = resourceName; + _contentType = contentType; + } + + public Task Dispatch(DashboardContext context) + { + if (!string.IsNullOrEmpty(_contentType)) { - if (!string.IsNullOrEmpty(_contentType)) + var contentType = context.Response.ContentType; + + if (string.IsNullOrEmpty(contentType)) { - var contentType = context.Response.ContentType; - - if (string.IsNullOrEmpty(contentType)) - { - // content type not yet set - context.Response.ContentType = _contentType; - } - else if (contentType != _contentType) - { - // content type already set, but doesn't match ours - throw new InvalidOperationException($"ContentType '{_contentType}' conflicts with '{context.Response.ContentType}'"); - } + // content type not yet set + context.Response.ContentType = _contentType; + } + else if (contentType != _contentType) + { + // content type already set, but doesn't match ours + throw new InvalidOperationException($"ContentType '{_contentType}' conflicts with '{context.Response.ContentType}'"); } - - return WriteResourceAsync(context.Response, _assembly, _resourceName); } - private static async Task WriteResourceAsync(DashboardResponse response, Assembly assembly, string resourceName) - { - using var stream = assembly.GetManifestResourceStream(resourceName); + return WriteResourceAsync(context.Response, _assembly, _resourceName); + } - if (stream == null) - throw new ArgumentException($@"Resource '{resourceName}' not found in assembly {assembly}."); + private static async Task WriteResourceAsync(DashboardResponse response, Assembly assembly, string resourceName) + { + using var stream = assembly.GetManifestResourceStream(resourceName); - await stream.CopyToAsync(response.Body); + if (stream == null) + { + throw new ArgumentException($@"Resource '{resourceName}' not found in assembly {assembly}."); } + + await stream.CopyToAsync(response.Body); } } diff --git a/src/Hangfire.Console/Support/HtmlHelperExtensions.cs b/src/Hangfire.Console/Support/HtmlHelperExtensions.cs index d09c826..bee8234 100644 --- a/src/Hangfire.Console/Support/HtmlHelperExtensions.cs +++ b/src/Hangfire.Console/Support/HtmlHelperExtensions.cs @@ -2,26 +2,27 @@ using System.Reflection; // ReSharper disable once CheckNamespace -namespace Hangfire.Dashboard.Extensions +namespace Hangfire.Dashboard.Extensions; + +/// +/// Provides extension methods for . +/// +internal static class HtmlHelperExtensions { + // ReSharper disable once InconsistentNaming + private static readonly FieldInfo _page = typeof(HtmlHelper).GetTypeInfo().GetDeclaredField(nameof(_page)); + /// - /// Provides extension methods for . + /// Returs a associated with . /// - internal static class HtmlHelperExtensions + /// Helper + public static RazorPage GetPage(this HtmlHelper helper) { - // ReSharper disable once InconsistentNaming - private static readonly FieldInfo _page = typeof(HtmlHelper).GetTypeInfo().GetDeclaredField(nameof(_page)); - - /// - /// Returs a associated with . - /// - /// Helper - public static RazorPage GetPage(this HtmlHelper helper) + if (helper == null) { - if (helper == null) - throw new ArgumentNullException(nameof(helper)); - - return (RazorPage)_page.GetValue(helper); + throw new ArgumentNullException(nameof(helper)); } + + return (RazorPage)_page.GetValue(helper); } } diff --git a/src/Hangfire.Console/Support/RouteCollectionExtensions.cs b/src/Hangfire.Console/Support/RouteCollectionExtensions.cs index 42524d9..0ab28ff 100644 --- a/src/Hangfire.Console/Support/RouteCollectionExtensions.cs +++ b/src/Hangfire.Console/Support/RouteCollectionExtensions.cs @@ -4,134 +4,161 @@ using System.Reflection; // ReSharper disable once CheckNamespace -namespace Hangfire.Dashboard.Extensions +namespace Hangfire.Dashboard.Extensions; + +/// +/// Provides extension methods for . +/// +internal static class RouteCollectionExtensions { + // ReSharper disable once InconsistentNaming + private static readonly FieldInfo _dispatchers = typeof(RouteCollection).GetTypeInfo().GetDeclaredField(nameof(_dispatchers)); + + /// + /// Returns a private list of registered routes. + /// + /// Route collection + private static List> GetDispatchers(this RouteCollection routes) + { + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } + + return (List>)_dispatchers.GetValue(routes); + } + /// - /// Provides extension methods for . + /// Checks if there's a dispatcher registered for given . /// - internal static class RouteCollectionExtensions + /// Route collection + /// Path template + public static bool Contains(this RouteCollection routes, string pathTemplate) { - // ReSharper disable once InconsistentNaming - private static readonly FieldInfo _dispatchers = typeof(RouteCollection).GetTypeInfo().GetDeclaredField(nameof(_dispatchers)); - - /// - /// Returns a private list of registered routes. - /// - /// Route collection - private static List> GetDispatchers(this RouteCollection routes) + if (routes == null) { - if (routes == null) - throw new ArgumentNullException(nameof(routes)); + throw new ArgumentNullException(nameof(routes)); + } - return (List>)_dispatchers.GetValue(routes); + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); } - /// - /// Checks if there's a dispatcher registered for given . - /// - /// Route collection - /// Path template - public static bool Contains(this RouteCollection routes, string pathTemplate) + return routes.GetDispatchers().Any(x => x.Item1 == pathTemplate); + } + + /// + /// Combines exising dispatcher for with . + /// If there's no dispatcher for the specified path, adds a new one. + /// + /// Route collection + /// Path template + /// Dispatcher to add or append for specified path + public static void Append(this RouteCollection routes, string pathTemplate, IDashboardDispatcher dispatcher) + { + if (routes == null) { - if (routes == null) - throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) - throw new ArgumentNullException(nameof(pathTemplate)); + throw new ArgumentNullException(nameof(routes)); + } - return routes.GetDispatchers().Any(x => x.Item1 == pathTemplate); + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); } - /// - /// Combines exising dispatcher for with . - /// If there's no dispatcher for the specified path, adds a new one. - /// - /// Route collection - /// Path template - /// Dispatcher to add or append for specified path - public static void Append(this RouteCollection routes, string pathTemplate, IDashboardDispatcher dispatcher) + if (dispatcher == null) { - if (routes == null) - throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) - throw new ArgumentNullException(nameof(pathTemplate)); - if (dispatcher == null) - throw new ArgumentNullException(nameof(dispatcher)); + throw new ArgumentNullException(nameof(dispatcher)); + } - var list = routes.GetDispatchers(); + var list = routes.GetDispatchers(); - for (var i = 0; i < list.Count; i++) + for (var i = 0; i < list.Count; i++) + { + var pair = list[i]; + if (pair.Item1 == pathTemplate) { - var pair = list[i]; - if (pair.Item1 == pathTemplate) + if (!(pair.Item2 is CompositeDispatcher composite)) { - if (!(pair.Item2 is CompositeDispatcher composite)) - { - // replace original dispatcher with a composite one - composite = new CompositeDispatcher(pair.Item2); - list[i] = new Tuple(pair.Item1, composite); - } - - composite.AddDispatcher(dispatcher); - return; + // replace original dispatcher with a composite one + composite = new CompositeDispatcher(pair.Item2); + list[i] = new Tuple(pair.Item1, composite); } + + composite.AddDispatcher(dispatcher); + return; } + } + + routes.Add(pathTemplate, dispatcher); + } + + /// + /// Replaces exising dispatcher for with . + /// If there's no dispatcher for the specified path, adds a new one. + /// + /// Route collection + /// Path template + /// Dispatcher to set for specified path + public static void Replace(this RouteCollection routes, string pathTemplate, IDashboardDispatcher dispatcher) + { + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } - routes.Add(pathTemplate, dispatcher); + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); } - /// - /// Replaces exising dispatcher for with . - /// If there's no dispatcher for the specified path, adds a new one. - /// - /// Route collection - /// Path template - /// Dispatcher to set for specified path - public static void Replace(this RouteCollection routes, string pathTemplate, IDashboardDispatcher dispatcher) + if (dispatcher == null) { - if (routes == null) - throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) - throw new ArgumentNullException(nameof(pathTemplate)); - if (dispatcher == null) - throw new ArgumentNullException(nameof(dispatcher)); + throw new ArgumentNullException(nameof(dispatcher)); + } - var list = routes.GetDispatchers(); + var list = routes.GetDispatchers(); - for (var i = 0; i < list.Count; i++) + for (var i = 0; i < list.Count; i++) + { + var pair = list[i]; + if (pair.Item1 == pathTemplate) { - var pair = list[i]; - if (pair.Item1 == pathTemplate) - { - list[i] = new Tuple(pair.Item1, dispatcher); - return; - } + list[i] = new Tuple(pair.Item1, dispatcher); + return; } + } + + routes.Add(pathTemplate, dispatcher); + } - routes.Add(pathTemplate, dispatcher); + /// + /// Removes dispatcher for . + /// + /// Route collection + /// Path template + public static void Remove(this RouteCollection routes, string pathTemplate) + { + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); } - - /// - /// Removes dispatcher for . - /// - /// Route collection - /// Path template - public static void Remove(this RouteCollection routes, string pathTemplate) + + if (pathTemplate == null) { - if (routes == null) - throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) - throw new ArgumentNullException(nameof(pathTemplate)); + throw new ArgumentNullException(nameof(pathTemplate)); + } - var list = routes.GetDispatchers(); + var list = routes.GetDispatchers(); - for (var i = 0; i < list.Count; i++) + for (var i = 0; i < list.Count; i++) + { + var pair = list[i]; + if (pair.Item1 == pathTemplate) { - var pair = list[i]; - if (pair.Item1 == pathTemplate) - { - list.RemoveAt(i); - return; - } + list.RemoveAt(i); + return; } } } diff --git a/tests/Hangfire.Console.Tests/ConsoleExtensionsFacts.cs b/tests/Hangfire.Console.Tests/ConsoleExtensionsFacts.cs index 141b950..b4f1d21 100644 --- a/tests/Hangfire.Console.Tests/ConsoleExtensionsFacts.cs +++ b/tests/Hangfire.Console.Tests/ConsoleExtensionsFacts.cs @@ -1,116 +1,117 @@ -using Hangfire.Console.Progress; +using System; +using Hangfire.Common; +using Hangfire.Console.Progress; using Hangfire.Console.Serialization; using Hangfire.Console.Server; using Hangfire.Console.Storage; using Hangfire.Server; using Hangfire.Storage; using Moq; -using System; using Xunit; -namespace Hangfire.Console.Tests +namespace Hangfire.Console.Tests; + +public class ConsoleExtensionsFacts { - public class ConsoleExtensionsFacts - { - private readonly Mock _cancellationToken; - private readonly Mock _connection; - private readonly Mock _transaction; - private readonly Mock _jobStorage; + private readonly Mock _cancellationToken; + + private readonly Mock _connection; - public ConsoleExtensionsFacts() - { - _cancellationToken = new Mock(); - _connection = new Mock(); - _transaction = new Mock(); - _jobStorage = new Mock(); + private readonly Mock _jobStorage; + + private readonly Mock _transaction; + + public ConsoleExtensionsFacts() + { + _cancellationToken = new Mock(); + _connection = new Mock(); + _transaction = new Mock(); + _jobStorage = new Mock(); - _connection.Setup(x => x.CreateWriteTransaction()) - .Returns(_transaction.Object); - } + _connection.Setup(x => x.CreateWriteTransaction()) + .Returns(_transaction.Object); + } - [Fact] - public void WriteLine_DoesNotFail_IfContextIsNull() - { - ConsoleExtensions.WriteLine(null, ""); + [Fact] + public void WriteLine_DoesNotFail_IfContextIsNull() + { + ConsoleExtensions.WriteLine(null, ""); - _transaction.Verify(x => x.Commit(), Times.Never); - } + _transaction.Verify(x => x.Commit(), Times.Never); + } - [Fact] - public void WriteLine_Writes_IfConsoleCreated() - { - var context = CreatePerformContext(); - context.Items["ConsoleContext"] = CreateConsoleContext(context); + [Fact] + public void WriteLine_Writes_IfConsoleCreated() + { + var context = CreatePerformContext(); + context.Items["ConsoleContext"] = CreateConsoleContext(context); - context.WriteLine(""); + context.WriteLine(""); - _transaction.Verify(x => x.Commit()); - } + _transaction.Verify(x => x.Commit()); + } - [Fact] - public void WriteLine_DoesNotFail_IfConsoleNotCreated() - { - var context = CreatePerformContext(); + [Fact] + public void WriteLine_DoesNotFail_IfConsoleNotCreated() + { + var context = CreatePerformContext(); - context.WriteLine(""); + context.WriteLine(""); - _transaction.Verify(x => x.Commit(), Times.Never); - } + _transaction.Verify(x => x.Commit(), Times.Never); + } - [Fact] - public void WriteProgressBar_ReturnsNoOp_IfContextIsNull() - { - var progressBar = ConsoleExtensions.WriteProgressBar(null); + [Fact] + public void WriteProgressBar_ReturnsNoOp_IfContextIsNull() + { + var progressBar = ConsoleExtensions.WriteProgressBar(null); - Assert.IsType(progressBar); - _transaction.Verify(x => x.Commit(), Times.Never); - } + Assert.IsType(progressBar); + _transaction.Verify(x => x.Commit(), Times.Never); + } - [Fact] - public void WriteProgressBar_ReturnsProgressBar_IfConsoleCreated() - { - var context = CreatePerformContext(); - context.Items["ConsoleContext"] = CreateConsoleContext(context); + [Fact] + public void WriteProgressBar_ReturnsProgressBar_IfConsoleCreated() + { + var context = CreatePerformContext(); + context.Items["ConsoleContext"] = CreateConsoleContext(context); - var progressBar = context.WriteProgressBar(); + var progressBar = context.WriteProgressBar(); - Assert.IsType(progressBar); - _transaction.Verify(x => x.Commit()); - } + Assert.IsType(progressBar); + _transaction.Verify(x => x.Commit()); + } - [Fact] - public void WriteProgressBar_ReturnsNoOp_IfConsoleNotCreated() - { - var context = CreatePerformContext(); + [Fact] + public void WriteProgressBar_ReturnsNoOp_IfConsoleNotCreated() + { + var context = CreatePerformContext(); - var progressBar = context.WriteProgressBar(); + var progressBar = context.WriteProgressBar(); - Assert.IsType(progressBar); - _transaction.Verify(x => x.Commit(), Times.Never); - } + Assert.IsType(progressBar); + _transaction.Verify(x => x.Commit(), Times.Never); + } - // ReSharper disable once RedundantDisableWarningComment + // ReSharper disable once RedundantDisableWarningComment #pragma warning disable xUnit1013 - // ReSharper disable once MemberCanBePrivate.Global - public static void JobMethod() { + + // ReSharper disable once MemberCanBePrivate.Global + public static void JobMethod() + { #pragma warning restore xUnit1013 - } - - private PerformContext CreatePerformContext() - { - return new PerformContext( - _jobStorage.Object, - _connection.Object, - new BackgroundJob("1", Common.Job.FromExpression(() => JobMethod()), DateTime.UtcNow), - _cancellationToken.Object); - } - - private ConsoleContext CreateConsoleContext(PerformContext context) - { - return new ConsoleContext( - new ConsoleId(context.BackgroundJob.Id, DateTime.UtcNow), - new ConsoleStorage(context.Connection)); - } + } + private PerformContext CreatePerformContext() + { + return new PerformContext( + _jobStorage.Object, + _connection.Object, + new BackgroundJob("1", Job.FromExpression(() => JobMethod()), DateTime.UtcNow), + _cancellationToken.Object); } + + private ConsoleContext CreateConsoleContext(PerformContext context) => new( + new ConsoleId(context.BackgroundJob.Id, DateTime.UtcNow), + new ConsoleStorage(context.Connection)); } diff --git a/tests/Hangfire.Console.Tests/Dashboard/ConsoleRendererFacts.cs b/tests/Hangfire.Console.Tests/Dashboard/ConsoleRendererFacts.cs index 79cdda4..fb27977 100644 --- a/tests/Hangfire.Console.Tests/Dashboard/ConsoleRendererFacts.cs +++ b/tests/Hangfire.Console.Tests/Dashboard/ConsoleRendererFacts.cs @@ -1,347 +1,344 @@ -using Hangfire.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hangfire.Common; using Hangfire.Console.Dashboard; using Hangfire.Console.Serialization; using Hangfire.Console.Storage; using Hangfire.States; using Hangfire.Storage; using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Xunit; -namespace Hangfire.Console.Tests.Dashboard +namespace Hangfire.Console.Tests.Dashboard; + +public class ConsoleRendererFacts { - public class ConsoleRendererFacts - { - private readonly ConsoleId _consoleId = new("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - private readonly Mock _storage = new(); + private readonly ConsoleId _consoleId = new("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderText_Empty() - { - var text = ""; - var builder = new StringBuilder(); + private readonly Mock _storage = new(); - ConsoleRenderer.RenderText(builder, text); + [Fact] + public void RenderText_Empty() + { + var text = ""; + var builder = new StringBuilder(); - Assert.Equal("", builder.ToString()); - } + ConsoleRenderer.RenderText(builder, text); - [Fact] - public void RenderText_Simple() - { - var text = "test"; - var builder = new StringBuilder(); + Assert.Equal("", builder.ToString()); + } - ConsoleRenderer.RenderText(builder, text); + [Fact] + public void RenderText_Simple() + { + var text = "test"; + var builder = new StringBuilder(); - Assert.Equal("test", builder.ToString()); - } + ConsoleRenderer.RenderText(builder, text); - [Fact] - public void RenderText_HtmlEncode() - { - var text = ""; - var builder = new StringBuilder(); + Assert.Equal("test", builder.ToString()); + } - ConsoleRenderer.RenderText(builder, text); + [Fact] + public void RenderText_HtmlEncode() + { + var text = ""; + var builder = new StringBuilder(); - Assert.Equal("<bolts & nuts>", builder.ToString()); - } + ConsoleRenderer.RenderText(builder, text); - [Fact] - public void RenderText_Hyperlink() - { - var text = "go to http://localhost?a=1&b=2 & enjoy!"; - var builder = new StringBuilder(); + Assert.Equal("<bolts & nuts>", builder.ToString()); + } - ConsoleRenderer.RenderText(builder, text); + [Fact] + public void RenderText_Hyperlink() + { + var text = "go to http://localhost?a=1&b=2 & enjoy!"; + var builder = new StringBuilder(); - Assert.Equal("go to http://localhost?a=1&b=2 & enjoy!", builder.ToString()); - } + ConsoleRenderer.RenderText(builder, text); - [Fact] - public void RenderLine_Basic() - { - var line = new ConsoleLine() { TimeOffset = 0, Message = "test" }; - var builder = new StringBuilder(); + Assert.Equal("go to http://localhost?a=1&b=2 & enjoy!", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_Basic() + { + var line = new ConsoleLine { TimeOffset = 0, Message = "test" }; + var builder = new StringBuilder(); - Assert.Equal("
+ <1mstest
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithOffset() - { - var line = new ConsoleLine() { TimeOffset = 1.108, Message = "test" }; - var builder = new StringBuilder(); + Assert.Equal("
+ <1mstest
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithOffset() + { + var line = new ConsoleLine { TimeOffset = 1.108, Message = "test" }; + var builder = new StringBuilder(); - Assert.Equal("
+1.108stest
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithNegativeOffset() - { - var line = new ConsoleLine() { TimeOffset = -1.206, Message = "test" }; - var builder = new StringBuilder(); + Assert.Equal("
+1.108stest
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithNegativeOffset() + { + var line = new ConsoleLine { TimeOffset = -1.206, Message = "test" }; + var builder = new StringBuilder(); - Assert.Equal("
-1.206stest
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithColor() - { - var line = new ConsoleLine() { TimeOffset = 0, Message = "test", TextColor = "#ffffff" }; - var builder = new StringBuilder(); + Assert.Equal("
-1.206stest
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithColor() + { + var line = new ConsoleLine { TimeOffset = 0, Message = "test", TextColor = "#ffffff" }; + var builder = new StringBuilder(); - Assert.Equal("
+ <1mstest
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithProgress() - { - var line = new ConsoleLine() { TimeOffset = 0, Message = "3", ProgressValue = 17 }; - var builder = new StringBuilder(); + Assert.Equal("
+ <1mstest
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithProgress() + { + var line = new ConsoleLine { TimeOffset = 0, Message = "3", ProgressValue = 17 }; + var builder = new StringBuilder(); - Assert.Equal("
+ <1ms
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithProgressName() - { - var line = new ConsoleLine() { TimeOffset = 0, Message = "3", ProgressValue = 17, ProgressName = "test " }; - var builder = new StringBuilder(); + Assert.Equal("
+ <1ms
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithProgressName() + { + var line = new ConsoleLine { TimeOffset = 0, Message = "3", ProgressValue = 17, ProgressName = "test " }; + var builder = new StringBuilder(); - Assert.Equal("
test &lt;go&gt;
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithFractionalProgress() - { - var line = new ConsoleLine() { TimeOffset = 0, Message = "3", ProgressValue = 17.3 }; - var builder = new StringBuilder(); + Assert.Equal("
test &lt;go&gt;
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithFractionalProgress() + { + var line = new ConsoleLine { TimeOffset = 0, Message = "3", ProgressValue = 17.3 }; + var builder = new StringBuilder(); - Assert.Equal("
+ <1ms
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLine_WithProgressAndColor() - { - var line = new ConsoleLine() { TimeOffset = 0, Message = "3", ProgressValue = 17, TextColor = "#ffffff" }; - var builder = new StringBuilder(); + Assert.Equal("
+ <1ms
", builder.ToString()); + } - ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + [Fact] + public void RenderLine_WithProgressAndColor() + { + var line = new ConsoleLine { TimeOffset = 0, Message = "3", ProgressValue = 17, TextColor = "#ffffff" }; + var builder = new StringBuilder(); - Assert.Equal("
+ <1ms
", builder.ToString()); - } + ConsoleRenderer.RenderLine(builder, line, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void RenderLines_ThrowsException_IfBuilderIsNull() - { - Assert.Throws("builder", () => ConsoleRenderer.RenderLines(null, Enumerable.Empty(), DateTime.UtcNow)); - } + Assert.Equal("
+ <1ms
", builder.ToString()); + } - [Fact] - public void RenderLines_RendersNothing_IfLinesIsNull() - { - var builder = new StringBuilder(); - ConsoleRenderer.RenderLines(builder, null, DateTime.UtcNow); + [Fact] + public void RenderLines_ThrowsException_IfBuilderIsNull() + { + Assert.Throws("builder", () => ConsoleRenderer.RenderLines(null, Enumerable.Empty(), DateTime.UtcNow)); + } - Assert.Equal(0, builder.Length); - } + [Fact] + public void RenderLines_RendersNothing_IfLinesIsNull() + { + var builder = new StringBuilder(); + ConsoleRenderer.RenderLines(builder, null, DateTime.UtcNow); - [Fact] - public void RenderLines_RendersNothing_IfLinesIsEmpty() - { - var builder = new StringBuilder(); - ConsoleRenderer.RenderLines(builder, Enumerable.Empty(), DateTime.UtcNow); + Assert.Equal(0, builder.Length); + } - Assert.Equal(0, builder.Length); - } + [Fact] + public void RenderLines_RendersNothing_IfLinesIsEmpty() + { + var builder = new StringBuilder(); + ConsoleRenderer.RenderLines(builder, Enumerable.Empty(), DateTime.UtcNow); - [Fact] - public void RenderLines_RendersAllLines() - { - var builder = new StringBuilder(); - ConsoleRenderer.RenderLines(builder, new[] - { - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" } - }, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - - Assert.Equal( - "
+ <1msline1
" + - "
+1sline2
", - builder.ToString()); - } + Assert.Equal(0, builder.Length); + } - [Fact] - public void RenderLineBuffer_ThrowsException_IfBuilderIsNull() + [Fact] + public void RenderLines_RendersAllLines() + { + var builder = new StringBuilder(); + ConsoleRenderer.RenderLines(builder, new[] { - Assert.Throws("builder", () => ConsoleRenderer.RenderLineBuffer(null, _storage.Object, _consoleId, 0)); - } + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" } + }, new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + + Assert.Equal( + "
+ <1msline1
" + + "
+1sline2
", + builder.ToString()); + } - [Fact] - public void RenderLineBuffer_ThrowsException_IfStorageIsNull() - { - Assert.Throws("storage", () => ConsoleRenderer.RenderLineBuffer(new StringBuilder(), null, _consoleId, 0)); - } + [Fact] + public void RenderLineBuffer_ThrowsException_IfBuilderIsNull() + { + Assert.Throws("builder", () => ConsoleRenderer.RenderLineBuffer(null, _storage.Object, _consoleId, 0)); + } - [Fact] - public void RenderLineBuffer_ThrowsException_IfConsoleIdIsNull() - { - Assert.Throws("consoleId", () => ConsoleRenderer.RenderLineBuffer(new StringBuilder(), _storage.Object, null, 0)); - } + [Fact] + public void RenderLineBuffer_ThrowsException_IfStorageIsNull() + { + Assert.Throws("storage", () => ConsoleRenderer.RenderLineBuffer(new StringBuilder(), null, _consoleId, 0)); + } - [Fact] - public void RenderLineBuffer_RendersEmpty_WithNegativeN_IfStartIsNegative() - { - SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue)); + [Fact] + public void RenderLineBuffer_ThrowsException_IfConsoleIdIsNull() + { + Assert.Throws("consoleId", () => ConsoleRenderer.RenderLineBuffer(new StringBuilder(), _storage.Object, null, 0)); + } - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, -1); + [Fact] + public void RenderLineBuffer_RendersEmpty_WithNegativeN_IfStartIsNegative() + { + SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue)); - Assert.Equal("
", builder.ToString()); - } + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, -1); - [Fact] - public void RenderLineBuffer_RendersAllLines_IfStartIsZero() - { - SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" }); - - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 0); - - Assert.Equal( - "
" + - "
+ <1msline1
" + - "
+1sline2
" + - "
", builder.ToString()); - } + Assert.Equal("
", builder.ToString()); + } - [Fact] - public void RenderLineBuffer_RendersLines_FromStartOffset() - { - SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" }); + [Fact] + public void RenderLineBuffer_RendersAllLines_IfStartIsZero() + { + SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }); + + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 0); + + Assert.Equal( + "
" + + "
+ <1msline1
" + + "
+1sline2
" + + "
", builder.ToString()); + } - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 1); + [Fact] + public void RenderLineBuffer_RendersLines_FromStartOffset() + { + SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }); - Assert.Equal( - "
" + - "
+1sline2
" + - "
", builder.ToString()); - } + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 1); - [Fact] - public void RenderLineBuffer_RendersEmpty_WithLineCount_IfNoMoreLines() - { - SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" }); + Assert.Equal( + "
" + + "
+1sline2
" + + "
", builder.ToString()); + } - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); + [Fact] + public void RenderLineBuffer_RendersEmpty_WithLineCount_IfNoMoreLines() + { + SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }); - Assert.Equal("
", builder.ToString()); - } + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); - [Fact] - public void RenderLineBuffer_RendersEmpty_WithMinusTwo_IfStateNotFound() - { - SetupStorage(null, - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" }); + Assert.Equal("
", builder.ToString()); + } - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); + [Fact] + public void RenderLineBuffer_RendersEmpty_WithMinusTwo_IfStateNotFound() + { + SetupStorage(null, + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }); - Assert.Equal("
", builder.ToString()); - } + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); - [Fact] - public void RenderLineBuffer_RendersEmpty_WithMinusOne_IfStateIsNotProcessing() - { - SetupStorage(CreateState(SucceededState.StateName, _consoleId.DateValue), - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" }); + Assert.Equal("
", builder.ToString()); + } + + [Fact] + public void RenderLineBuffer_RendersEmpty_WithMinusOne_IfStateIsNotProcessing() + { + SetupStorage(CreateState(SucceededState.StateName, _consoleId.DateValue), + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }); - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); - Assert.Equal("
", builder.ToString()); - } + Assert.Equal("
", builder.ToString()); + } - [Fact] - public void RenderLineBuffer_RendersEmpty_WithMinusOne_IfStateIsAnotherProcessing() - { - SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue.AddMinutes(1)), - new ConsoleLine() { TimeOffset = 0, Message = "line1" }, - new ConsoleLine() { TimeOffset = 1, Message = "line2" }); + [Fact] + public void RenderLineBuffer_RendersEmpty_WithMinusOne_IfStateIsAnotherProcessing() + { + SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue.AddMinutes(1)), + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }); - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 2); - Assert.Equal("
", builder.ToString()); - } + Assert.Equal("
", builder.ToString()); + } - [Fact] - public void RenderLineBuffer_AggregatesMultipleProgressLines() - { - SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), - new ConsoleLine() { TimeOffset = 0, Message = "0", ProgressValue = 1 }, - new ConsoleLine() { TimeOffset = 1, Message = "1", ProgressValue = 3 }, - new ConsoleLine() { TimeOffset = 2, Message = "0", ProgressValue = 5 }); - - var builder = new StringBuilder(); - ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 0); - - Assert.Equal( - "
" + - "
+ <1ms
" + - "
+1s
" + - "
", builder.ToString()); - } + [Fact] + public void RenderLineBuffer_AggregatesMultipleProgressLines() + { + SetupStorage(CreateState(ProcessingState.StateName, _consoleId.DateValue), + new ConsoleLine { TimeOffset = 0, Message = "0", ProgressValue = 1 }, + new ConsoleLine { TimeOffset = 1, Message = "1", ProgressValue = 3 }, + new ConsoleLine { TimeOffset = 2, Message = "0", ProgressValue = 5 }); + + var builder = new StringBuilder(); + ConsoleRenderer.RenderLineBuffer(builder, _storage.Object, _consoleId, 0); + + Assert.Equal( + "
" + + "
+ <1ms
" + + "
+1s
" + + "
", builder.ToString()); + } - private StateData CreateState(string stateName, DateTime startedAt) + private StateData CreateState(string stateName, DateTime startedAt) => new() + { + Name = stateName, + Data = new Dictionary { - return new StateData() - { - Name = stateName, - Data = new Dictionary() - { - ["StartedAt"] = JobHelper.SerializeDateTime(startedAt) - } - }; + ["StartedAt"] = JobHelper.SerializeDateTime(startedAt) } + }; - private void SetupStorage(StateData stateData, params ConsoleLine[] lines) - { - _storage.Setup(x => x.GetState(It.IsAny())) - .Returns(stateData); - _storage.Setup(x => x.GetLineCount(It.IsAny())) - .Returns(lines.Length); - _storage.Setup(x => x.GetLines(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((ConsoleId _, int start, int end) => lines.Where((_, i) => i >= start && i <= end)); - } + private void SetupStorage(StateData stateData, params ConsoleLine[] lines) + { + _storage.Setup(x => x.GetState(It.IsAny())) + .Returns(stateData); + _storage.Setup(x => x.GetLineCount(It.IsAny())) + .Returns(lines.Length); + _storage.Setup(x => x.GetLines(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((ConsoleId _, int start, int end) => lines.Where((_, i) => i >= start && i <= end)); } } diff --git a/tests/Hangfire.Console.Tests/Dashboard/JobProgressDispatcherFacts.cs b/tests/Hangfire.Console.Tests/Dashboard/JobProgressDispatcherFacts.cs index 4d2f2c9..38b02fe 100644 --- a/tests/Hangfire.Console.Tests/Dashboard/JobProgressDispatcherFacts.cs +++ b/tests/Hangfire.Console.Tests/Dashboard/JobProgressDispatcherFacts.cs @@ -3,23 +3,22 @@ using Newtonsoft.Json; using Xunit; -namespace Hangfire.Console.Tests.Dashboard +namespace Hangfire.Console.Tests.Dashboard; + +public class JobProgressDispatcherFacts { - public class JobProgressDispatcherFacts + [Fact] + public void JsonSettings_PreservesDictionaryKeyCase() { - [Fact] - public void JsonSettings_PreservesDictionaryKeyCase() + var result = new Dictionary { - var result = new Dictionary - { - ["AAA"] = 1.0, - ["Bbb"] = 2.0, - ["ccc"] = 3.0 - }; + ["AAA"] = 1.0, + ["Bbb"] = 2.0, + ["ccc"] = 3.0 + }; - var json = JsonConvert.SerializeObject(result, JobProgressDispatcher.JsonSettings); + var json = JsonConvert.SerializeObject(result, JobProgressDispatcher.JsonSettings); - Assert.Equal("{\"AAA\":1.0,\"Bbb\":2.0,\"ccc\":3.0}", json); - } + Assert.Equal("{\"AAA\":1.0,\"Bbb\":2.0,\"ccc\":3.0}", json); } -} \ No newline at end of file +} diff --git a/tests/Hangfire.Console.Tests/Hangfire.Console.Tests.csproj b/tests/Hangfire.Console.Tests/Hangfire.Console.Tests.csproj index 5a9087e..6903a6f 100644 --- a/tests/Hangfire.Console.Tests/Hangfire.Console.Tests.csproj +++ b/tests/Hangfire.Console.Tests/Hangfire.Console.Tests.csproj @@ -1,24 +1,24 @@  - - net7.0 - false - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + net7.0 + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/tests/Hangfire.Console.Tests/Serialization/ConsoleIdFacts.cs b/tests/Hangfire.Console.Tests/Serialization/ConsoleIdFacts.cs index c95c82d..548f027 100644 --- a/tests/Hangfire.Console.Tests/Serialization/ConsoleIdFacts.cs +++ b/tests/Hangfire.Console.Tests/Serialization/ConsoleIdFacts.cs @@ -1,79 +1,78 @@ -using Hangfire.Console.Serialization; -using System; +using System; +using Hangfire.Console.Serialization; using Xunit; -namespace Hangfire.Console.Tests.Serialization +namespace Hangfire.Console.Tests.Serialization; + +public class ConsoleIdFacts { - public class ConsoleIdFacts - { - private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - [Fact] - public void Ctor_ThrowsAnException_WhenJobIdIsNull() - { - Assert.Throws("jobId", () => new ConsoleId(null, DateTime.UtcNow)); - } + [Fact] + public void Ctor_ThrowsAnException_WhenJobIdIsNull() + { + Assert.Throws("jobId", () => new ConsoleId(null, DateTime.UtcNow)); + } - [Fact] - public void Ctor_ThrowsAnException_WhenJobIdIsEmpty() - { - Assert.Throws("jobId", () => new ConsoleId("", DateTime.UtcNow)); - } + [Fact] + public void Ctor_ThrowsAnException_WhenJobIdIsEmpty() + { + Assert.Throws("jobId", () => new ConsoleId("", DateTime.UtcNow)); + } - [Fact] - public void Ctor_ThrowsAnException_WhenTimestampBeforeEpoch() - { - Assert.Throws("timestamp", - () => new ConsoleId("123", UnixEpoch)); - } + [Fact] + public void Ctor_ThrowsAnException_WhenTimestampBeforeEpoch() + { + Assert.Throws("timestamp", + () => new ConsoleId("123", UnixEpoch)); + } - [Fact] - public void Ctor_ThrowsAnException_WhenTimestampAfterEpochPlusMaxInt() - { - Assert.Throws("timestamp", - () => new ConsoleId("123", UnixEpoch.AddSeconds(int.MaxValue).AddSeconds(1))); - } + [Fact] + public void Ctor_ThrowsAnException_WhenTimestampAfterEpochPlusMaxInt() + { + Assert.Throws("timestamp", + () => new ConsoleId("123", UnixEpoch.AddSeconds(int.MaxValue).AddSeconds(1))); + } - [Fact] - public void SerializesCorrectly() - { - var x = new ConsoleId("123", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var s = x.ToString(); - Assert.Equal("00cdb7af151123", s); - } + [Fact] + public void SerializesCorrectly() + { + var x = new ConsoleId("123", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var s = x.ToString(); + Assert.Equal("00cdb7af151123", s); + } - [Fact] - public void IgnoresFractionalMilliseconds() - { - var x = new ConsoleId("123", UnixEpoch.AddMilliseconds(3.0)); - var y = new ConsoleId("123", UnixEpoch.AddMilliseconds(3.0215)); - Assert.Equal(x, y); - } + [Fact] + public void IgnoresFractionalMilliseconds() + { + var x = new ConsoleId("123", UnixEpoch.AddMilliseconds(3.0)); + var y = new ConsoleId("123", UnixEpoch.AddMilliseconds(3.0215)); + Assert.Equal(x, y); + } - [Fact] - public void Parse_ThrowsAnException_WhenValueIsNull() - { - Assert.Throws("value", () => ConsoleId.Parse(null)); - } + [Fact] + public void Parse_ThrowsAnException_WhenValueIsNull() + { + Assert.Throws("value", () => ConsoleId.Parse(null)); + } - [Fact] - public void Parse_ThrowsAnException_WhenValueIsTooShort() - { - Assert.Throws("value", () => ConsoleId.Parse("00cdb7af1")); - } + [Fact] + public void Parse_ThrowsAnException_WhenValueIsTooShort() + { + Assert.Throws("value", () => ConsoleId.Parse("00cdb7af1")); + } - [Fact] - public void Parse_ThrowsAnException_WhenValueIsInvalid() - { - Assert.Throws("value", () => ConsoleId.Parse("00x00y00z001")); - } + [Fact] + public void Parse_ThrowsAnException_WhenValueIsInvalid() + { + Assert.Throws("value", () => ConsoleId.Parse("00x00y00z001")); + } - [Fact] - public void DeserializesCorrectly() - { - var x = ConsoleId.Parse("00cdb7af151123"); - Assert.Equal("123", x.JobId); - Assert.Equal(new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc), x.DateValue); - } + [Fact] + public void DeserializesCorrectly() + { + var x = ConsoleId.Parse("00cdb7af151123"); + Assert.Equal("123", x.JobId); + Assert.Equal(new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc), x.DateValue); } } diff --git a/tests/Hangfire.Console.Tests/Server/ConsoleContextFacts.cs b/tests/Hangfire.Console.Tests/Server/ConsoleContextFacts.cs index 89325a0..3bcc0c0 100644 --- a/tests/Hangfire.Console.Tests/Server/ConsoleContextFacts.cs +++ b/tests/Hangfire.Console.Tests/Server/ConsoleContextFacts.cs @@ -1,185 +1,184 @@ -using Hangfire.Console.Serialization; +using System; +using Hangfire.Console.Serialization; using Hangfire.Console.Server; using Hangfire.Console.Storage; using Moq; -using System; using Xunit; -namespace Hangfire.Console.Tests.Server +namespace Hangfire.Console.Tests.Server; + +public class ConsoleContextFacts { - public class ConsoleContextFacts + private readonly Mock _storage = new(); + + [Fact] + public void Ctor_ThrowsException_IfConsoleIdIsNull() { - private readonly Mock _storage = new(); + Assert.Throws("consoleId", () => new ConsoleContext(null, _storage.Object)); + } - [Fact] - public void Ctor_ThrowsException_IfConsoleIdIsNull() - { - Assert.Throws("consoleId", () => new ConsoleContext(null, _storage.Object)); - } + [Fact] + public void Ctor_ThrowsException_IfStorageIsNull() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - [Fact] - public void Ctor_ThrowsException_IfStorageIsNull() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.Throws("storage", () => new ConsoleContext(consoleId, null)); + } - Assert.Throws("storage", () => new ConsoleContext(consoleId, null)); - } + [Fact] + public void Ctor_InitializesConsole() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + _ = new ConsoleContext(consoleId, _storage.Object); - [Fact] - public void Ctor_InitializesConsole() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - _ = new ConsoleContext(consoleId, _storage.Object); + _storage.Verify(x => x.InitConsole(consoleId)); + } - _storage.Verify(x => x.InitConsole(consoleId)); - } - - [Fact] - public void AddLine_ThrowsException_IfLineIsNull() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void AddLine_ThrowsException_IfLineIsNull() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - Assert.Throws("line", () => context.AddLine(null)); - } + Assert.Throws("line", () => context.AddLine(null)); + } - [Fact] - public void AddLine_ReallyAddsLine() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void AddLine_ReallyAddsLine() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - context.AddLine(new ConsoleLine() { TimeOffset = 0, Message = "line" }); + context.AddLine(new ConsoleLine { TimeOffset = 0, Message = "line" }); - _storage.Verify(x => x.AddLine(It.IsAny(), It.IsAny())); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.IsAny())); + } - [Fact] - public void AddLine_CorrectsTimeOffset() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void AddLine_CorrectsTimeOffset() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - var line1 = new ConsoleLine() { TimeOffset = 0, Message = "line" }; - var line2 = new ConsoleLine() { TimeOffset = 0, Message = "line" }; + var line1 = new ConsoleLine { TimeOffset = 0, Message = "line" }; + var line2 = new ConsoleLine { TimeOffset = 0, Message = "line" }; - context.AddLine(line1); - context.AddLine(line2); + context.AddLine(line1); + context.AddLine(line2); - _storage.Verify(x => x.AddLine(It.IsAny(), It.IsAny()), Times.Exactly(2)); - Assert.NotEqual(line1.TimeOffset, line2.TimeOffset, 4); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.IsAny()), Times.Exactly(2)); + Assert.NotEqual(line1.TimeOffset, line2.TimeOffset, 4); + } - [Fact] - public void WriteLine_ReallyAddsLine() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void WriteLine_ReallyAddsLine() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - context.WriteLine("line", null); + context.WriteLine("line", null); - _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.Message == "line" && l.TextColor == null))); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.Message == "line" && l.TextColor == null))); + } - [Fact] - public void WriteLine_ReallyAddsLineWithColor() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void WriteLine_ReallyAddsLineWithColor() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - context.WriteLine("line", ConsoleTextColor.Red); + context.WriteLine("line", ConsoleTextColor.Red); - _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.Message == "line" && l.TextColor == ConsoleTextColor.Red))); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.Message == "line" && l.TextColor == ConsoleTextColor.Red))); + } - [Fact] - public void WriteProgressBar_WritesDefaults_AndReturnsNonNull() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void WriteProgressBar_WritesDefaults_AndReturnsNonNull() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - var progressBar = context.WriteProgressBar(null, 0, null); + var progressBar = context.WriteProgressBar(null, 0, null); - _storage.Verify(x => x.AddLine(It.IsAny(), It.IsAny())); - Assert.NotNull(progressBar); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.IsAny())); + Assert.NotNull(progressBar); + } - [Fact] - public void WriteProgressBar_WritesName_AndReturnsNonNull() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void WriteProgressBar_WritesName_AndReturnsNonNull() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - var progressBar = context.WriteProgressBar("test", 0, null); + var progressBar = context.WriteProgressBar("test", 0, null); - _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.ProgressName == "test"))); - Assert.NotNull(progressBar); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.ProgressName == "test"))); + Assert.NotNull(progressBar); + } - [Fact] - public void WriteProgressBar_WritesInitialValue_AndReturnsNonNull() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void WriteProgressBar_WritesInitialValue_AndReturnsNonNull() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - var progressBar = context.WriteProgressBar(null, 5, null); + var progressBar = context.WriteProgressBar(null, 5, null); - _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => - l.ProgressValue.HasValue && Math.Abs(l.ProgressValue.Value - 5.0) < double.Epsilon))); - Assert.NotNull(progressBar); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => + l.ProgressValue.HasValue && Math.Abs(l.ProgressValue.Value - 5.0) < double.Epsilon))); + Assert.NotNull(progressBar); + } - [Fact] - public void WriteProgressBar_WritesProgressBarColor_AndReturnsNonNull() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void WriteProgressBar_WritesProgressBarColor_AndReturnsNonNull() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - var progressBar = context.WriteProgressBar(null, 0, ConsoleTextColor.Red); + var progressBar = context.WriteProgressBar(null, 0, ConsoleTextColor.Red); - _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.TextColor == ConsoleTextColor.Red))); - Assert.NotNull(progressBar); - } + _storage.Verify(x => x.AddLine(It.IsAny(), It.Is(l => l.TextColor == ConsoleTextColor.Red))); + Assert.NotNull(progressBar); + } - [Fact] - public void Expire_ReallyExpiresLines() - { - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + [Fact] + public void Expire_ReallyExpiresLines() + { + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - context.Expire(TimeSpan.FromHours(1)); + context.Expire(TimeSpan.FromHours(1)); - _storage.Verify(x => x.Expire(It.IsAny(), It.IsAny())); - } + _storage.Verify(x => x.Expire(It.IsAny(), It.IsAny())); + } - [Fact] - public void FixExpiration_RequestsConsoleTtl_IgnoresIfNegative() - { - _storage.Setup(x => x.GetConsoleTtl(It.IsAny())) - .Returns(TimeSpan.FromSeconds(-1)); + [Fact] + public void FixExpiration_RequestsConsoleTtl_IgnoresIfNegative() + { + _storage.Setup(x => x.GetConsoleTtl(It.IsAny())) + .Returns(TimeSpan.FromSeconds(-1)); - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - context.FixExpiration(); + context.FixExpiration(); - _storage.Verify(x => x.GetConsoleTtl(It.IsAny())); - _storage.Verify(x => x.Expire(It.IsAny(), It.IsAny()), Times.Never); - } + _storage.Verify(x => x.GetConsoleTtl(It.IsAny())); + _storage.Verify(x => x.Expire(It.IsAny(), It.IsAny()), Times.Never); + } - [Fact] - public void FixExpiration_RequestsConsoleTtl_ExpiresIfPositive() - { - _storage.Setup(x => x.GetConsoleTtl(It.IsAny())) - .Returns(TimeSpan.FromHours(1)); + [Fact] + public void FixExpiration_RequestsConsoleTtl_ExpiresIfPositive() + { + _storage.Setup(x => x.GetConsoleTtl(It.IsAny())) + .Returns(TimeSpan.FromHours(1)); - var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - var context = new ConsoleContext(consoleId, _storage.Object); + var consoleId = new ConsoleId("1", new DateTime(2016, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + var context = new ConsoleContext(consoleId, _storage.Object); - context.FixExpiration(); + context.FixExpiration(); - _storage.Verify(x => x.GetConsoleTtl(It.IsAny())); - _storage.Verify(x => x.Expire(It.IsAny(), It.IsAny())); - } + _storage.Verify(x => x.GetConsoleTtl(It.IsAny())); + _storage.Verify(x => x.Expire(It.IsAny(), It.IsAny())); } } diff --git a/tests/Hangfire.Console.Tests/Server/ConsoleServerFilterFacts.cs b/tests/Hangfire.Console.Tests/Server/ConsoleServerFilterFacts.cs index 8651674..7651070 100644 --- a/tests/Hangfire.Console.Tests/Server/ConsoleServerFilterFacts.cs +++ b/tests/Hangfire.Console.Tests/Server/ConsoleServerFilterFacts.cs @@ -1,220 +1,220 @@ -using Hangfire.Common; +using System; +using System.Collections.Generic; +using Hangfire.Common; using Hangfire.Console.Server; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; using Moq; -using System; -using System.Collections.Generic; using Xunit; -namespace Hangfire.Console.Tests.Server +namespace Hangfire.Console.Tests.Server; + +public class ConsoleServerFilterFacts { - public class ConsoleServerFilterFacts - { - private readonly Mock _otherFilter; - private readonly Mock _cancellationToken; - private readonly Mock _connection; - private readonly Mock _transaction; - private readonly Mock _jobStorage; + private readonly Mock _cancellationToken; - public ConsoleServerFilterFacts() - { - _otherFilter = new Mock(); - _cancellationToken = new Mock(); - _connection = new Mock(); - _transaction = new Mock(); - _jobStorage = new Mock(); - - _connection.Setup(x => x.CreateWriteTransaction()) - .Returns(_transaction.Object); - } + private readonly Mock _connection; - [Fact] - public void DoesNotCreateConsoleContext_IfStateNotFound() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns((StateData)null); + private readonly Mock _jobStorage; - var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); - var context = CreatePerformContext(); + private readonly Mock _otherFilter; - performer.Perform(context); + private readonly Mock _transaction; - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); - } + public ConsoleServerFilterFacts() + { + _otherFilter = new Mock(); + _cancellationToken = new Mock(); + _connection = new Mock(); + _transaction = new Mock(); + _jobStorage = new Mock(); + + _connection.Setup(x => x.CreateWriteTransaction()) + .Returns(_transaction.Object); + } - [Fact] - public void DoesNotCreateConsoleContext_IfStateIsNotProcessing() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns(CreateState(SucceededState.StateName)); + [Fact] + public void DoesNotCreateConsoleContext_IfStateNotFound() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns((StateData)null); - var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); - var context = CreatePerformContext(); + var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); + var context = CreatePerformContext(); - performer.Perform(context); + performer.Perform(context); - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); - } + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); + } - [Fact] - public void CreatesConsoleContext_IfStateIsProcessing_DoesNotExpireData_IfConsoleNotPresent() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns(CreateState(ProcessingState.StateName)); - _otherFilter.Setup(x => x.OnPerforming(It.IsAny())) - .Callback(x => { x.Items.Remove("ConsoleContext"); }); + [Fact] + public void DoesNotCreateConsoleContext_IfStateIsNotProcessing() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns(CreateState(SucceededState.StateName)); - var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); - var context = CreatePerformContext(); + var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); + var context = CreatePerformContext(); - performer.Perform(context); + performer.Perform(context); - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); + } - _transaction.Verify(x => x.Commit(), Times.Never); - } + [Fact] + public void CreatesConsoleContext_IfStateIsProcessing_DoesNotExpireData_IfConsoleNotPresent() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns(CreateState(ProcessingState.StateName)); + _otherFilter.Setup(x => x.OnPerforming(It.IsAny())) + .Callback(x => { x.Items.Remove("ConsoleContext"); }); - [Fact] - public void CreatesConsoleContext_IfStateIsProcessing_FixesExpiration_IfFollowsJobRetention() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns(CreateState(ProcessingState.StateName)); - _connection.Setup(x => x.GetHashTtl(It.IsAny())) - .Returns(TimeSpan.FromSeconds(1)); + var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); + var context = CreatePerformContext(); - var performer = new BackgroundJobPerformer(CreateJobFilterProvider(true)); - var context = CreatePerformContext(); + performer.Perform(context); - performer.Perform(context); + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); + _transaction.Verify(x => x.Commit(), Times.Never); + } - _connection.Verify(x => x.GetHashTtl(It.IsAny())); + [Fact] + public void CreatesConsoleContext_IfStateIsProcessing_FixesExpiration_IfFollowsJobRetention() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns(CreateState(ProcessingState.StateName)); + _connection.Setup(x => x.GetHashTtl(It.IsAny())) + .Returns(TimeSpan.FromSeconds(1)); - _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny())); + var performer = new BackgroundJobPerformer(CreateJobFilterProvider(true)); + var context = CreatePerformContext(); - _transaction.Verify(x => x.Commit()); - } + performer.Perform(context); - [Fact] - public void CreatesConsoleContext_IfStateIsProcessing_DoesNotFixExpiration_IfNegativeTtl_AndFollowsJobRetention() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns(CreateState(ProcessingState.StateName)); - _connection.Setup(x => x.GetHashTtl(It.IsAny())) - .Returns(TimeSpan.FromSeconds(-1)); + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); - var performer = new BackgroundJobPerformer(CreateJobFilterProvider(true)); - var context = CreatePerformContext(); + _connection.Verify(x => x.GetHashTtl(It.IsAny())); - performer.Perform(context); + _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny())); - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); + _transaction.Verify(x => x.Commit()); + } - _connection.Verify(x => x.GetHashTtl(It.IsAny())); + [Fact] + public void CreatesConsoleContext_IfStateIsProcessing_DoesNotFixExpiration_IfNegativeTtl_AndFollowsJobRetention() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns(CreateState(ProcessingState.StateName)); + _connection.Setup(x => x.GetHashTtl(It.IsAny())) + .Returns(TimeSpan.FromSeconds(-1)); - _transaction.Verify(x => x.Commit(), Times.Never); - } + var performer = new BackgroundJobPerformer(CreateJobFilterProvider(true)); + var context = CreatePerformContext(); - [Fact] - public void CreatesConsoleContext_IfStateIsProcessing_DoesNotFixExpiration_IfZeroTtl_AndFollowsJobRetention() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns(CreateState(ProcessingState.StateName)); - _connection.Setup(x => x.GetHashTtl(It.IsAny())) - .Returns(TimeSpan.Zero); + performer.Perform(context); - var performer = new BackgroundJobPerformer(CreateJobFilterProvider(true)); - var context = CreatePerformContext(); + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); - performer.Perform(context); + _connection.Verify(x => x.GetHashTtl(It.IsAny())); - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); + _transaction.Verify(x => x.Commit(), Times.Never); + } - _connection.Verify(x => x.GetHashTtl(It.IsAny())); + [Fact] + public void CreatesConsoleContext_IfStateIsProcessing_DoesNotFixExpiration_IfZeroTtl_AndFollowsJobRetention() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns(CreateState(ProcessingState.StateName)); + _connection.Setup(x => x.GetHashTtl(It.IsAny())) + .Returns(TimeSpan.Zero); - _transaction.Verify(x => x.Commit(), Times.Never); - } + var performer = new BackgroundJobPerformer(CreateJobFilterProvider(true)); + var context = CreatePerformContext(); - [Fact] - public void CreatesConsoleContext_IfStateIsProcessing_ExpiresData_IfNotFollowsJobRetention() - { - _connection.Setup(x => x.GetStateData("1")) - .Returns(CreateState(ProcessingState.StateName)); + performer.Perform(context); - var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); - var context = CreatePerformContext(); + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); - performer.Perform(context); + _connection.Verify(x => x.GetHashTtl(It.IsAny())); - var consoleContext = ConsoleContext.FromPerformContext(context); - Assert.Null(consoleContext); + _transaction.Verify(x => x.Commit(), Times.Never); + } - _connection.Verify(x => x.GetHashTtl(It.IsAny()), Times.Never); + [Fact] + public void CreatesConsoleContext_IfStateIsProcessing_ExpiresData_IfNotFollowsJobRetention() + { + _connection.Setup(x => x.GetStateData("1")) + .Returns(CreateState(ProcessingState.StateName)); - _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny())); + var performer = new BackgroundJobPerformer(CreateJobFilterProvider()); + var context = CreatePerformContext(); - _transaction.Verify(x => x.Commit()); - } + performer.Perform(context); - // ReSharper disable once RedundantDisableWarningComment + var consoleContext = ConsoleContext.FromPerformContext(context); + Assert.Null(consoleContext); + + _connection.Verify(x => x.GetHashTtl(It.IsAny()), Times.Never); + + _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny())); + + _transaction.Verify(x => x.Commit()); + } + + // ReSharper disable once RedundantDisableWarningComment #pragma warning disable xUnit1013 - // ReSharper disable once MemberCanBePrivate.Global - public static void JobMethod(PerformContext context) + + // ReSharper disable once MemberCanBePrivate.Global + public static void JobMethod(PerformContext context) #pragma warning restore xUnit1013 - { - // reset transaction method calls after OnPerforming is completed - var @this = (ConsoleServerFilterFacts) context.Items["this"]; - @this._transaction.Invocations.Clear(); - } + { + // reset transaction method calls after OnPerforming is completed + var @this = (ConsoleServerFilterFacts)context.Items["this"]; + @this._transaction.Invocations.Clear(); + } - private IJobFilterProvider CreateJobFilterProvider(bool followJobRetention = false) + private IJobFilterProvider CreateJobFilterProvider(bool followJobRetention = false) + { + var filters = new JobFilterCollection { - var filters = new JobFilterCollection - { - new ConsoleServerFilter(new ConsoleOptions() { FollowJobRetentionPolicy = followJobRetention }), - _otherFilter.Object - }; - return new JobFilterProviderCollection(filters); - } + new ConsoleServerFilter(new ConsoleOptions { FollowJobRetentionPolicy = followJobRetention }), + _otherFilter.Object + }; + return new JobFilterProviderCollection(filters); + } - private PerformContext CreatePerformContext() + private PerformContext CreatePerformContext() + { + var context = new PerformContext( + _jobStorage.Object, + _connection.Object, + new BackgroundJob("1", Job.FromExpression(() => JobMethod(null)), DateTime.UtcNow), + _cancellationToken.Object) { - var context = new PerformContext( - _jobStorage.Object, - _connection.Object, - new BackgroundJob("1", Job.FromExpression(() => JobMethod(null)), DateTime.UtcNow), - _cancellationToken.Object) + Items = { - Items = - { - ["this"] = this - } - }; - return context; - } + ["this"] = this + } + }; + return context; + } - private StateData CreateState(string stateName) + private StateData CreateState(string stateName) => new() + { + Name = stateName, + Data = new Dictionary { - return new StateData() - { - Name = stateName, - Data = new Dictionary() - { - ["StartedAt"] = JobHelper.SerializeDateTime(DateTime.UtcNow) - } - }; + ["StartedAt"] = JobHelper.SerializeDateTime(DateTime.UtcNow) } - - } + }; } diff --git a/tests/Hangfire.Console.Tests/States/ConsoleApplyStateFilterFacts.cs b/tests/Hangfire.Console.Tests/States/ConsoleApplyStateFilterFacts.cs index 5b7f0f6..4ab6c4a 100644 --- a/tests/Hangfire.Console.Tests/States/ConsoleApplyStateFilterFacts.cs +++ b/tests/Hangfire.Console.Tests/States/ConsoleApplyStateFilterFacts.cs @@ -1,208 +1,201 @@ -using Hangfire.Common; +using System; +using System.Collections.Generic; +using Hangfire.Common; using Hangfire.Console.States; using Hangfire.States; using Hangfire.Storage; using Hangfire.Storage.Monitoring; using Moq; -using System; -using System.Collections.Generic; using Xunit; -namespace Hangfire.Console.Tests.States -{ - public class ConsoleApplyStateFilterFacts - { - private readonly Mock _otherFilter; - private readonly Mock _storage; - private readonly Mock _connection; - private readonly Mock _transaction; - private readonly Mock _monitoring; +namespace Hangfire.Console.Tests.States; - public ConsoleApplyStateFilterFacts() - { - _otherFilter = new Mock(); - _storage = new Mock(); - _connection = new Mock(); - _transaction = new Mock(); - _monitoring = new Mock(); - - _storage.Setup(x => x.GetConnection()) - .Returns(_connection.Object); - _storage.Setup(x => x.GetMonitoringApi()) - .Returns(_monitoring.Object); - - _connection.Setup(x => x.CreateWriteTransaction()) - .Returns(_transaction.Object); - } - - [Fact] - public void UsesFinalJobExpirationTimeoutValue() - { - _otherFilter.Setup(x => x.OnStateApplied(It.IsAny(), It.IsAny())) - .Callback((c, _) => c.JobExpirationTimeout = TimeSpan.FromSeconds(123)); - _connection.Setup(x => x.GetJobData("1")) - .Returns(CreateJobData(ProcessingState.StateName)); - _monitoring.Setup(x => x.JobDetails("1")) - .Returns(CreateJobDetails()); +public class ConsoleApplyStateFilterFacts +{ + private readonly Mock _connection; - var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider()); - var context = CreateStateChangeContext(new MockSucceededState()); + private readonly Mock _monitoring; - stateChanger.ChangeState(context); + private readonly Mock _otherFilter; - _transaction.Verify(x => x.ExpireJob(It.IsAny(), TimeSpan.FromSeconds(123))); - _transaction.Verify(x => x.ExpireSet(It.IsAny(), TimeSpan.FromSeconds(123))); - } + private readonly Mock _storage; - [Fact] - public void DoesNotExpire_IfNotFollowsJobRetention() - { - _connection.Setup(x => x.GetJobData("1")) - .Returns(CreateJobData(ProcessingState.StateName)); - _monitoring.Setup(x => x.JobDetails("1")) - .Returns(CreateJobDetails()); + private readonly Mock _transaction; - var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider(false)); - var context = CreateStateChangeContext(new MockSucceededState()); + public ConsoleApplyStateFilterFacts() + { + _otherFilter = new Mock(); + _storage = new Mock(); + _connection = new Mock(); + _transaction = new Mock(); + _monitoring = new Mock(); + + _storage.Setup(x => x.GetConnection()) + .Returns(_connection.Object); + _storage.Setup(x => x.GetMonitoringApi()) + .Returns(_monitoring.Object); + + _connection.Setup(x => x.CreateWriteTransaction()) + .Returns(_transaction.Object); + } - stateChanger.ChangeState(context); + [Fact] + public void UsesFinalJobExpirationTimeoutValue() + { + _otherFilter.Setup(x => x.OnStateApplied(It.IsAny(), It.IsAny())) + .Callback((c, _) => c.JobExpirationTimeout = TimeSpan.FromSeconds(123)); + _connection.Setup(x => x.GetJobData("1")) + .Returns(CreateJobData(ProcessingState.StateName)); + _monitoring.Setup(x => x.JobDetails("1")) + .Returns(CreateJobDetails()); - _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny()), Times.Never); - _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny()), Times.Never); - } + var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider()); + var context = CreateStateChangeContext(new MockSucceededState()); - [Fact] - public void Expires_IfStateIsFinal() - { - _connection.Setup(x => x.GetJobData("1")) - .Returns(CreateJobData(ProcessingState.StateName)); - _monitoring.Setup(x => x.JobDetails("1")) - .Returns(CreateJobDetails()); + stateChanger.ChangeState(context); - var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider()); - var context = CreateStateChangeContext(new MockSucceededState()); + _transaction.Verify(x => x.ExpireJob(It.IsAny(), TimeSpan.FromSeconds(123))); + _transaction.Verify(x => x.ExpireSet(It.IsAny(), TimeSpan.FromSeconds(123))); + } - stateChanger.ChangeState(context); + [Fact] + public void DoesNotExpire_IfNotFollowsJobRetention() + { + _connection.Setup(x => x.GetJobData("1")) + .Returns(CreateJobData(ProcessingState.StateName)); + _monitoring.Setup(x => x.JobDetails("1")) + .Returns(CreateJobDetails()); - _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny())); - } + var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider(false)); + var context = CreateStateChangeContext(new MockSucceededState()); - [Fact] - public void Persists_IfStateIsNotFinal() - { - _connection.Setup(x => x.GetJobData("1")) - .Returns(CreateJobData(ProcessingState.StateName)); - _monitoring.Setup(x => x.JobDetails("1")) - .Returns(CreateJobDetails()); + stateChanger.ChangeState(context); - var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider()); - var context = CreateStateChangeContext(new MockFailedState()); + _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny()), Times.Never); + _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny()), Times.Never); + } - stateChanger.ChangeState(context); + [Fact] + public void Expires_IfStateIsFinal() + { + _connection.Setup(x => x.GetJobData("1")) + .Returns(CreateJobData(ProcessingState.StateName)); + _monitoring.Setup(x => x.JobDetails("1")) + .Returns(CreateJobDetails()); - _transaction.Verify(x => x.PersistSet(It.IsAny())); - _transaction.Verify(x => x.PersistHash(It.IsAny())); - } + var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider()); + var context = CreateStateChangeContext(new MockSucceededState()); - private IJobFilterProvider CreateJobFilterProvider(bool followJobRetention = true) - { - var filters = new JobFilterCollection(); - filters.Add(new ConsoleApplyStateFilter(new ConsoleOptions() { FollowJobRetentionPolicy = followJobRetention }), int.MaxValue); - filters.Add(_otherFilter.Object); - return new JobFilterProviderCollection(filters); - } + stateChanger.ChangeState(context); - public class MockSucceededState : IState - { - public string Name => SucceededState.StateName; + _transaction.Verify(x => x.ExpireSet(It.IsAny(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(It.IsAny(), It.IsAny())); + } - public string Reason => null; + [Fact] + public void Persists_IfStateIsNotFinal() + { + _connection.Setup(x => x.GetJobData("1")) + .Returns(CreateJobData(ProcessingState.StateName)); + _monitoring.Setup(x => x.JobDetails("1")) + .Returns(CreateJobDetails()); - public bool IsFinal => true; + var stateChanger = new BackgroundJobStateChanger(CreateJobFilterProvider()); + var context = CreateStateChangeContext(new MockFailedState()); - public bool IgnoreJobLoadException => false; + stateChanger.ChangeState(context); - public Dictionary SerializeData() - { - return new Dictionary(); - } - } + _transaction.Verify(x => x.PersistSet(It.IsAny())); + _transaction.Verify(x => x.PersistHash(It.IsAny())); + } - public class MockFailedState : IState - { - public string Name => FailedState.StateName; + private IJobFilterProvider CreateJobFilterProvider(bool followJobRetention = true) + { + var filters = new JobFilterCollection(); + filters.Add(new ConsoleApplyStateFilter(new ConsoleOptions { FollowJobRetentionPolicy = followJobRetention }), int.MaxValue); + filters.Add(_otherFilter.Object); + return new JobFilterProviderCollection(filters); + } - public string Reason => null; + private StateChangeContext CreateStateChangeContext(IState state) => new(_storage.Object, _connection.Object, "1", state); - public bool IsFinal => false; + // ReSharper disable once RedundantDisableWarningComment +#pragma warning disable xUnit1013 + public static void JobMethod() +#pragma warning restore xUnit1013 + { } - public bool IgnoreJobLoadException => false; + private JobDetailsDto CreateJobDetails() + { + var date = DateTime.UtcNow.AddHours(-1); + var history = new List(); - public Dictionary SerializeData() + history.Add(new StateHistoryDto + { + StateName = EnqueuedState.StateName, + CreatedAt = date, + Data = new Dictionary { - return new Dictionary(); + ["EnqueuedAt"] = JobHelper.SerializeDateTime(date), + ["Queue"] = EnqueuedState.DefaultQueue } - } + }); - private StateChangeContext CreateStateChangeContext(IState state) + history.Add(new StateHistoryDto { - return new StateChangeContext(_storage.Object, _connection.Object, "1", state); - } + StateName = ProcessingState.StateName, + CreatedAt = date.AddSeconds(2), + Data = new Dictionary + { + ["StartedAt"] = JobHelper.SerializeDateTime(date.AddSeconds(2)), + ["ServerId"] = "SERVER-1", + ["WorkerId"] = "WORKER-1" + } + }); - // ReSharper disable once RedundantDisableWarningComment -#pragma warning disable xUnit1013 - public static void JobMethod() -#pragma warning restore xUnit1013 + history.Reverse(); + + return new JobDetailsDto { - } + CreatedAt = history[0].CreatedAt, + Job = Job.FromExpression(() => JobMethod()), + History = history + }; + } - private JobDetailsDto CreateJobDetails() + private JobData CreateJobData(string state) + { + return new JobData { - var date = DateTime.UtcNow.AddHours(-1); - var history = new List(); + CreatedAt = DateTime.UtcNow.AddHours(-1), + Job = Job.FromExpression(() => JobMethod()), + State = state + }; + } - history.Add(new StateHistoryDto() - { - StateName = EnqueuedState.StateName, - CreatedAt = date, - Data = new Dictionary() - { - ["EnqueuedAt"] = JobHelper.SerializeDateTime(date), - ["Queue"] = EnqueuedState.DefaultQueue - } - }); - - history.Add(new StateHistoryDto() - { - StateName = ProcessingState.StateName, - CreatedAt = date.AddSeconds(2), - Data = new Dictionary() - { - ["StartedAt"] = JobHelper.SerializeDateTime(date.AddSeconds(2)), - ["ServerId"] = "SERVER-1", - ["WorkerId"] = "WORKER-1" - } - }); - - history.Reverse(); - - return new JobDetailsDto() - { - CreatedAt = history[0].CreatedAt, - Job = Job.FromExpression(() => JobMethod()), - History = history - }; - } + public class MockSucceededState : IState + { + public string Name => SucceededState.StateName; - private JobData CreateJobData(string state) - { - return new JobData() - { - CreatedAt = DateTime.UtcNow.AddHours(-1), - Job = Job.FromExpression(() => JobMethod()), - State = state - }; - } + public string Reason => null; + + public bool IsFinal => true; + + public bool IgnoreJobLoadException => false; + + public Dictionary SerializeData() => new(); + } + + public class MockFailedState : IState + { + public string Name => FailedState.StateName; + + public string Reason => null; + + public bool IsFinal => false; + + public bool IgnoreJobLoadException => false; + + public Dictionary SerializeData() => new(); } } diff --git a/tests/Hangfire.Console.Tests/Storage/ConsoleExpirationTransactionFacts.cs b/tests/Hangfire.Console.Tests/Storage/ConsoleExpirationTransactionFacts.cs index 723520a..c5360f6 100644 --- a/tests/Hangfire.Console.Tests/Storage/ConsoleExpirationTransactionFacts.cs +++ b/tests/Hangfire.Console.Tests/Storage/ConsoleExpirationTransactionFacts.cs @@ -1,79 +1,77 @@ -using Hangfire.Console.Serialization; +using System; +using Hangfire.Console.Serialization; using Hangfire.Console.Storage; using Hangfire.Storage; using Moq; -using System; using Xunit; -namespace Hangfire.Console.Tests.Storage -{ - public class ConsoleExpirationTransactionFacts - { - private readonly ConsoleId _consoleId = new("1", DateTime.UtcNow); +namespace Hangfire.Console.Tests.Storage; - private readonly Mock _transaction = new(); +public class ConsoleExpirationTransactionFacts +{ + private readonly ConsoleId _consoleId = new("1", DateTime.UtcNow); - [Fact] - public void Ctor_ThrowsException_IfTransactionIsNull() - { - Assert.Throws("transaction", () => new ConsoleExpirationTransaction(null)); - } + private readonly Mock _transaction = new(); - [Fact] - public void Dispose_ReallyDisposesTransaction() - { - var expiration = new ConsoleExpirationTransaction(_transaction.Object); + [Fact] + public void Ctor_ThrowsException_IfTransactionIsNull() + { + Assert.Throws("transaction", () => new ConsoleExpirationTransaction(null)); + } - expiration.Dispose(); + [Fact] + public void Dispose_ReallyDisposesTransaction() + { + var expiration = new ConsoleExpirationTransaction(_transaction.Object); - _transaction.Verify(x => x.Dispose()); - } + expiration.Dispose(); - [Fact] - public void Expire_ThrowsException_IfConsoleIdIsNull() - { - var expiration = new ConsoleExpirationTransaction(_transaction.Object); + _transaction.Verify(x => x.Dispose()); + } - Assert.Throws("consoleId", () => expiration.Expire(null, TimeSpan.FromHours(1))); - } + [Fact] + public void Expire_ThrowsException_IfConsoleIdIsNull() + { + var expiration = new ConsoleExpirationTransaction(_transaction.Object); - [Fact] - public void Expire_ExpiresSetAndHash() - { - var expiration = new ConsoleExpirationTransaction(_transaction.Object); + Assert.Throws("consoleId", () => expiration.Expire(null, TimeSpan.FromHours(1))); + } - expiration.Expire(_consoleId, TimeSpan.FromHours(1)); + [Fact] + public void Expire_ExpiresSetAndHash() + { + var expiration = new ConsoleExpirationTransaction(_transaction.Object); - _transaction.Verify(x => x.ExpireSet(_consoleId.GetSetKey(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(_consoleId.GetHashKey(), It.IsAny())); + expiration.Expire(_consoleId, TimeSpan.FromHours(1)); - // backward compatibility: - _transaction.Verify(x => x.ExpireSet(_consoleId.GetOldConsoleKey(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(_consoleId.GetOldConsoleKey(), It.IsAny())); - } + _transaction.Verify(x => x.ExpireSet(_consoleId.GetSetKey(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(_consoleId.GetHashKey(), It.IsAny())); - [Fact] - public void Persist_ThrowsException_IfConsoleIdIsNull() - { - var expiration = new ConsoleExpirationTransaction(_transaction.Object); + // backward compatibility: + _transaction.Verify(x => x.ExpireSet(_consoleId.GetOldConsoleKey(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(_consoleId.GetOldConsoleKey(), It.IsAny())); + } - Assert.Throws("consoleId", () => expiration.Persist(null)); - } + [Fact] + public void Persist_ThrowsException_IfConsoleIdIsNull() + { + var expiration = new ConsoleExpirationTransaction(_transaction.Object); - [Fact] - public void Persist_PersistsSetAndHash() - { - var expiration = new ConsoleExpirationTransaction(_transaction.Object); + Assert.Throws("consoleId", () => expiration.Persist(null)); + } - expiration.Persist(_consoleId); + [Fact] + public void Persist_PersistsSetAndHash() + { + var expiration = new ConsoleExpirationTransaction(_transaction.Object); - _transaction.Verify(x => x.PersistSet(_consoleId.GetSetKey())); - _transaction.Verify(x => x.PersistHash(_consoleId.GetHashKey())); + expiration.Persist(_consoleId); - // backward compatibility: - _transaction.Verify(x => x.PersistSet(_consoleId.GetOldConsoleKey())); - _transaction.Verify(x => x.PersistHash(_consoleId.GetOldConsoleKey())); - } + _transaction.Verify(x => x.PersistSet(_consoleId.GetSetKey())); + _transaction.Verify(x => x.PersistHash(_consoleId.GetHashKey())); + // backward compatibility: + _transaction.Verify(x => x.PersistSet(_consoleId.GetOldConsoleKey())); + _transaction.Verify(x => x.PersistHash(_consoleId.GetOldConsoleKey())); } } diff --git a/tests/Hangfire.Console.Tests/Storage/ConsoleStorageFacts.cs b/tests/Hangfire.Console.Tests/Storage/ConsoleStorageFacts.cs index 6dfcfcc..79d2101 100644 --- a/tests/Hangfire.Console.Tests/Storage/ConsoleStorageFacts.cs +++ b/tests/Hangfire.Console.Tests/Storage/ConsoleStorageFacts.cs @@ -1,442 +1,446 @@ -using Hangfire.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Hangfire.Common; using Hangfire.Console.Serialization; using Hangfire.Console.Storage; using Hangfire.States; using Hangfire.Storage; using Moq; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using Xunit; using KVP = System.Collections.Generic.KeyValuePair; -namespace Hangfire.Console.Tests.Storage +namespace Hangfire.Console.Tests.Storage; + +public class ConsoleStorageFacts { - public class ConsoleStorageFacts + private readonly Mock _connection; + + private readonly ConsoleId _consoleId; + + private readonly Mock _transaction; + + public ConsoleStorageFacts() { - private readonly ConsoleId _consoleId; + _consoleId = new ConsoleId("1", DateTime.UtcNow); - private readonly Mock _connection; - private readonly Mock _transaction; + _connection = new Mock(); + _transaction = new Mock(); - public ConsoleStorageFacts() - { - _consoleId = new ConsoleId("1", DateTime.UtcNow); + _connection.Setup(x => x.CreateWriteTransaction()) + .Returns(_transaction.Object); + } - _connection = new Mock(); - _transaction = new Mock(); + [Fact] + public void Ctor_ThrowsException_IfConnectionIsNull() + { + Assert.Throws("connection", () => new ConsoleStorage(null)); + } - _connection.Setup(x => x.CreateWriteTransaction()) - .Returns(_transaction.Object); - } + [Fact] + public void Ctor_ThrowsException_IfNotImplementsJobStorageConnection() + { + var dummyConnection = new Mock(); - [Fact] - public void Ctor_ThrowsException_IfConnectionIsNull() - { - Assert.Throws("connection", () => new ConsoleStorage(null)); - } + Assert.Throws(() => new ConsoleStorage(dummyConnection.Object)); + } - [Fact] - public void Ctor_ThrowsException_IfNotImplementsJobStorageConnection() - { - var dummyConnection = new Mock(); + [Fact] + public void Dispose_ReallyDisposesConnection() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws(() => new ConsoleStorage(dummyConnection.Object)); - } + storage.Dispose(); - [Fact] - public void Dispose_ReallyDisposesConnection() - { - var storage = new ConsoleStorage(_connection.Object); + _connection.Verify(x => x.Dispose()); + } - storage.Dispose(); + [Fact] + public void InitConsole_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - _connection.Verify(x => x.Dispose()); - } + Assert.Throws("consoleId", () => storage.InitConsole(null)); + } - [Fact] - public void InitConsole_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void InitConsole_ThrowsException_IfNotImplementsJobStorageTransaction() + { + var dummyTransaction = new Mock(); + _connection.Setup(x => x.CreateWriteTransaction()) + .Returns(dummyTransaction.Object); - Assert.Throws("consoleId", () => storage.InitConsole(null)); - } + var storage = new ConsoleStorage(_connection.Object); - [Fact] - public void InitConsole_ThrowsException_IfNotImplementsJobStorageTransaction() - { - var dummyTransaction = new Mock(); - _connection.Setup(x => x.CreateWriteTransaction()) - .Returns(dummyTransaction.Object); + Assert.Throws(() => storage.InitConsole(_consoleId)); + } - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void InitConsole_JobIdIsAddedToHash() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws(() => storage.InitConsole(_consoleId)); - } + storage.InitConsole(_consoleId); - [Fact] - public void InitConsole_JobIdIsAddedToHash() - { - var storage = new ConsoleStorage(_connection.Object); + _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); + _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It2.AnyIs(p => p.Key == "jobId"))); + _transaction.Verify(x => x.Commit(), Times.Once); + } - storage.InitConsole(_consoleId); + [Fact] + public void AddLine_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); - _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It2.AnyIs(p => p.Key == "jobId"))); - _transaction.Verify(x => x.Commit(), Times.Once); - } + Assert.Throws("consoleId", () => storage.AddLine(null, new ConsoleLine())); + } - [Fact] - public void AddLine_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void AddLine_ThrowsException_IfLineIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.AddLine(null, new ConsoleLine())); - } + Assert.Throws("line", () => storage.AddLine(_consoleId, null)); + } - [Fact] - public void AddLine_ThrowsException_IfLineIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void AddLine_ThrowsException_IfLineIsReference() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("line", () => storage.AddLine(_consoleId, null)); - } + Assert.Throws("line", () => storage.AddLine(_consoleId, new ConsoleLine { IsReference = true })); + } - [Fact] - public void AddLine_ThrowsException_IfLineIsReference() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void AddLine_ShortLineIsAddedToSet() + { + var storage = new ConsoleStorage(_connection.Object); + var line = new ConsoleLine { Message = "test" }; + + storage.AddLine(_consoleId, line); + + Assert.False(line.IsReference); + _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); + _transaction.Verify(x => x.AddToSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())); + _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It.IsAny>()), Times.Never); + _transaction.Verify(x => x.Commit(), Times.Once); + } - Assert.Throws("line", () => storage.AddLine(_consoleId, new ConsoleLine() { IsReference = true })); - } + [Fact] + public void AddLine_LongLineIsAddedToHash_AndReferenceIsAddedToSet() + { + var storage = new ConsoleStorage(_connection.Object); + var line = new ConsoleLine + { + Message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " + + "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " + + "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " + + "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + }; + + storage.AddLine(_consoleId, line); + + Assert.True(line.IsReference); + _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); + _transaction.Verify(x => x.AddToSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())); + _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It2.AnyIs(p => p.Key == line.Message))); + _transaction.Verify(x => x.Commit(), Times.Once); + } - [Fact] - public void AddLine_ShortLineIsAddedToSet() + [Fact] + public void AddLine_ProgressBarIsAddedToSet_AndProgressIsUpdated() + { + var storage = new ConsoleStorage(_connection.Object); + var line = new ConsoleLine { - var storage = new ConsoleStorage(_connection.Object); - var line = new ConsoleLine() { Message = "test" }; + Message = "1", + ProgressValue = 10 + }; - storage.AddLine(_consoleId, line); + storage.AddLine(_consoleId, line); - Assert.False(line.IsReference); - _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); - _transaction.Verify(x => x.AddToSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())); - _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It.IsAny>()), Times.Never); - _transaction.Verify(x => x.Commit(), Times.Once); - } + Assert.False(line.IsReference); + _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); + _transaction.Verify(x => x.AddToSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())); + _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It2.AnyIs(p => p.Key == "progress"))); + _transaction.Verify(x => x.Commit(), Times.Once); + } - [Fact] - public void AddLine_LongLineIsAddedToHash_AndReferenceIsAddedToSet() - { - var storage = new ConsoleStorage(_connection.Object); - var line = new ConsoleLine() - { - Message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + - "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " + - "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " + - "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " + - "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." - }; - - storage.AddLine(_consoleId, line); - - Assert.True(line.IsReference); - _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); - _transaction.Verify(x => x.AddToSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())); - _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It2.AnyIs(p => p.Key == line.Message))); - _transaction.Verify(x => x.Commit(), Times.Once); - } - - - [Fact] - public void AddLine_ProgressBarIsAddedToSet_AndProgressIsUpdated() - { - var storage = new ConsoleStorage(_connection.Object); - var line = new ConsoleLine() - { - Message = "1", - ProgressValue = 10 - }; - - storage.AddLine(_consoleId, line); - - Assert.False(line.IsReference); - _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); - _transaction.Verify(x => x.AddToSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())); - _transaction.Verify(x => x.SetRangeInHash(_consoleId.GetHashKey(), It2.AnyIs(p => p.Key == "progress"))); - _transaction.Verify(x => x.Commit(), Times.Once); - } - - [Fact] - public void Expire_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void Expire_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.Expire(null, TimeSpan.FromHours(1))); - } + Assert.Throws("consoleId", () => storage.Expire(null, TimeSpan.FromHours(1))); + } - [Fact] - public void Expire_ExpiresSetAndHash() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void Expire_ExpiresSetAndHash() + { + var storage = new ConsoleStorage(_connection.Object); - storage.Expire(_consoleId, TimeSpan.FromHours(1)); + storage.Expire(_consoleId, TimeSpan.FromHours(1)); - _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); - _transaction.Verify(x => x.ExpireSet(_consoleId.GetSetKey(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(_consoleId.GetHashKey(), It.IsAny())); - _transaction.Verify(x => x.Commit(), Times.Once); - } + _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); + _transaction.Verify(x => x.ExpireSet(_consoleId.GetSetKey(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(_consoleId.GetHashKey(), It.IsAny())); + _transaction.Verify(x => x.Commit(), Times.Once); + } - [Fact] - public void Expire_ExpiresOldSetAndHashKeysEither_ForBackwardsCompatibility() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void Expire_ExpiresOldSetAndHashKeysEither_ForBackwardsCompatibility() + { + var storage = new ConsoleStorage(_connection.Object); - storage.Expire(_consoleId, TimeSpan.FromHours(1)); + storage.Expire(_consoleId, TimeSpan.FromHours(1)); - _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); - _transaction.Verify(x => x.ExpireSet(_consoleId.GetOldConsoleKey(), It.IsAny())); - _transaction.Verify(x => x.ExpireHash(_consoleId.GetOldConsoleKey(), It.IsAny())); - _transaction.Verify(x => x.Commit(), Times.Once); - } + _connection.Verify(x => x.CreateWriteTransaction(), Times.Once); + _transaction.Verify(x => x.ExpireSet(_consoleId.GetOldConsoleKey(), It.IsAny())); + _transaction.Verify(x => x.ExpireHash(_consoleId.GetOldConsoleKey(), It.IsAny())); + _transaction.Verify(x => x.Commit(), Times.Once); + } - [Fact] - public void GetConsoleTtl_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void GetConsoleTtl_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.GetConsoleTtl(null)); - } + Assert.Throws("consoleId", () => storage.GetConsoleTtl(null)); + } - [Fact] - public void GetConsoleTtl_ReturnsTtlOfHash() - { - _connection.Setup(x => x.GetHashTtl(_consoleId.GetHashKey())) - .Returns(TimeSpan.FromSeconds(123)); + [Fact] + public void GetConsoleTtl_ReturnsTtlOfHash() + { + _connection.Setup(x => x.GetHashTtl(_consoleId.GetHashKey())) + .Returns(TimeSpan.FromSeconds(123)); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var ttl = storage.GetConsoleTtl(_consoleId); + var ttl = storage.GetConsoleTtl(_consoleId); - Assert.Equal(TimeSpan.FromSeconds(123), ttl); - } + Assert.Equal(TimeSpan.FromSeconds(123), ttl); + } - [Fact] - public void GetLineCount_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void GetLineCount_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.GetLineCount(null)); - } + Assert.Throws("consoleId", () => storage.GetLineCount(null)); + } - [Fact] - public void GetLineCount_ReturnsCountOfSet() - { - _connection.Setup(x => x.GetSetCount(_consoleId.GetSetKey())) - .Returns(123); + [Fact] + public void GetLineCount_ReturnsCountOfSet() + { + _connection.Setup(x => x.GetSetCount(_consoleId.GetSetKey())) + .Returns(123); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var count = storage.GetLineCount(_consoleId); + var count = storage.GetLineCount(_consoleId); - Assert.Equal(123, count); - } + Assert.Equal(123, count); + } - [Fact] - public void GetLineCount_ReturnsCountOfOldSet_WhenNewOneReturnsZero_ForBackwardsCompatibility() - { - _connection.Setup(x => x.GetSetCount(_consoleId.GetSetKey())).Returns(0); - _connection.Setup(x => x.GetSetCount(_consoleId.GetOldConsoleKey())).Returns(123); + [Fact] + public void GetLineCount_ReturnsCountOfOldSet_WhenNewOneReturnsZero_ForBackwardsCompatibility() + { + _connection.Setup(x => x.GetSetCount(_consoleId.GetSetKey())).Returns(0); + _connection.Setup(x => x.GetSetCount(_consoleId.GetOldConsoleKey())).Returns(123); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var count = storage.GetLineCount(_consoleId); + var count = storage.GetLineCount(_consoleId); - Assert.Equal(123, count); - } + Assert.Equal(123, count); + } - [Fact] - public void GetLines_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void GetLines_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.GetLines(null, 0, 1).ToArray()); - } + Assert.Throws("consoleId", () => storage.GetLines(null, 0, 1).ToArray()); + } - [Fact] - public void GetLines_ReturnsRangeFromSet() + [Fact] + public void GetLines_ReturnsRangeFromSet() + { + var lines = new[] { - var lines = new[] { - new ConsoleLine { TimeOffset = 0, Message = "line1" }, - new ConsoleLine { TimeOffset = 1, Message = "line2" }, - new ConsoleLine { TimeOffset = 2, Message = "line3" }, - new ConsoleLine { TimeOffset = 3, Message = "line4" }, - }; + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }, + new ConsoleLine { TimeOffset = 2, Message = "line3" }, + new ConsoleLine { TimeOffset = 3, Message = "line4" } + }; - _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())) - .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); + _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())) + .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetLines(_consoleId, 1, 2).ToArray(); + var result = storage.GetLines(_consoleId, 1, 2).ToArray(); - Assert.Equal(lines.Skip(1).Take(2).Select(x => x.Message), result.Select(x => x.Message)); - } + Assert.Equal(lines.Skip(1).Take(2).Select(x => x.Message), result.Select(x => x.Message)); + } - [Fact] - public void GetLines_ReturnsRangeFromOldSet_ForBackwardsCompatibility() + [Fact] + public void GetLines_ReturnsRangeFromOldSet_ForBackwardsCompatibility() + { + var lines = new[] { - var lines = new[] { - new ConsoleLine { TimeOffset = 0, Message = "line1" }, - new ConsoleLine { TimeOffset = 1, Message = "line2" }, - new ConsoleLine { TimeOffset = 2, Message = "line3" }, - new ConsoleLine { TimeOffset = 3, Message = "line4" }, - }; + new ConsoleLine { TimeOffset = 0, Message = "line1" }, + new ConsoleLine { TimeOffset = 1, Message = "line2" }, + new ConsoleLine { TimeOffset = 2, Message = "line3" }, + new ConsoleLine { TimeOffset = 3, Message = "line4" } + }; - _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetOldConsoleKey(), It.IsAny(), It.IsAny())) - .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); + _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetOldConsoleKey(), It.IsAny(), It.IsAny())) + .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetLines(_consoleId, 1, 2).ToArray(); + var result = storage.GetLines(_consoleId, 1, 2).ToArray(); - Assert.Equal(lines.Skip(1).Take(2).Select(x => x.Message), result.Select(x => x.Message)); - } + Assert.Equal(lines.Skip(1).Take(2).Select(x => x.Message), result.Select(x => x.Message)); + } - [Fact] - public void GetLines_ExpandsReferences() + [Fact] + public void GetLines_ExpandsReferences() + { + var lines = new[] { - var lines = new[] { - new ConsoleLine { TimeOffset = 0, Message = "line1", IsReference = true } - }; + new ConsoleLine { TimeOffset = 0, Message = "line1", IsReference = true } + }; - _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())) - .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); - _connection.Setup(x => x.GetValueFromHash(_consoleId.GetHashKey(), It.IsAny())) - .Returns("Dereferenced Line"); + _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetSetKey(), It.IsAny(), It.IsAny())) + .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); + _connection.Setup(x => x.GetValueFromHash(_consoleId.GetHashKey(), It.IsAny())) + .Returns("Dereferenced Line"); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetLines(_consoleId, 0, 1).Single(); + var result = storage.GetLines(_consoleId, 0, 1).Single(); - Assert.False(result.IsReference); - Assert.Equal("Dereferenced Line", result.Message); - } + Assert.False(result.IsReference); + Assert.Equal("Dereferenced Line", result.Message); + } - [Fact] - public void GetLines_ExpandsReferencesFromOldHash_ForBackwardsCompatibility() + [Fact] + public void GetLines_ExpandsReferencesFromOldHash_ForBackwardsCompatibility() + { + var lines = new[] { - var lines = new[] { - new ConsoleLine { TimeOffset = 0, Message = "line1", IsReference = true } - }; + new ConsoleLine { TimeOffset = 0, Message = "line1", IsReference = true } + }; - _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetOldConsoleKey(), It.IsAny(), It.IsAny())) - .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); - _connection.Setup(x => x.GetValueFromHash(_consoleId.GetOldConsoleKey(), It.IsAny())) - .Returns("Dereferenced Line"); + _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetOldConsoleKey(), It.IsAny(), It.IsAny())) + .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); + _connection.Setup(x => x.GetValueFromHash(_consoleId.GetOldConsoleKey(), It.IsAny())) + .Returns("Dereferenced Line"); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetLines(_consoleId, 0, 1).Single(); + var result = storage.GetLines(_consoleId, 0, 1).Single(); - Assert.False(result.IsReference); - Assert.Equal("Dereferenced Line", result.Message); - } + Assert.False(result.IsReference); + Assert.Equal("Dereferenced Line", result.Message); + } - [Fact] - public void GetLines_HandlesHashException_WhenTryingToExpandReferences() + [Fact] + public void GetLines_HandlesHashException_WhenTryingToExpandReferences() + { + var lines = new[] { - var lines = new[] { - new ConsoleLine { TimeOffset = 0, Message = "line1", IsReference = true } - }; + new ConsoleLine { TimeOffset = 0, Message = "line1", IsReference = true } + }; - _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetOldConsoleKey(), It.IsAny(), It.IsAny())) - .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); + _connection.Setup(x => x.GetRangeFromSet(_consoleId.GetOldConsoleKey(), It.IsAny(), It.IsAny())) + .Returns((string _, int start, int end) => lines.Where((_, i) => i >= start && i <= end).Select(SerializationHelper.Serialize).ToList()); - _connection.Setup(x => x.GetValueFromHash(_consoleId.GetOldConsoleKey(), It.IsAny())) - .Throws(new NotSupportedException()); + _connection.Setup(x => x.GetValueFromHash(_consoleId.GetOldConsoleKey(), It.IsAny())) + .Throws(new NotSupportedException()); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetLines(_consoleId, 0, 1).Single(); + var result = storage.GetLines(_consoleId, 0, 1).Single(); - Assert.False(result.IsReference); - Assert.Equal("line1", result.Message); - } + Assert.False(result.IsReference); + Assert.Equal("line1", result.Message); + } - [Fact] - public void GetState_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void GetState_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.GetState(null)); - } + Assert.Throws("consoleId", () => storage.GetState(null)); + } - [Fact] - public void GetState_ReturnsStateData() + [Fact] + public void GetState_ReturnsStateData() + { + var state = new StateData { - var state = new StateData() - { - Name = ProcessingState.StateName, - Data = new Dictionary() - }; + Name = ProcessingState.StateName, + Data = new Dictionary() + }; - _connection.Setup(x => x.GetStateData(It.IsAny())) - .Returns(state); + _connection.Setup(x => x.GetStateData(It.IsAny())) + .Returns(state); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetState(_consoleId); + var result = storage.GetState(_consoleId); - Assert.Same(state, result); - } + Assert.Same(state, result); + } - [Fact] - public void GetProgress_ThrowsException_IfConsoleIdIsNull() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void GetProgress_ThrowsException_IfConsoleIdIsNull() + { + var storage = new ConsoleStorage(_connection.Object); - Assert.Throws("consoleId", () => storage.GetProgress(null)); - } + Assert.Throws("consoleId", () => storage.GetProgress(null)); + } - [Fact] - public void GetProgress_ReturnsNull_IfProgressNotPresent() - { - var storage = new ConsoleStorage(_connection.Object); + [Fact] + public void GetProgress_ReturnsNull_IfProgressNotPresent() + { + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetProgress(_consoleId); + var result = storage.GetProgress(_consoleId); - Assert.Null(result); - } + Assert.Null(result); + } - [Fact] - public void GetProgress_ReturnsNull_IfValueIsInvalid() - { - _connection.Setup(x => x.GetValueFromHash(It.IsAny(), It.IsIn("progress"))) - .Returns("null"); + [Fact] + public void GetProgress_ReturnsNull_IfValueIsInvalid() + { + _connection.Setup(x => x.GetValueFromHash(It.IsAny(), It.IsIn("progress"))) + .Returns("null"); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetProgress(_consoleId); + var result = storage.GetProgress(_consoleId); - Assert.Null(result); - } + Assert.Null(result); + } - [Fact] - public void GetProgress_ReturnsProgressValue() - { - const double progress = 12.5; + [Fact] + public void GetProgress_ReturnsProgressValue() + { + const double progress = 12.5; - _connection.Setup(x => x.GetValueFromHash(It.IsAny(), It.IsIn("progress"))) - .Returns(progress.ToString(CultureInfo.InvariantCulture)); + _connection.Setup(x => x.GetValueFromHash(It.IsAny(), It.IsIn("progress"))) + .Returns(progress.ToString(CultureInfo.InvariantCulture)); - var storage = new ConsoleStorage(_connection.Object); + var storage = new ConsoleStorage(_connection.Object); - var result = storage.GetProgress(_consoleId); + var result = storage.GetProgress(_consoleId); - Assert.Equal(progress, result); - } + Assert.Equal(progress, result); } } diff --git a/tests/Hangfire.Console.Tests/Support/It2.cs b/tests/Hangfire.Console.Tests/Support/It2.cs index 6e8299b..1ac7aef 100644 --- a/tests/Hangfire.Console.Tests/Support/It2.cs +++ b/tests/Hangfire.Console.Tests/Support/It2.cs @@ -5,17 +5,16 @@ using Moq; // ReSharper disable once CheckNamespace -namespace Hangfire.Console.Tests +namespace Hangfire.Console.Tests; + +public static class It2 { - public static class It2 + public static IEnumerable AnyIs(Expression> match) { - public static IEnumerable AnyIs(Expression> match) - { - var predicate = match.Compile(); + var predicate = match.Compile(); - return Match.Create( - values => values.Any(predicate), - () => AnyIs(match)); - } + return Match.Create( + values => values.Any(predicate), + () => AnyIs(match)); } }