diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5fadfd59..0b2fd0e3c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Features +- The SDK now supports monitor upserting. You can programmatically set up your monitors via the options callback in `SentrySdk.CaptureCheckIn` ([#3330](https://github.com/getsentry/sentry-dotnet/pull/3330)) - Added an `SentrySdk.RunAsyncVoid` helper method that lets you capture exceptions from `async void` methods ([#3379](https://github.com/getsentry/sentry-dotnet/pull/3379)) ### Fixes diff --git a/src/Sentry/Extensibility/DiagnosticLoggerExtensions.cs b/src/Sentry/Extensibility/DiagnosticLoggerExtensions.cs index ecb2fd4295..cdf744ba3d 100644 --- a/src/Sentry/Extensibility/DiagnosticLoggerExtensions.cs +++ b/src/Sentry/Extensibility/DiagnosticLoggerExtensions.cs @@ -252,6 +252,15 @@ public static void LogError( string message) => logger.LogIfEnabled(SentryLevel.Error, null, message); + /// + /// Log an error message. + /// + public static void LogError( + this IDiagnosticLogger logger, + string message, + TArg arg) + => logger.LogIfEnabled(SentryLevel.Error, null, message, arg); + /// /// Log an exception with an error message. /// diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs index 4014fee97b..731f5a9aee 100644 --- a/src/Sentry/Extensibility/DisabledHub.cs +++ b/src/Sentry/Extensibility/DisabledHub.cs @@ -183,8 +183,13 @@ public void CaptureSession(SessionUpdate sessionUpdate) /// /// No-Op /// - public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, - TimeSpan? duration = null, Scope? scope = null) + public SentryId CaptureCheckIn( + string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, + TimeSpan? duration = null, + Scope? scope = null, + Action? configureMonitorOptions = null) => SentryId.Empty; /// diff --git a/src/Sentry/Extensibility/HubAdapter.cs b/src/Sentry/Extensibility/HubAdapter.cs index a8ffedc4f6..3bf48f74c3 100644 --- a/src/Sentry/Extensibility/HubAdapter.cs +++ b/src/Sentry/Extensibility/HubAdapter.cs @@ -270,9 +270,14 @@ public void CaptureSession(SessionUpdate sessionUpdate) /// /// Forwards the call to . /// - public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, - TimeSpan? duration = null, Scope? scope = null) - => SentrySdk.CaptureCheckIn(monitorSlug, status, sentryId, duration); + public SentryId CaptureCheckIn( + string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, + TimeSpan? duration = null, + Scope? scope = null, + Action? monitorOptions = null) + => SentrySdk.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope, monitorOptions); /// /// Forwards the call to diff --git a/src/Sentry/ISentryClient.cs b/src/Sentry/ISentryClient.cs index e503dabc4d..e3e12fe191 100644 --- a/src/Sentry/ISentryClient.cs +++ b/src/Sentry/ISentryClient.cs @@ -79,8 +79,13 @@ public interface ISentryClient /// /// /// - SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, - TimeSpan? duration = null, Scope? scope = null); + /// The optional monitor config used to create a Check-In programmatically. + SentryId CaptureCheckIn(string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, + TimeSpan? duration = null, + Scope? scope = null, + Action? configureMonitorOptions = null); /// /// Flushes the queue of captured events until the timeout is reached. diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 6a8bf44625..3d1ebdf26a 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -561,9 +561,13 @@ public void CaptureSession(SessionUpdate sessionUpdate) } } - public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, + public SentryId CaptureCheckIn( + string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, TimeSpan? duration = null, - Scope? scope = null) + Scope? scope = null, + Action? configureMonitorOptions = null) { if (!IsEnabled) { @@ -580,7 +584,7 @@ public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryI scope = currentScope; } - return _ownedClient.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope); + return _ownedClient.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope, configureMonitorOptions); } catch (Exception e) { diff --git a/src/Sentry/SentryCheckIn.cs b/src/Sentry/SentryCheckIn.cs index 5965f665a5..c73308369f 100644 --- a/src/Sentry/SentryCheckIn.cs +++ b/src/Sentry/SentryCheckIn.cs @@ -4,7 +4,7 @@ namespace Sentry; /// -/// The Checkin Status +/// The Check-In Status /// public enum CheckInStatus { @@ -25,13 +25,13 @@ public enum CheckInStatus } /// -/// Sentry Checkin +/// Sentry Check-In /// // https://develop.sentry.dev/sdk/check-ins/ public class SentryCheckIn : ISentryJsonSerializable { /// - /// CheckIn ID + /// Check-In ID /// public SentryId Id { get; } @@ -40,13 +40,14 @@ public class SentryCheckIn : ISentryJsonSerializable /// public string MonitorSlug { get; } + /// - /// The status of the Checkin + /// The status of the Check-In /// public CheckInStatus Status { get; } /// - /// The duration of the check-in in seconds. Will only take effect if the status is ok or error. + /// The duration of the Check-In in seconds. Will only take effect if the status is ok or error. /// public TimeSpan? Duration { get; set; } @@ -65,6 +66,11 @@ public class SentryCheckIn : ISentryJsonSerializable /// internal SentryId? TraceId { get; set; } + /// + /// The Monitor Config + /// + internal SentryMonitorOptions? MonitorOptions { get; set; } + /// /// Initializes a new instance of . /// @@ -103,6 +109,8 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) writer.WriteEndObject(); } + MonitorOptions?.WriteTo(writer, logger); + writer.WriteEndObject(); } diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 6e6ef2a9fd..cf211258ba 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -220,7 +220,13 @@ public void CaptureSession(SessionUpdate sessionUpdate) => CaptureEnvelope(Envelope.FromSession(sessionUpdate)); /// - public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, TimeSpan? duration = null, Scope? scope = null) + public SentryId CaptureCheckIn( + string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, + TimeSpan? duration = null, + Scope? scope = null, + Action? configureMonitorOptions = null) { scope ??= new Scope(_options); @@ -234,9 +240,16 @@ public SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryI var checkIn = new SentryCheckIn(monitorSlug, status, sentryId) { Duration = duration, - TraceId = traceId + TraceId = traceId, }; + if (configureMonitorOptions is not null) + { + var monitorOptions = new SentryMonitorOptions(); + configureMonitorOptions.Invoke(monitorOptions); + checkIn.MonitorOptions = monitorOptions; + } + _enricher.Apply(checkIn); return CaptureEnvelope(Envelope.FromCheckIn(checkIn)) diff --git a/src/Sentry/SentryMonitorOptions.cs b/src/Sentry/SentryMonitorOptions.cs new file mode 100644 index 0000000000..4384b08e46 --- /dev/null +++ b/src/Sentry/SentryMonitorOptions.cs @@ -0,0 +1,224 @@ +using Sentry.Extensibility; +using Sentry.Internal.Extensions; + +namespace Sentry; + +internal enum SentryMonitorScheduleType +{ + None, + Crontab, + Interval +} + +/// +/// Sentry's intervals for monitors +/// +public enum SentryMonitorInterval +{ + /// + /// Year + /// + Year, + + /// + /// Month + /// + Month, + + /// + /// Week + /// + Week, + + /// + /// Day + /// + Day, + + /// + /// Hour + /// + Hour, + + /// + /// Minute + /// + Minute +} + +/// +/// Sentry's options for monitors +/// +public partial class SentryMonitorOptions : ISentryJsonSerializable +{ + private SentryMonitorScheduleType _type = SentryMonitorScheduleType.None; + private string? _crontab; + private int? _interval; + private SentryMonitorInterval? _unit; + + // Breakdown of the validation + // ^(\*|([0-5]?\d)) Minute 0 - 59 + // (\s+)(\*|([01]?\d|2[0-3])) Hour 0 - 23 + // (\s+)(\*|([1-9]|[12]\d|3[01])) Day 1 - 31 + // (\s+)(\*|([1-9]|1[0-2])) Month 1 - 12 + // (\s+)(\*|([0-7])) Weekday 0 - 7 + // $ End of string +#if NET7_0_OR_GREATER + [GeneratedRegex(@"^(\*|([0-5]?\d))(\s+)(\*|([01]?\d|2[0-3]))(\s+)(\*|([1-9]|[12]\d|3[01]))(\s+)(\*|([1-9]|1[0-2]))(\s+)(\*|([0-7]))$", RegexOptions.IgnoreCase)] + private static partial Regex CrontabValidation(); +#else + private static Regex? CrontabValidationInstance; + + private static Regex CrontabValidation() + { + return CrontabValidationInstance ??= new Regex( + @"^(\*|([0-5]?\d))(\s+)(\*|([01]?\d|2[0-3]))(\s+)(\*|([1-9]|[12]\d|3[01]))(\s+)(\*|([1-9]|1[0-2]))(\s+)(\*|([0-7]))$", + RegexOptions.Compiled | RegexOptions.CultureInvariant); + } +#endif + + /// + /// Set Interval + /// + /// + public void Interval(string crontab) + { + if (_type is not SentryMonitorScheduleType.None) + { + throw new ArgumentException("You tried to set the interval twice. The Check-Ins interval is supposed to be set only once."); + } + + if (!CrontabValidation().IsMatch(crontab)) + { + throw new ArgumentException("The provided crontab does not match the expected format of '* * * * *' " + + "translating to 'minute', 'hour', 'day of the month', 'month', and 'day of the week'."); + } + + _type = SentryMonitorScheduleType.Crontab; + _crontab = crontab; + } + + /// + /// Set Interval + /// + /// + /// + public void Interval(int interval, SentryMonitorInterval unit) + { + if (_type is not SentryMonitorScheduleType.None) + { + throw new ArgumentException("You tried to set the interval twice. The Check-Ins interval is supposed to be set only once."); + } + + _type = SentryMonitorScheduleType.Interval; + _interval = interval; + _unit = unit; + } + + /// + /// The allowed margin of minutes after the expected check-in time that the monitor will not be considered missed for. + /// + public TimeSpan? CheckInMargin { get; set; } + + /// + /// The allowed duration in minutes that the monitor may be in_progress for before being considered failed due to timeout. + /// + public TimeSpan? MaxRuntime { get; set; } + + private int? _failureIssueThreshold; + + /// + /// The number of consecutive failed check-ins it takes before an issue is created. + /// + public int? FailureIssueThreshold + { + get => _failureIssueThreshold; + set + { + if (value <= 0) + { + throw new ArgumentException("FailureIssueThreshold has to be set to a number greater than 0."); + } + _failureIssueThreshold = value; + } + } + + private int? _recoveryThreshold; + + /// + /// The number of consecutive OK check-ins it takes before an issue is resolved. + /// + public int? RecoveryThreshold + { + get => _recoveryThreshold; + set + { + if (value <= 0) + { + throw new ArgumentException("RecoveryThreshold has to be set to a number greater than 0."); + } + _recoveryThreshold = value; + } + } + + /// + /// A tz database string representing the timezone which the monitor's execution schedule is in (i.e. "America/Los_Angeles"). + /// + public string? Timezone { get; set; } + + /// + /// An actor identifier string. This looks like 'user:john@example.com team:a-sentry-team'. IDs can also be used but will result in a poor DX. + /// + public string? Owner { get; set; } + + internal SentryMonitorOptions() { } + + /// + public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) + { + Debug.Assert(_type != SentryMonitorScheduleType.None, "The Monitor Options do not contain a valid interval." + + "Please update your monitor options by setting the Interval."); + + writer.WriteStartObject("monitor_config"); + writer.WriteStartObject("schedule"); + + writer.WriteString("type", TypeToString(_type)); + switch (_type) + { + case SentryMonitorScheduleType.Crontab: + Debug.Assert(!string.IsNullOrEmpty(_crontab), "The provided 'crontab' cannot be an empty string."); + writer.WriteStringIfNotWhiteSpace("value", _crontab); + break; + case SentryMonitorScheduleType.Interval: + Debug.Assert(_interval != null, "The provided 'interval' cannot be null."); + writer.WriteNumberIfNotNull("value", _interval); + Debug.Assert(_unit != null, "The provided 'unit' cannot be null."); + writer.WriteStringIfNotWhiteSpace("unit", _unit.ToString()!.ToLower()); + break; + default: + logger?.LogError("Invalid MonitorScheduleType: '{0}'", _type.ToString()); + break; + } + + writer.WriteEndObject(); + + writer.WriteNumberIfNotNull("checkin_margin", CheckInMargin?.TotalMinutes); + writer.WriteNumberIfNotNull("max_runtime", MaxRuntime?.TotalMinutes); + writer.WriteNumberIfNotNull("failure_issue_threshold", FailureIssueThreshold); + writer.WriteNumberIfNotNull("recovery_threshold", RecoveryThreshold); + writer.WriteStringIfNotWhiteSpace("timezone", Timezone); + writer.WriteStringIfNotWhiteSpace("owner", Owner); + + writer.WriteEndObject(); + } + + private static string TypeToString(SentryMonitorScheduleType type) + { + return type switch + { + SentryMonitorScheduleType.Crontab => "crontab", + SentryMonitorScheduleType.Interval => "interval", + _ => throw new ArgumentException($"Unsupported Monitor Schedule Type: '{type}'.") + }; + } +} diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index db733792b5..a1e63ce24d 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -541,13 +541,25 @@ public static void CaptureSession(SessionUpdate sessionUpdate) /// /// The monitor slug of the check-in. /// The status of the check-in. - /// The optional . - /// The optional . - /// The optional duratin of the check-in. - /// The Id of the check-in. - [DebuggerStepThrough] - public static SentryId CaptureCheckIn(string monitorSlug, CheckInStatus status, SentryId? sentryId = null, TimeSpan? duration = null, Scope? scope = null) - => CurrentHub.CaptureCheckIn(monitorSlug, status, sentryId, duration, scope); + /// The associated with the check-in. + /// The duration of the check-in. + /// The scope of the check-in. + /// The optional monitor config used to create a Check-In programmatically. + /// The ID of the check-in. + [DebuggerStepThrough] + public static SentryId CaptureCheckIn(string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, + TimeSpan? duration = null, + Scope? scope = null, + Action? configureMonitorOptions = null) + => CurrentHub.CaptureCheckIn( + monitorSlug, + status, + sentryId, + duration, + scope, + configureMonitorOptions); /// /// Starts a transaction. diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 91d8079619..1f486f760a 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -230,7 +230,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null); + Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null); bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); @@ -454,7 +454,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } @@ -622,6 +622,27 @@ namespace Sentry protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } } + public enum SentryMonitorInterval + { + Year = 0, + Month = 1, + Week = 2, + Day = 3, + Hour = 4, + Minute = 5, + } + public class SentryMonitorOptions : Sentry.ISentryJsonSerializable + { + public System.TimeSpan? CheckInMargin { get; set; } + public int? FailureIssueThreshold { get; set; } + public System.TimeSpan? MaxRuntime { get; set; } + public string? Owner { get; set; } + public int? RecoveryThreshold { get; set; } + public string? Timezone { get; set; } + public void Interval(string crontab) { } + public void Interval(int interval, Sentry.SentryMonitorInterval unit) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } public class SentryOptions { public SentryOptions() { } @@ -768,7 +789,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } - public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1235,6 +1256,7 @@ namespace Sentry.Extensibility public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } @@ -1258,7 +1280,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1300,7 +1322,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? monitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 91d8079619..1f486f760a 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -230,7 +230,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null); + Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null); bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); @@ -454,7 +454,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } @@ -622,6 +622,27 @@ namespace Sentry protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } } + public enum SentryMonitorInterval + { + Year = 0, + Month = 1, + Week = 2, + Day = 3, + Hour = 4, + Minute = 5, + } + public class SentryMonitorOptions : Sentry.ISentryJsonSerializable + { + public System.TimeSpan? CheckInMargin { get; set; } + public int? FailureIssueThreshold { get; set; } + public System.TimeSpan? MaxRuntime { get; set; } + public string? Owner { get; set; } + public int? RecoveryThreshold { get; set; } + public string? Timezone { get; set; } + public void Interval(string crontab) { } + public void Interval(int interval, Sentry.SentryMonitorInterval unit) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } public class SentryOptions { public SentryOptions() { } @@ -768,7 +789,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } - public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1235,6 +1256,7 @@ namespace Sentry.Extensibility public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } @@ -1258,7 +1280,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1300,7 +1322,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? monitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 414eee11f5..eb32acd392 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -231,7 +231,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null); + Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null); bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); @@ -455,7 +455,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } @@ -623,6 +623,27 @@ namespace Sentry protected override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } } + public enum SentryMonitorInterval + { + Year = 0, + Month = 1, + Week = 2, + Day = 3, + Hour = 4, + Minute = 5, + } + public class SentryMonitorOptions : Sentry.ISentryJsonSerializable + { + public System.TimeSpan? CheckInMargin { get; set; } + public int? FailureIssueThreshold { get; set; } + public System.TimeSpan? MaxRuntime { get; set; } + public string? Owner { get; set; } + public int? RecoveryThreshold { get; set; } + public string? Timezone { get; set; } + public void Interval(string crontab) { } + public void Interval(int interval, Sentry.SentryMonitorInterval unit) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } public class SentryOptions { public SentryOptions() { } @@ -770,7 +791,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } - public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1237,6 +1258,7 @@ namespace Sentry.Extensibility public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } @@ -1260,7 +1282,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1302,7 +1324,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? monitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 2c3f4005c1..ea909bc3e5 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -229,7 +229,7 @@ namespace Sentry public interface ISentryClient { bool IsEnabled { get; } - Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null); + Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null); bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); @@ -453,7 +453,7 @@ namespace Sentry { public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } @@ -620,6 +620,27 @@ namespace Sentry protected abstract Sentry.ISpan? ProcessRequest(System.Net.Http.HttpRequestMessage request, string method, string url); protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { } } + public enum SentryMonitorInterval + { + Year = 0, + Month = 1, + Week = 2, + Day = 3, + Hour = 4, + Minute = 5, + } + public class SentryMonitorOptions : Sentry.ISentryJsonSerializable + { + public System.TimeSpan? CheckInMargin { get; set; } + public int? FailureIssueThreshold { get; set; } + public System.TimeSpan? MaxRuntime { get; set; } + public string? Owner { get; set; } + public int? RecoveryThreshold { get; set; } + public string? Timezone { get; set; } + public void Interval(string crontab) { } + public void Interval(int interval, Sentry.SentryMonitorInterval unit) { } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer, Sentry.Extensibility.IDiagnosticLogger? logger) { } + } public class SentryOptions { public SentryOptions() { } @@ -765,7 +786,7 @@ namespace Sentry public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } public static void BindException(System.Exception exception, Sentry.ISpan span) { } - public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public static Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public static bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1232,6 +1253,7 @@ namespace Sentry.Extensibility public static void LogDebug(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message) { } + public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2) { } public static void LogError(this Sentry.Extensibility.IDiagnosticLogger logger, System.Exception exception, string message, TArg arg, TArg2 arg2, TArg3 arg3) { } @@ -1255,7 +1277,7 @@ namespace Sentry.Extensibility public Sentry.IMetricAggregator Metrics { get; } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? configureMonitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null, Sentry.SentryHint? hint = null) { } @@ -1297,7 +1319,7 @@ namespace Sentry.Extensibility public void AddBreadcrumb(Sentry.Infrastructure.ISystemClock clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public void BindClient(Sentry.ISentryClient client) { } public void BindException(System.Exception exception, Sentry.ISpan span) { } - public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureCheckIn(string monitorSlug, Sentry.CheckInStatus status, Sentry.SentryId? sentryId = default, System.TimeSpan? duration = default, Sentry.Scope? scope = null, System.Action? monitorOptions = null) { } public bool CaptureEnvelope(Sentry.Protocol.Envelopes.Envelope envelope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index b3d5f5a494..f7348fdd58 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -1291,6 +1291,70 @@ public void CaptureCheckIn_ScopeHasSpan_CheckInHasTraceIdFromSpan() Assert.Equal(traceId, actualCheckIn.TraceId); } + [Fact] + public void CaptureCheckIn_CheckInOptionsProvided_CheckInHasOptions() + { + Envelope envelope = null; + var sut = _fixture.GetSut(); + sut.Worker.EnqueueEnvelope(Arg.Do(e => envelope = e)); + + var monitorOptions = new SentryMonitorOptions + { + CheckInMargin = TimeSpan.FromMinutes(1), + MaxRuntime = TimeSpan.FromMinutes(1), + FailureIssueThreshold = 1, + RecoveryThreshold = 1, + Timezone = "America/Los_Angeles", + Owner = "test-owner" + }; + + sut.CaptureCheckIn("my-monitor", CheckInStatus.InProgress, configureMonitorOptions: options => + { + options.CheckInMargin = monitorOptions.CheckInMargin; + options.MaxRuntime = monitorOptions.MaxRuntime; + options.FailureIssueThreshold = monitorOptions.FailureIssueThreshold; + options.RecoveryThreshold = monitorOptions.RecoveryThreshold; + options.Timezone = monitorOptions.Timezone; + options.Owner = monitorOptions.Owner; + }); + + var actualCheckIn = (SentryCheckIn)(envelope.Items[0].Payload as JsonSerializable)?.Source; + Assert.NotNull(actualCheckIn); + Assert.NotNull(actualCheckIn.MonitorOptions); + Assert.Equal(actualCheckIn.MonitorOptions.CheckInMargin, monitorOptions.CheckInMargin); + Assert.Equal(actualCheckIn.MonitorOptions.MaxRuntime, monitorOptions.MaxRuntime); + Assert.Equal(actualCheckIn.MonitorOptions.FailureIssueThreshold, monitorOptions.FailureIssueThreshold); + Assert.Equal(actualCheckIn.MonitorOptions.RecoveryThreshold, monitorOptions.RecoveryThreshold); + Assert.Equal(actualCheckIn.MonitorOptions.Timezone, monitorOptions.Timezone); + Assert.Equal(actualCheckIn.MonitorOptions.Owner, monitorOptions.Owner); + } + + [Fact] + public void CaptureCheckIn_IntervalSetMoreThanOnce_Throws() + { + Assert.Throws(() => _fixture.GetSut().CaptureCheckIn("my-monitor", CheckInStatus.InProgress, + configureMonitorOptions: options => + { + options.Interval(1, SentryMonitorInterval.Month); + options.Interval(2, SentryMonitorInterval.Day); + })); + } + + [Theory] + [InlineData("")] + [InlineData("not a crontab")] + [InlineData("* * a * *")] + [InlineData("60 * * * *")] + [InlineData("* 24 * * *")] + [InlineData("* * 32 * *")] + [InlineData("* * * 13 *")] + [InlineData("* * * * 8")] + public void CaptureCheckIn_InvalidCrontabSet_Throws(string crontab) + { + Assert.Throws(() => _fixture.GetSut().CaptureCheckIn("my-monitor", CheckInStatus.InProgress, + configureMonitorOptions: options => options.Interval(crontab))); + } + [Fact] public void Dispose_Worker_FlushCalled() {