diff --git a/cms/lib/spectacular.py b/cms/lib/spectacular.py index 662cf80c65f0..cdecd80b3be0 100644 --- a/cms/lib/spectacular.py +++ b/cms/lib/spectacular.py @@ -5,14 +5,16 @@ from drf_spectacular.openapi import AutoSchema # Legacy operations superseded by a conforming address under /api/authoring/. -# Each is listed in the truncated spelling as well as the full one: the routes -# make the final character optional, so both addresses resolve, and which one -# the generator emits depends on how it renders that regex. +# Regex-routed entries are listed in both the truncated and full spellings (the +# route makes the final character optional, so both addresses resolve, and which +# the generator emits depends on how it renders that regex). Exact-match entries +# (path()) appear only once. SUPERSEDED_PATHS = ( "/api/contentstore/v0/youtube_transcripts/{course_id}/chec", "/api/contentstore/v0/youtube_transcripts/{course_id}/check", "/api/contentstore/v0/youtube_transcripts/{course_id}/uploa", "/api/contentstore/v0/youtube_transcripts/{course_id}/upload", + "/api/contentstore/v1/home", ) # Legacy addresses of APIs migrated to /api/authoring/, marked deprecated for diff --git a/cms/lib/tests/test_spectacular.py b/cms/lib/tests/test_spectacular.py index 4581f23a6148..a1ad10ad9e36 100644 --- a/cms/lib/tests/test_spectacular.py +++ b/cms/lib/tests/test_spectacular.py @@ -76,8 +76,33 @@ def test_both_spellings_of_each_superseded_path_are_listed(self): "/api/contentstore/v0/youtube_transcripts/{course_id}/check", "/api/contentstore/v0/youtube_transcripts/{course_id}/uploa", "/api/contentstore/v0/youtube_transcripts/{course_id}/upload", + "/api/contentstore/v1/home", } + def test_v1_home_is_marked_deprecated_in_generated_schema(self): + """The URL router emits the path key without a trailing slash, matching SUPERSEDED_PATHS.""" + patterns = [url_path("api/contentstore/v1/home", _HomeViewSet.as_view({"get": "list"}))] + schema = SchemaGenerator(patterns=patterns).get_schema(request=None, public=True) + result = self._run(schema) + assert result["paths"]["/api/contentstore/v1/home"]["get"]["deprecated"] is True + + def test_v1_home_libraries_sibling_is_not_marked_deprecated(self): + """Exact-path lookup must not spill onto the sibling libraries path.""" + home_path = "/api/contentstore/v1/home" + libraries_path = "/api/contentstore/v1/home/libraries" + result = self._run({"paths": { + home_path: {"get": {}}, + libraries_path: {"get": {}}, + }}) + assert result["paths"][home_path]["get"]["deprecated"] is True + assert "deprecated" not in result["paths"][libraries_path]["get"] + + def test_a_conforming_sibling_path_is_not_marked_deprecated(self): + """A real path near the replacement that no entry in SUPERSEDED_PATHS covers is unchanged.""" + sibling_path = "/api/authoring/v3/home/courses/" + result = self._run({"paths": {sibling_path: {"get": {}}}}) + assert "deprecated" not in result["paths"][sibling_path]["get"] + def test_the_conforming_paths_are_left_unmarked(self): conforming = "/api/authoring/v1/courses/{course_key}/youtube_transcript_checks/" result = self._run({"paths": {conforming: {"get": {"operationId": "x"}}}})