151 lines
5.3 KiB
C#
151 lines
5.3 KiB
C#
using System.CommandLine;
|
|
using ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;
|
|
|
|
/// <summary>
|
|
/// #54: <c>template script add/update</c> must expose <c>--min-time-between-runs</c>
|
|
/// (a duration with ms/s/min units) and <c>--execution-timeout-seconds</c> (an int)
|
|
/// so the per-script throttle/re-fire interval and the execution-timeout override —
|
|
/// previously settable only via Transport bundle import — are CLI-authorable. These
|
|
/// tests pin the option surface and the duration-parsing semantics of
|
|
/// <see cref="TemplateCommands.TryParseMinTimeBetweenRuns"/>.
|
|
/// </summary>
|
|
public class TemplateScriptTimingTests
|
|
{
|
|
private static readonly Option<string> Url = new("--url") { Recursive = true };
|
|
private static readonly Option<string> Username = new("--username") { Recursive = true };
|
|
private static readonly Option<string> Password = new("--password") { Recursive = true };
|
|
private static readonly Option<string> Format = CliOptions.CreateFormatOption();
|
|
|
|
private static Command ScriptGroup()
|
|
=> TemplateCommands.Build(Url, Format, Username, Password)
|
|
.Subcommands.Single(c => c.Name == "script");
|
|
|
|
// ---- option surface ----
|
|
|
|
[Fact]
|
|
public void ScriptAdd_HasTimingOptions()
|
|
{
|
|
var add = ScriptGroup().Subcommands.Single(c => c.Name == "add");
|
|
var names = add.Options.Select(o => o.Name).ToArray();
|
|
Assert.Contains("--min-time-between-runs", names);
|
|
Assert.Contains("--execution-timeout-seconds", names);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScriptUpdate_HasTimingOptions()
|
|
{
|
|
var update = ScriptGroup().Subcommands.Single(c => c.Name == "update");
|
|
var names = update.Options.Select(o => o.Name).ToArray();
|
|
Assert.Contains("--min-time-between-runs", names);
|
|
Assert.Contains("--execution-timeout-seconds", names);
|
|
}
|
|
|
|
// ---- --min-time-between-runs parsing ----
|
|
|
|
[Fact]
|
|
public void MinTime_Absent_IsUnsetNoError()
|
|
{
|
|
Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(null, out var d, out var err));
|
|
Assert.Null(d);
|
|
Assert.Null(err);
|
|
}
|
|
|
|
[Fact]
|
|
public void MinTime_Blank_IsUnsetNoError()
|
|
{
|
|
Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(" ", out var d, out var err));
|
|
Assert.Null(d);
|
|
Assert.Null(err);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("500ms", 500)]
|
|
[InlineData("250MS", 250)]
|
|
public void MinTime_Milliseconds_Parsed(string value, int expectedMs)
|
|
{
|
|
Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
|
|
Assert.Null(err);
|
|
Assert.Equal(TimeSpan.FromMilliseconds(expectedMs), d);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("5")] // bare number → seconds
|
|
[InlineData("5s")]
|
|
[InlineData("5sec")]
|
|
[InlineData("5SEC")]
|
|
public void MinTime_Seconds_BareDefaultsToSeconds(string value)
|
|
{
|
|
Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
|
|
Assert.Null(err);
|
|
Assert.Equal(TimeSpan.FromSeconds(5), d);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("2m")]
|
|
[InlineData("2min")]
|
|
[InlineData("2MIN")]
|
|
public void MinTime_Minutes_Parsed(string value)
|
|
{
|
|
Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
|
|
Assert.Null(err);
|
|
Assert.Equal(TimeSpan.FromMinutes(2), d);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("0")]
|
|
[InlineData("0s")]
|
|
[InlineData("0min")]
|
|
public void MinTime_Zero_IsUnset(string value)
|
|
{
|
|
// 0 means "no throttle" → null, mirroring DurationInput.Compose's non-positive handling.
|
|
Assert.True(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
|
|
Assert.Null(err);
|
|
Assert.Null(d);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("abc")]
|
|
[InlineData("5h")] // hours not supported
|
|
[InlineData("5days")]
|
|
[InlineData("-3")]
|
|
[InlineData("1.5s")] // non-integer
|
|
public void MinTime_Invalid_ReturnsError(string value)
|
|
{
|
|
Assert.False(TemplateCommands.TryParseMinTimeBetweenRuns(value, out var d, out var err));
|
|
Assert.Null(d);
|
|
Assert.NotNull(err);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An absurdly large value (16-digit millisecond count) would overflow long/TimeSpan
|
|
/// without the overflow guard. The parser must return false cleanly — not throw.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData("9999999999999999ms")] // 16-digit ms: far exceeds TimeSpan.MaxValue (~922337203685477ms)
|
|
[InlineData("9999999999999999s")] // 16-digit seconds: even larger when scaled
|
|
[InlineData("9999999999999999min")] // 16-digit minutes
|
|
public void MinTime_AbsurdlyLarge_ReturnsFalseWithoutThrowing(string value)
|
|
{
|
|
// Must not throw — TryParse contract: never throws, always returns bool.
|
|
var threw = false;
|
|
bool result = false;
|
|
TimeSpan? duration = null;
|
|
string? error = null;
|
|
try
|
|
{
|
|
result = TemplateCommands.TryParseMinTimeBetweenRuns(value, out duration, out error);
|
|
}
|
|
catch
|
|
{
|
|
threw = true;
|
|
}
|
|
|
|
Assert.False(threw, "TryParseMinTimeBetweenRuns must not throw on large input");
|
|
Assert.False(result);
|
|
Assert.Null(duration);
|
|
Assert.NotNull(error);
|
|
}
|
|
}
|