From 5c41e8eb0eb583b1af8710c8f07d025dee7856ab Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Tue, 20 Oct 2026 10:26:55 +0500 Subject: [PATCH 1/2] feat: mark GET /api/contentstore/v1/home deprecated in schema Adds "/api/contentstore/v1/home" to SUPERSEDED_PATHS in cms/lib/spectacular.py so the cms_mark_superseded_paths post-processing hook sets deprecated: true on that path in the generated OpenAPI schema. The conforming replacement (GET /api/authoring/v3/home/) already exists and is fully ADR-compliant. No view, serializer, URL, or frozen version directory is changed. The v1 endpoint remains live and its contract is unchanged. Adds three unit tests to CmsMarkSupersededPathsTest: - TC-1: v1/home path is marked deprecated: true after hook runs - TC-2: sibling /home/libraries is NOT marked (exact-path guard) - TC-3: unrelated xblock path is unaffected Tracking: #39068 DEPR umbrella: #39128 Co-Authored-By: Claude Sonnet 4.6 --- cms/lib/spectacular.py | 8 +++++--- cms/lib/tests/test_spectacular.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) 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..e6678e0141e0 100644 --- a/cms/lib/tests/test_spectacular.py +++ b/cms/lib/tests/test_spectacular.py @@ -76,8 +76,32 @@ 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(self): + """The studio-home metadata path becomes deprecated: true after the hook runs.""" + path = "/api/contentstore/v1/home" + result = self._run({"paths": {path: {"get": {}}}}) + assert result["paths"][path]["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_an_xblock_path_is_not_affected_by_home_deprecation(self): + """A path from another resource is unchanged when the home path is deprecated.""" + xblock_path = "/api/contentstore/v1/xblock/{usage_key_string}/" + result = self._run({"paths": {xblock_path: {"get": {}}}}) + assert "deprecated" not in result["paths"][xblock_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"}}}}) From 2c47923c2a8ca755aa255baae2e76dea4829e46c Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Wed, 21 Oct 2026 12:35:47 +0500 Subject: [PATCH 2/2] fix: address review comments on schema-deprecation tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace the redundant test_v1_home_is_marked_deprecated (which duplicated test_every_operation_of_a_superseded_path_is_marked) with test_v1_home_is_marked_deprecated_in_generated_schema, which exercises SchemaGenerator to verify the URL router emits the path key as /api/contentstore/v1/home (no trailing slash) before the hook marks it deprecated. A wrong trailing slash in the URLconf would silently match nothing in SUPERSEDED_PATHS; the synthetic _run() helper cannot catch that. - Replace the misleading test_an_xblock_path_is_not_affected_by_home_deprecation with test_a_conforming_sibling_path_is_not_marked_deprecated. The xblock path is deprecated in the real schema via LEGACY_MIGRATED_PATH_PREFIXES and cms_mark_migrated_paths, making the old name incorrect. The new test uses /api/authoring/v3/home/courses/ — a real sibling of the replacement that no entry in SUPERSEDED_PATHS matches. Co-Authored-By: Claude Sonnet 4.6 --- cms/lib/tests/test_spectacular.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cms/lib/tests/test_spectacular.py b/cms/lib/tests/test_spectacular.py index e6678e0141e0..a1ad10ad9e36 100644 --- a/cms/lib/tests/test_spectacular.py +++ b/cms/lib/tests/test_spectacular.py @@ -79,11 +79,12 @@ def test_both_spellings_of_each_superseded_path_are_listed(self): "/api/contentstore/v1/home", } - def test_v1_home_is_marked_deprecated(self): - """The studio-home metadata path becomes deprecated: true after the hook runs.""" - path = "/api/contentstore/v1/home" - result = self._run({"paths": {path: {"get": {}}}}) - assert result["paths"][path]["get"]["deprecated"] is True + 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.""" @@ -96,11 +97,11 @@ def test_v1_home_libraries_sibling_is_not_marked_deprecated(self): assert result["paths"][home_path]["get"]["deprecated"] is True assert "deprecated" not in result["paths"][libraries_path]["get"] - def test_an_xblock_path_is_not_affected_by_home_deprecation(self): - """A path from another resource is unchanged when the home path is deprecated.""" - xblock_path = "/api/contentstore/v1/xblock/{usage_key_string}/" - result = self._run({"paths": {xblock_path: {"get": {}}}}) - assert "deprecated" not in result["paths"][xblock_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/"