From 68cab9184ab3c5f9d94ddb59da23c979325469da Mon Sep 17 00:00:00 2001 From: "Carlos J. Aliaga" Date: Fri, 31 Oct 2025 14:30:45 -0700 Subject: [PATCH] Updated ScriptJobHostOptions to implement IOptionsFormatter and support JSON serialization with a custom converter. Added a Format method for serializing options to JSON. Introduced unit tests to validate serialization logic. --- .../Config/ScriptJobHostOptions.cs | 38 +++++++- .../ScriptJobHostOptionsTests.cs | 87 +++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 test/WebJobs.Script.Tests/Configuration/ScriptJobHostOptionsTests.cs diff --git a/src/WebJobs.Script/Config/ScriptJobHostOptions.cs b/src/WebJobs.Script/Config/ScriptJobHostOptions.cs index d0dac72fb0..544f0d899f 100644 --- a/src/WebJobs.Script/Config/ScriptJobHostOptions.cs +++ b/src/WebJobs.Script/Config/ScriptJobHostOptions.cs @@ -1,17 +1,26 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Azure.WebJobs.Hosting; using Microsoft.Azure.WebJobs.Script.Description; using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry; namespace Microsoft.Azure.WebJobs.Script { - public class ScriptJobHostOptions + public class ScriptJobHostOptions : IOptionsFormatter { + private static readonly JsonSerializerOptions _serializerOptions = new() + { + Converters = { new ScriptJobHostOptionsConverter() }, + WriteIndented = true, + }; + private string _rootScriptPath; private ImmutableArray _directorySnapshot; @@ -145,5 +154,30 @@ public string RootScriptPath /// Gets or sets a value indicating the timeout duration for the function metadata provider. /// public TimeSpan MetadataProviderTimeout { get; set; } = TimeSpan.Zero; + + public string Format() + { + return JsonSerializer.Serialize(this, _serializerOptions); + } + + private class ScriptJobHostOptionsConverter : JsonConverter + { + public override ScriptJobHostOptions Read( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write( + Utf8JsonWriter writer, ScriptJobHostOptions value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteBoolean(nameof(value.FileWatchingEnabled), value.FileWatchingEnabled); + writer.WriteString(nameof(value.FileLoggingMode), value.FileLoggingMode.ToString()); + writer.WriteString(nameof(value.FunctionTimeout), value.FunctionTimeout?.ToString()); + writer.WriteString(nameof(value.TelemetryMode), value.TelemetryMode.ToString()); + writer.WriteEndObject(); + } + } } } diff --git a/test/WebJobs.Script.Tests/Configuration/ScriptJobHostOptionsTests.cs b/test/WebJobs.Script.Tests/Configuration/ScriptJobHostOptionsTests.cs new file mode 100644 index 0000000000..8628e6cb4c --- /dev/null +++ b/test/WebJobs.Script.Tests/Configuration/ScriptJobHostOptionsTests.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using AwesomeAssertions; +using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry; +using Xunit; + +namespace Microsoft.Azure.WebJobs.Script.Tests.Configuration +{ + public class ScriptJobHostOptionsTests + { + [Theory] + [InlineData(FileLoggingMode.Always, 10, true, "Always", "00:10:00", "ApplicationInsights")] + [InlineData(FileLoggingMode.Never, 5, false, "Never", "00:05:00", "OpenTelemetry")] + [InlineData(FileLoggingMode.DebugOnly, 0.5, true, "DebugOnly", "00:00:30", "ApplicationInsights")] + public void Format_SerializesOptionsToJson( + FileLoggingMode loggingMode, + double timeoutMinutes, + bool fileWatchingEnabled, + string expectedLoggingMode, + string expectedTimeout, + string expectedTelemetryMode) + { + var options = new ScriptJobHostOptions + { + FileLoggingMode = loggingMode, + FunctionTimeout = TimeSpan.FromMinutes(timeoutMinutes), + FileWatchingEnabled = fileWatchingEnabled, + TelemetryMode = Enum.Parse(expectedTelemetryMode) + }; + + string json = options.Format(); + var root = JsonDocument.Parse(json).RootElement; + + root.TryGetProperty("FileWatchingEnabled", out var fileWatchingProperty).Should().BeTrue(); + fileWatchingProperty.GetBoolean().Should().Be(fileWatchingEnabled); + + root.TryGetProperty("FileLoggingMode", out var fileLoggingProperty).Should().BeTrue(); + fileLoggingProperty.GetString().Should().Be(expectedLoggingMode); + + root.TryGetProperty("FunctionTimeout", out var timeoutProperty).Should().BeTrue(); + timeoutProperty.GetString().Should().Be(expectedTimeout); + + root.TryGetProperty("TelemetryMode", out var telemetryModeProperty).Should().BeTrue(); + telemetryModeProperty.GetString().Should().Be(expectedTelemetryMode); + } + + [Fact] + public void Format_WithNullFunctionTimeout_SerializesAsNull() + { + var options = new ScriptJobHostOptions + { + FileLoggingMode = FileLoggingMode.Never, + FileWatchingEnabled = true, + FunctionTimeout = null + }; + + string json = options.Format(); + var root = JsonDocument.Parse(json).RootElement; + + root.TryGetProperty("FunctionTimeout", out var timeoutProperty).Should().BeTrue(); + timeoutProperty.ValueKind.Should().Be(JsonValueKind.Null); + } + + [Fact] + public void Format_ReturnsValidIndentedJson() + { + var options = new ScriptJobHostOptions + { + FileLoggingMode = FileLoggingMode.Always, + FileWatchingEnabled = true, + FunctionTimeout = TimeSpan.FromMinutes(5) + }; + + string json = options.Format(); + + // Should be valid JSON + var exception = Record.Exception(() => JsonDocument.Parse(json)); + exception.Should().BeNull(); + + // Should be indented (contains newlines) + json.Should().Contain(Environment.NewLine); + } + } +}