From 8732dcb70624ea2d4d89122d64ba8a0009be27b2 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Fri, 22 Sep 2023 12:40:15 -0500
Subject: [PATCH] fix: bring tests up current. add subseries names to rfc view.
 fix html.

---
 ietf/doc/factories.py                      | 28 ++++++++
 ietf/doc/tests_subseries.py                | 49 ++++++++++++++
 ietf/name/fixtures/names.json              | 75 ++++++++++++++++++++--
 ietf/sync/tests.py                         | 24 +++++--
 ietf/templates/doc/document_subseries.html |  2 +-
 ietf/templates/doc/document_top.html       |  4 +-
 ietf/templates/doc/index_subseries.html    |  2 +-
 7 files changed, 172 insertions(+), 12 deletions(-)
 create mode 100644 ietf/doc/tests_subseries.py

diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py
index 32e87f195..50fba50c4 100644
--- a/ietf/doc/factories.py
+++ b/ietf/doc/factories.py
@@ -517,3 +517,31 @@ class StatementFactory(BaseDocumentFactory):
                 obj.set_state(State.objects.get(type_id=state_type_id, slug=state_slug))
         else:
             obj.set_state(State.objects.get(type_id="statement", slug="active"))
+
+class SubseriesFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = Document
+        skip_postgeneration_save = True
+
+    @factory.lazy_attribute_sequence
+    def name(self, n):
+        return f"{self.type_id}{n}"
+    
+    @factory.post_generation
+    def contains(obj, create, extracted, **kwargs):
+        if not create:
+            return
+        if extracted:
+            for doc in extracted:
+                obj.relateddocument_set.create(relationship_id="contains",target=doc)
+        else:
+            obj.relateddocument_set.create(relationship_id="contains", target=RfcFactory())
+
+class BcpFactory(SubseriesFactory):
+    type_id="bcp"
+
+class StdFactory(SubseriesFactory):
+    type_id="std"
+
+class FyiFactory(SubseriesFactory):
+    type_id="fyi"
diff --git a/ietf/doc/tests_subseries.py b/ietf/doc/tests_subseries.py
new file mode 100644
index 000000000..e2b8a1cf6
--- /dev/null
+++ b/ietf/doc/tests_subseries.py
@@ -0,0 +1,49 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+# -*- coding: utf-8 -*-
+
+import debug # pyflakes:ignore
+
+from pyquery import PyQuery
+
+from django.urls import reverse as urlreverse
+
+from ietf.doc.factories import SubseriesFactory, RfcFactory
+from ietf.doc.models import Document
+from ietf.utils.test_utils import TestCase
+
+class SubseriesTests(TestCase):
+
+    def test_index_and_view(self):
+        types = ["bcp", "std", "fyi"]
+        for type_id in types:
+            doc = SubseriesFactory(type_id=type_id)
+            self.assertEqual(len(doc.contains()), 1)
+            rfc = doc.contains()[0]
+            # Index
+            url = urlreverse("ietf.doc.views_search.index_subseries", kwargs=dict(type_id=type_id))
+            r = self.client.get(url)
+            self.assertEqual(r.status_code, 200)
+            q = PyQuery(r.content)
+            self.assertIsNotNone(q(f"#{doc.name}"))
+            self.assertIn(rfc.name,q(f"#{doc.name}").text())
+            # Subseries document view
+            url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name))
+            r = self.client.get(url)
+            self.assertEqual(r.status_code, 200)
+            q = PyQuery(r.content)
+            self.assertIn(f"{doc.type_id.upper()} {doc.name[3:]} consists of:",q("h2").text())
+            self.assertIn(f"RFC {rfc.name[3:]}", q("div.row p a").text())
+            # RFC view
+            url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc.name))
+            r = self.client.get(url)
+            q = PyQuery(r.content)
+            self.assertIn(f"RFC {rfc.name[3:]} also known as {type_id.upper()} {doc.name[3:]}", q("h1").text())
+        bcp = Document.objects.filter(type_id="bcp").last()
+        bcp.relateddocument_set.create(relationship_id="contains", target=RfcFactory())
+        for rfc in bcp.contains():
+            url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc.name))
+            r = self.client.get(url)
+            q = PyQuery(r.content)
+            self.assertIn(f"RFC {rfc.name[3:]} part of BCP {bcp.name[3:]}", q("h1").text())     
+
+
diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json
index 6ec5f8528..120aefe58 100644
--- a/ietf/name/fixtures/names.json
+++ b/ietf/name/fixtures/names.json
@@ -2585,6 +2585,13 @@
     "model": "doc.statetype",
     "pk": "agenda"
   },
+  {
+    "fields": {
+      "label": "bcp state"
+    },
+    "model": "doc.statetype",
+    "pk": "bcp"
+  },
   {
     "fields": {
       "label": "State"
@@ -2704,6 +2711,13 @@
     "model": "doc.statetype",
     "pk": "draft-stream-ise"
   },
+  {
+    "fields": {
+      "label": "fyi state"
+    },
+    "model": "doc.statetype",
+    "pk": "fyi"
+  },
   {
     "fields": {
       "label": "State"
@@ -2795,6 +2809,13 @@
     "model": "doc.statetype",
     "pk": "statement"
   },
+  {
+    "fields": {
+      "label": "std state"
+    },
+    "model": "doc.statetype",
+    "pk": "std"
+  },
   {
     "fields": {
       "about_page": "ietf.group.views.group_about",
@@ -5869,7 +5890,7 @@
   {
     "fields": {
       "desc": "The document's authors",
-      "template": "{% if doc.type_id == \"draft\" %}<{{doc.name}}@ietf.org>{% endif %}"
+      "template": "{% if doc.type_id == \"draft\" or doc.type_id == \"rfc\" %}<{{doc.name}}@ietf.org>{% endif %}"
     },
     "model": "mailtrigger.recipient",
     "pk": "doc_authors"
@@ -10040,6 +10061,17 @@
     "model": "name.docrelationshipname",
     "pk": "conflrev"
   },
+  {
+    "fields": {
+      "desc": "This document contains other documents (e.g., STDs contain RFCs)",
+      "name": "Contains",
+      "order": 0,
+      "revname": "Is part of",
+      "used": true
+    },
+    "model": "name.docrelationshipname",
+    "pk": "contains"
+  },
   {
     "fields": {
       "desc": "Approval for downref",
@@ -10556,6 +10588,17 @@
     "model": "name.doctypename",
     "pk": "agenda"
   },
+  {
+    "fields": {
+      "desc": "",
+      "name": "Best Current Practice",
+      "order": 0,
+      "prefix": "bcp",
+      "used": true
+    },
+    "model": "name.doctypename",
+    "pk": "bcp"
+  },
   {
     "fields": {
       "desc": "",
@@ -10622,6 +10665,17 @@
     "model": "name.doctypename",
     "pk": "draft"
   },
+  {
+    "fields": {
+      "desc": "",
+      "name": "For Your Information",
+      "order": 0,
+      "prefix": "fyi",
+      "used": true
+    },
+    "model": "name.doctypename",
+    "pk": "fyi"
+  },
   {
     "fields": {
       "desc": "",
@@ -10754,6 +10808,17 @@
     "model": "name.doctypename",
     "pk": "statement"
   },
+  {
+    "fields": {
+      "desc": "",
+      "name": "Standard",
+      "order": 0,
+      "prefix": "std",
+      "used": true
+    },
+    "model": "name.doctypename",
+    "pk": "std"
+  },
   {
     "fields": {
       "desc": "",
@@ -16604,7 +16669,7 @@
     "fields": {
       "command": "xym",
       "switch": "--version",
-      "time": "2023-08-22T07:09:39.542Z",
+      "time": "2023-09-21T07:09:40.201Z",
       "used": true,
       "version": "xym 0.7.0"
     },
@@ -16615,7 +16680,7 @@
     "fields": {
       "command": "pyang",
       "switch": "--version",
-      "time": "2023-08-22T07:09:39.881Z",
+      "time": "2023-09-21T07:09:40.525Z",
       "used": true,
       "version": "pyang 2.5.3"
     },
@@ -16626,7 +16691,7 @@
     "fields": {
       "command": "yanglint",
       "switch": "--version",
-      "time": "2023-08-22T07:09:39.899Z",
+      "time": "2023-09-21T07:09:40.546Z",
       "used": true,
       "version": "yanglint SO 1.9.2"
     },
@@ -16637,7 +16702,7 @@
     "fields": {
       "command": "xml2rfc",
       "switch": "--version",
-      "time": "2023-08-22T07:09:40.791Z",
+      "time": "2023-09-21T07:09:41.465Z",
       "used": true,
       "version": "xml2rfc 3.18.0"
     },
diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index cedca2e49..936f001d1 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -371,10 +371,26 @@ class RFCSyncTests(TestCase):
         rfc_doc = Document.objects.filter(rfc_number=1234, type_id="rfc").first()
         self.assertIsNotNone(rfc_doc, "RFC document should have been created")
         rfc_events = rfc_doc.docevent_set.all()
-        self.assertEqual(len(rfc_events), 2)
-        self.assertEqual(rfc_events[0].type, "sync_from_rfc_editor")
-        self.assertEqual(rfc_events[1].type, "published_rfc")
-        self.assertEqual(rfc_events[1].time.astimezone(RPC_TZINFO).date(), today)
+        self.assertEqual(len(rfc_events), 8)
+        expected_events = [
+            ["sync_from_rfc_editor", f"Received changes through RFC Editor sync (created document RFC 1234, created became rfc relationship between {rfc_doc.came_from_draft().name} and RFC 1234, set title to 'A Testing RFC', set abstract to 'This is some interesting text.', set pages to 42, set standardization level to Proposed Standard, added RFC published event at 2023-09-22, created updates relation between RFC 1234 and RFC 123, added Errata tag)"],
+            ["sync_from_rfc_editor", "Added rfc1234 to std1"],
+            ["std_history_marker", "No history of STD1 is currently available in the datatracker before this point"],
+            ["sync_from_rfc_editor", "Added rfc1234 to fyi1"],
+            ["fyi_history_marker", "No history of FYI1 is currently available in the datatracker before this point"],
+            ["sync_from_rfc_editor", "Added rfc1234 to bcp1"],
+            ["bcp_history_marker", "No history of BCP1 is currently available in the datatracker before this point"],
+            ["published_rfc", "RFC published"]
+        ]
+        for index, [event_type, desc] in enumerate(expected_events):
+            self.assertEqual(rfc_events[index].type, event_type)
+            self.assertEqual(rfc_events[index].desc, desc)
+        self.assertEqual(rfc_events[7].time.astimezone(RPC_TZINFO).date(), today)
+        for subseries_slug in ["bcp", "fyi", "std"]:
+            sub = Document.objects.filter(type_id=subseries_slug,name=f"{subseries_slug}1").first()
+            self.assertIsNotNone(sub, f"{subseries_slug}1 not created")
+            self.assertTrue(rfc_doc in sub.contains())
+            self.assertTrue(sub in rfc_doc.part_of())
         self.assertEqual(rfc_doc.get_state_slug(), "published")
         # Should have an "errata" tag because there is an errata-url in the index XML, but no "verified-errata" tag
         # because there is no verified item in the errata JSON with doc-id matching the RFC document.
diff --git a/ietf/templates/doc/document_subseries.html b/ietf/templates/doc/document_subseries.html
index d98b568db..93fb33273 100644
--- a/ietf/templates/doc/document_subseries.html
+++ b/ietf/templates/doc/document_subseries.html
@@ -8,6 +8,6 @@
   {{ top|safe }}
   <h2>{{ doc.name|slice:":3"|upper }} {{ doc.name|slice:"3:"}} consists of:</h2>
   {% for rfc in doc.contains %}
-    <p><a href="{% url 'ietf.doc.views_doc.document_main' name=rfc.name %}"">RFC {{rfc.name|slice:"3:"}}</a> : {{rfc.title}}</p>
+    <p><a href="{% url 'ietf.doc.views_doc.document_main' name=rfc.name %}">RFC {{rfc.name|slice:"3:"}}</a> : {{rfc.title}}</p>
   {% endfor %}
 {% endblock %}
\ No newline at end of file
diff --git a/ietf/templates/doc/document_top.html b/ietf/templates/doc/document_top.html
index 29af73d9d..8a6923ee7 100644
--- a/ietf/templates/doc/document_top.html
+++ b/ietf/templates/doc/document_top.html
@@ -5,7 +5,9 @@
 <h1>
     {{ doc.title|default:"(Untitled)" }}
     <br>
-    <small class="text-body-secondary">{{ name }}</small>
+    <small class="text-body-secondary">{{ name }}{% if doc.part_of %}
+    {% for sub in doc.part_of %}{% if sub.contains|length_is:"1" %} also known as {% else %} part of {% endif %}<a href="{% url 'ietf.doc.views_doc.document_main' name=sub.name%}">{{sub.name|slice:":3"|upper}} {{sub.name|slice:"3:"}}</a>{% if not forloop.last %}, {%endif%}{% endfor %}
+    {% endif %}</small>
 </h1>
 <ul class="nav nav-tabs my-3">
     {% for name, t, url, active, tooltip in tabs %}
diff --git a/ietf/templates/doc/index_subseries.html b/ietf/templates/doc/index_subseries.html
index aaca236d7..c2eab238b 100644
--- a/ietf/templates/doc/index_subseries.html
+++ b/ietf/templates/doc/index_subseries.html
@@ -7,7 +7,7 @@
   {% origin %}
   <h1>{{type.name}}s</h1>
   {% for doc in docs %}
-    <div class="card mb-3" id={{doc.name}}>
+    <div class="card mb-3" id="{{doc.name}}">
     <div class="card-header"><a href="{% url 'ietf.doc.views_doc.document_main' name=doc.name %}">{{doc.name}}</a></div>
       <div class="card-body">
         {% for rfc in doc.contains %}