Repository: ietf-tools/datatracker

Issue:
Unexpected meeting chosen for meeting/agenda.ics and meeting/agenda.json endpoints
### Describe the issue

The `meeting/agenda.ics` and `meeting/agenda.json` endpoints returns an iCalendar file for the next meeting of any type - at the moment, this is for an interim meeting. This is surprising because `meeting/agenda.html` and `meeting/agenda.txt` both return the next full IETF meeting. It would be preferable if all the `meeting/agenda.*` URLs agreed on what meeting they represented.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:
This should be the next full ietf meeting.

Resolving patch:
diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py
--- a/ietf/meeting/helpers.py
+++ b/ietf/meeting/helpers.py
@@ -36,12 +36,14 @@
 from ietf.utils.text import xslugify
 
 
-def get_meeting(num=None,type_in=['ietf',],days=28):
+def get_meeting(num=None, type_in=('ietf',), days=28):
     meetings = Meeting.objects
-    if type_in:
+    if type_in is not None:
         meetings = meetings.filter(type__in=type_in)
-    if num == None:
-        meetings = meetings.filter(date__gte=timezone.now()-datetime.timedelta(days=days)).order_by('date')
+    if num is None:
+        meetings = meetings.filter(
+            date__gte=timezone.now() - datetime.timedelta(days=days)
+        ).order_by('date')
     else:
         meetings = meetings.filter(number=num)
     if meetings.exists():
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -2041,10 +2041,13 @@ def should_include_assignment(filter_params, assignment):
     hidden = len(set(filter_params['hide']).intersection(assignment.filter_keywords)) > 0
     return shown and not hidden
 
-def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
+def agenda_ical(request, num=None, acronym=None, session_id=None):
     """Agenda ical view
 
-    By default, all agenda items will be shown. A filter can be specified in 
+    If num is None, looks for the next IETF meeting. Otherwise, uses the requested meeting
+    regardless of its type.
+
+    By default, all agenda items will be shown. A filter can be specified in
     the querystring. It has the format
     
       ?show=...&hide=...&showtypes=...&hidetypes=...
@@ -2059,8 +2062,13 @@ def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
 
     Hiding (by wg or type) takes priority over showing.
     """
-    meeting = get_meeting(num, type_in=None)
-    schedule = get_schedule(meeting, name)
+    if num is None:
+        meeting = get_ietf_meeting()
+        if meeting is None:
+            raise Http404
+    else:
+        meeting = get_meeting(num, type_in=None)  # get requested meeting, whatever its type
+    schedule = get_schedule(meeting)
     updated = meeting.updated()
 
     if schedule is None and acronym is None and session_id is None:
@@ -2099,7 +2107,12 @@ def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
 
 @cache_page(15 * 60)
 def agenda_json(request, num=None):
-    meeting = get_meeting(num, type_in=['ietf','interim'])
+    if num is None:
+        meeting = get_ietf_meeting()
+        if meeting is None:
+            raise Http404
+    else:
+        meeting = get_meeting(num, type_in=None)  # get requested meeting, whatever its type
 
     sessions = []
     locations = set()


Test patch:
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -345,6 +345,39 @@ def test_meeting_agenda(self):
         r = self.client.get(urlreverse('floor-plan', kwargs=dict(num=meeting.number)))
         self.assertEqual(r.status_code, 200)
 
+    def test_agenda_ical_next_meeting_type(self):
+        # start with no upcoming IETF meetings, just an interim
+        MeetingFactory(
+            type_id="interim", date=date_today() + datetime.timedelta(days=15)
+        )
+        r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs={}))
+        self.assertEqual(
+            r.status_code, 404, "Should not return an interim meeting as next meeting"
+        )
+        # create an IETF meeting after the interim - it should be found as "next"
+        ietf_meeting = MeetingFactory(
+            type_id="ietf", date=date_today() + datetime.timedelta(days=30)
+        )
+        SessionFactory(meeting=ietf_meeting, name="Session at IETF meeting")
+        r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs={}))
+        self.assertContains(r, "Session at IETF meeting", status_code=200)
+
+    def test_agenda_json_next_meeting_type(self):
+        # start with no upcoming IETF meetings, just an interim
+        MeetingFactory(
+            type_id="interim", date=date_today() + datetime.timedelta(days=15)
+        )
+        r = self.client.get(urlreverse("ietf.meeting.views.agenda_json", kwargs={}))
+        self.assertEqual(
+            r.status_code, 404, "Should not return an interim meeting as next meeting"
+        )
+        # create an IETF meeting after the interim - it should be found as "next"
+        ietf_meeting = MeetingFactory(
+            type_id="ietf", date=date_today() + datetime.timedelta(days=30)
+        )
+        SessionFactory(meeting=ietf_meeting, name="Session at IETF meeting")
+        r = self.client.get(urlreverse("ietf.meeting.views.agenda_json", kwargs={}))
+        self.assertContains(r, "Session at IETF meeting", status_code=200)
 
     @override_settings(PROCEEDINGS_V1_BASE_URL='https://example.com/{meeting.number}')
     def test_agenda_redirects_for_old_meetings(self):