Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 10m54s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 7m34s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m59s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Failing after 1m27s
The three recurrence arrays are read CONJUNCTIVELY by AlternateScheduleSelector.GetScheduleForDate, so an empty set matches no date. `?? []` on an omitted array therefore returned HTTP 200 while storing an alternate-schedule or template item that could never apply, silently -- while the read side (#823) already read a NULL column as the All*() sets. Absent and explicitly-empty are two different requests and get two answers: ABSENT (missing, or explicit null) normalizes to AlternateScheduleSelector.All*(), the same symbols the read side substitutes; EXPLICIT [] is rejected with a 422 naming the consequence, via RecurrenceSetBounds called from both replace handlers. The rejection lives in the handlers, not the controller, because api.ffmpeg-profile-numeric-bounds' "accept an UNCHANGED bad value" rule binds hardest here: both PUT paths are whole-list replaces, so rejecting a pre-existing empty set would make every OTHER item in the list uneditable. That comparison needs the stored row. The validated set is derived from `incoming`, so the highest-Index catch-all -- whose recurrence the handler discards -- is excluded by construction. Verified: full ErsatzTV.Tests suite green; three mutation proofs with disjoint reddened sets; live-E2E against a real instance confirmed an OMITTED property round-trips as unrestricted (the Newtonsoft missing-property chain unit tests cannot reach), an explicit [] returns the 422, and [] on the catch-all is accepted. Cross-family cold review BLOCKED the first implementation with 3 findings, all real and all fixed; re-review returned MERGEABLE. Follow-up #894 filed: the SPA can still build the empty state the server rejects. fixes #880 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 lines
1.2 KiB
C#
42 lines
1.2 KiB
C#
#nullable enable
|
|
using ErsatzTV.Application.Scheduling;
|
|
using ErsatzTV.Core.Scheduling;
|
|
|
|
namespace ErsatzTV.Controllers.Api.Requests;
|
|
|
|
public record PlayoutTemplateItemRequest(
|
|
int Id,
|
|
int TemplateId,
|
|
int? DecoTemplateId,
|
|
List<DayOfWeek>? DaysOfWeek,
|
|
List<int>? DaysOfMonth,
|
|
List<int>? MonthsOfYear,
|
|
bool LimitToDateRange,
|
|
int StartMonth,
|
|
int StartDay,
|
|
int? StartYear,
|
|
int EndMonth,
|
|
int EndDay,
|
|
int? EndYear)
|
|
{
|
|
// Same contract as PlayoutAlternateScheduleItemRequest: ABSENT means UNRESTRICTED (the All*() sets
|
|
// the read side substitutes for a NULL column), an explicit `[]` is rejected with a 422 in
|
|
// PlayoutController. See that record for the full rationale (#880).
|
|
public ReplacePlayoutTemplate ToReplaceItem(int index) =>
|
|
new(
|
|
Id,
|
|
index,
|
|
TemplateId,
|
|
DecoTemplateId,
|
|
DaysOfWeek ?? AlternateScheduleSelector.AllDaysOfWeek(),
|
|
DaysOfMonth ?? AlternateScheduleSelector.AllDaysOfMonth(),
|
|
MonthsOfYear ?? AlternateScheduleSelector.AllMonthsOfYear(),
|
|
LimitToDateRange,
|
|
StartMonth,
|
|
StartDay,
|
|
StartYear,
|
|
EndMonth,
|
|
EndDay,
|
|
EndYear);
|
|
}
|