From 8250091fc235d477c32d04a602881914bef6e520 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:51:05 -0600 Subject: [PATCH] add basic sequential schedule validator tests --- .../ErsatzTV.Infrastructure.Tests.csproj | 6 + .../SequentialScheduleValidatorTests.cs | 200 ++++++++++++++++++ .../Scheduling/SequentialScheduleValidator.cs | 8 +- 3 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 ErsatzTV.Infrastructure.Tests/Scheduling/SequentialScheduleValidatorTests.cs diff --git a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj index 142eda6d5..80ed692fa 100644 --- a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj +++ b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj @@ -29,4 +29,10 @@ + + + Resources/sequential-schedule.schema.json + + + diff --git a/ErsatzTV.Infrastructure.Tests/Scheduling/SequentialScheduleValidatorTests.cs b/ErsatzTV.Infrastructure.Tests/Scheduling/SequentialScheduleValidatorTests.cs new file mode 100644 index 000000000..b0092e41b --- /dev/null +++ b/ErsatzTV.Infrastructure.Tests/Scheduling/SequentialScheduleValidatorTests.cs @@ -0,0 +1,200 @@ +using System.Reflection; +using ErsatzTV.Core; +using ErsatzTV.Infrastructure.Scheduling; +using Microsoft.Extensions.Logging; +using NSubstitute; +using NUnit.Framework; +using Shouldly; +using Testably.Abstractions.Testing; + +namespace ErsatzTV.Infrastructure.Tests.Scheduling; + +[TestFixture] +public class SequentialScheduleValidatorTests +{ + private readonly string _schema; + + public SequentialScheduleValidatorTests() + { + var assembly = Assembly.GetAssembly(typeof(SequentialScheduleValidatorTests)); + assembly.ShouldNotBeNull(); + + using var stream = assembly.GetManifestResourceStream( + "ErsatzTV.Infrastructure.Tests.Resources.sequential-schedule.schema.json"); + stream.ShouldNotBeNull(); + + using var reader = new StreamReader(stream); + _schema = reader.ReadToEnd(); + } + + [CancelAfter(2_000)] + [Test] + public async Task ValidateSchedule_Should_Succeed_Valid_Schedule(CancellationToken cancellationToken) + { + const string YAML = +""" +content: + - show: + key: "SOME_SHOW" + guids: + - source: "imdb" + value: "tt123456" + order: chronological + - search: + key: "FILLER" + query: "type:other_video" + order: "shuffle" + +reset: + - wait_until: '8:00am' + tomorrow: false + rewind_on_reset: true + +playout: + - duration: "30 minutes" + content: "SOME_SHOW" + discard_attempts: 2 + offline_tail: false + + - epg_group: true + advance: false + + - pad_to_next: 30 + content: "FILLER" + filler_kind: postroll + trim: true + + - epg_group: false + + - repeat: true +"""; + + string schemaFileName = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "sequential-schedule.schema.json"); + + var fileSystem = new MockFileSystem(); + fileSystem.Initialize() + .WithFile(schemaFileName).Which(f => f.HasStringContent(_schema)); + + var validator = new SequentialScheduleValidator( + fileSystem, + Substitute.For>()); + + bool result = await validator.ValidateSchedule(YAML, false); + result.ShouldBeTrue(); + } + + [CancelAfter(2_000)] + [Test] + public async Task ValidateSchedule_Should_Fail_Invalid_Schedule(CancellationToken cancellationToken) + { + const string YAML = + """ + content: + - show: + key: "SOME_SHOW" + guids22: + - source: "imdb" + value: "tt123456" + order: chronological + - search: + key: "FILLER" + query: "type:other_video" + order: "shuffle" + + reset: + - wait_until: '8:00am' + tomorrow: false + rewind_on_reset: true + + playout: + - duration: "30 minutes" + content: "SOME_SHOW" + discard_attempts: 2 + offline_tail: false + + - epg_group: true + advance: false + + - pad_to_next: 30 + content: "FILLER" + filler_kind: postroll + trim: true + + - epg_group: false + + - repeat: true + """; + + string schemaFileName = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "sequential-schedule.schema.json"); + + var fileSystem = new MockFileSystem(); + fileSystem.Initialize() + .WithFile(schemaFileName).Which(f => f.HasStringContent(_schema)); + + var validator = new SequentialScheduleValidator( + fileSystem, + Substitute.For>()); + + bool result = await validator.ValidateSchedule(YAML, false); + result.ShouldBeFalse(); + } + + [CancelAfter(2_000)] + [Test] + public async Task GetValidationMessages_With_Invalid_Schedule(CancellationToken cancellationToken) + { + const string YAML = + """ + content: + - show: + key: "SOME_SHOW" + guids22: + - source: "imdb" + value: "tt123456" + order: chronological + - search: + key: "FILLER" + query: "type:other_video" + order: "shuffle" + + reset: + - wait_until: '8:00am' + tomorrow: false + rewind_on_reset: true + + playout: + - duration: "30 minutes" + content: "SOME_SHOW" + discard_attempts: 2 + offline_tail: false + + - epg_group: true + advance: false + + - pad_to_next: 30 + content: "FILLER" + filler_kind: postroll + trim: true + + - epg_group: false + + - repeat: true + """; + + string schemaFileName = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "sequential-schedule.schema.json"); + + var fileSystem = new MockFileSystem(); + fileSystem.Initialize() + .WithFile(schemaFileName).Which(f => f.HasStringContent(_schema)); + + var validator = new SequentialScheduleValidator( + fileSystem, + Substitute.For>()); + + IList result = await validator.GetValidationMessages(YAML, false); + result.ShouldNotBeNull(); + result.Count.ShouldBe(1); + result[0].ShouldContain("line 3"); + result[0].ShouldContain("position 5"); + } +} diff --git a/ErsatzTV.Infrastructure/Scheduling/SequentialScheduleValidator.cs b/ErsatzTV.Infrastructure/Scheduling/SequentialScheduleValidator.cs index 1b5e6421c..3062aec4e 100644 --- a/ErsatzTV.Infrastructure/Scheduling/SequentialScheduleValidator.cs +++ b/ErsatzTV.Infrastructure/Scheduling/SequentialScheduleValidator.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.IO.Abstractions; using System.Text.Json; using ErsatzTV.Core; using ErsatzTV.Core.Interfaces.Scheduling; @@ -11,7 +12,8 @@ using JsonSerializer = System.Text.Json.JsonSerializer; namespace ErsatzTV.Infrastructure.Scheduling; -public class SequentialScheduleValidator(ILogger logger) : ISequentialScheduleValidator +public class SequentialScheduleValidator(IFileSystem fileSystem, ILogger logger) + : ISequentialScheduleValidator { public async Task ValidateSchedule(string yaml, bool isImport) { @@ -20,7 +22,7 @@ public class SequentialScheduleValidator(ILogger lo string schemaFileName = Path.Combine( FileSystemLayout.ResourcesCacheFolder, isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json"); - using StreamReader sr = File.OpenText(schemaFileName); + using StreamReader sr = fileSystem.File.OpenText(schemaFileName); await using var reader = new JsonTextReader(sr); var schema = JSchema.Load(reader); @@ -63,7 +65,7 @@ public class SequentialScheduleValidator(ILogger lo string schemaFileName = Path.Combine( FileSystemLayout.ResourcesCacheFolder, isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json"); - using StreamReader sr = File.OpenText(schemaFileName); + using StreamReader sr = fileSystem.File.OpenText(schemaFileName); await using var reader = new JsonTextReader(sr); var schema = JSchema.Load(reader);