From b01c9bf79f98e31f478262f0162948afb9033c72 Mon Sep 17 00:00:00 2001
From: Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org>
Date: Tue, 23 Jul 2024 14:41:03 +0200
Subject: [PATCH] fix(program-generation): fix undefined behaviour around start
 and end query parameters

The radio program is a continuous stream of program slots. Clients can
expect that the program generated for their queries fit the start and
end parameters given by the client. However, they *cannot* expect the
actual radio program (in the form of Timeslot objects) to fit these
artifical boundaries.

As the program endpoint should always output the actual program
broadcasted by the radio we must include timeslots that
  * have started before the specified start query but end after it
  * or end after the specified end query but start before it.

The changes in this commit ensure that the program matches the given
range exactly and that planned timeslots in that range are always
included.
---
 program/services.py | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/program/services.py b/program/services.py
index 23497ed6..86b9f0cd 100644
--- a/program/services.py
+++ b/program/services.py
@@ -720,13 +720,23 @@ def generate_program_entries(
         )
 
     def create_timeslot_entry(timeslot: TimeSlot):
-        return create_entry(timeslot.start, timeslot.end, timeslot.schedule.show, timeslot)
+        return create_entry(
+            # Ensure the program entry never starts before the requested start
+            # and never ends after the requested end.
+            max(timeslot.start, start),
+            min(timeslot.end, end),
+            timeslot.schedule.show,
+            timeslot,
+        )
 
     if start is None:
         start = timezone.now()
     if end is None:
         end = start + timedelta(days=1)
-    queryset = queryset.filter(start__gte=start, start__lt=end)
+    # Find all timeslots that
+    #   * have started before the specified start value but end after it
+    #   * or end after the specified end value but start before it
+    queryset = queryset.filter(end__gt=start, start__lt=end).order_by("start")
 
     if not include_virtual:
         yield from (create_timeslot_entry(timeslot) for timeslot in queryset)
@@ -744,5 +754,5 @@ def generate_program_entries(
             yield create_entry(entry_start, timeslot.start, fallback_show)
         yield create_timeslot_entry(timeslot)
         entry_start = timeslot.end
-    if entry_start != end:
+    if entry_start < end:
         yield create_entry(entry_start, end, fallback_show)
-- 
GitLab