test_musehub_oembed.py
python
| 1 | """Tests for the MuseHub oEmbed discovery endpoint. |
| 2 | |
| 3 | Covers acceptance criteria (original) and (rich metadata): |
| 4 | - test_oembed_endpoint — GET /oembed returns valid JSON with HTML embed code |
| 5 | - test_oembed_unknown_url_404 — Invalid / unrecognised URL returns 404 |
| 6 | - test_oembed_iframe_content — Returned HTML is an <iframe> pointing to embed route |
| 7 | - test_oembed_respects_maxwidth — maxwidth parameter is reflected in returned iframe width |
| 8 | - test_oembed_xml_format_501 — Non-JSON format returns 501 |
| 9 | - test_oembed_musehub_extension_fields — Response includes all musehub:* extension fields |
| 10 | - test_oembed_standard_fields_complete — Response has all required standard oEmbed fields |
| 11 | - test_oembed_commit_endpoint — GET /oembed/commit returns 200 for commit URLs |
| 12 | - test_oembed_commit_unknown_url_404 — /oembed/commit returns 404 for non-commit URLs |
| 13 | - test_oembed_commit_iframe_uses_sha — /oembed/commit iframe src contains the commit SHA |
| 14 | - test_oembed_commit_xml_format_501 — /oembed/commit returns 501 for XML format |
| 15 | |
| 16 | The /oembed and /oembed/commit endpoints require no auth — oEmbed consumers |
| 17 | (CMSes, blog platforms) call them without user credentials to discover embed metadata. |
| 18 | """ |
| 19 | from __future__ import annotations |
| 20 | |
| 21 | import pytest |
| 22 | from httpx import AsyncClient |
| 23 | |
| 24 | |
| 25 | @pytest.mark.anyio |
| 26 | async def test_oembed_endpoint(client: AsyncClient) -> None: |
| 27 | """GET /oembed with a valid embed URL returns 200 JSON with oEmbed fields.""" |
| 28 | repo_id = "aaaabbbb-cccc-dddd-eeee-ffff00001111" |
| 29 | ref = "abc1234567890" |
| 30 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 31 | |
| 32 | response = await client.get(f"/oembed?url={embed_url}") |
| 33 | assert response.status_code == 200 |
| 34 | assert "application/json" in response.headers["content-type"] |
| 35 | |
| 36 | data = response.json() |
| 37 | assert data["version"] == "1.0" |
| 38 | assert data["type"] == "rich" |
| 39 | assert "title" in data |
| 40 | assert data["provider_name"] == "MuseHub" |
| 41 | assert "html" in data |
| 42 | assert isinstance(data["width"], int) |
| 43 | assert isinstance(data["height"], int) |
| 44 | |
| 45 | |
| 46 | @pytest.mark.anyio |
| 47 | async def test_oembed_unknown_url_404(client: AsyncClient) -> None: |
| 48 | """GET /oembed with a URL that doesn't match an embed pattern returns 404.""" |
| 49 | response = await client.get("/oembed?url=https://example.com/not-musehub") |
| 50 | assert response.status_code == 404 |
| 51 | |
| 52 | |
| 53 | @pytest.mark.anyio |
| 54 | async def test_oembed_iframe_content(client: AsyncClient) -> None: |
| 55 | """The HTML field returned by /oembed is an <iframe> pointing to the embed route.""" |
| 56 | repo_id = "11112222-3333-4444-5555-666677778888" |
| 57 | ref = "deadbeef1234" |
| 58 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 59 | |
| 60 | response = await client.get(f"/oembed?url={embed_url}") |
| 61 | assert response.status_code == 200 |
| 62 | |
| 63 | html = response.json()["html"] |
| 64 | assert "<iframe" in html |
| 65 | assert f"/musehub/ui/{repo_id}/embed/{ref}" in html |
| 66 | assert "</iframe>" in html |
| 67 | |
| 68 | |
| 69 | @pytest.mark.anyio |
| 70 | async def test_oembed_respects_maxwidth(client: AsyncClient) -> None: |
| 71 | """maxwidth query parameter is reflected as the iframe width attribute.""" |
| 72 | repo_id = "aaaabbbb-1111-2222-3333-ccccddddeeee" |
| 73 | ref = "cafebabe" |
| 74 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 75 | |
| 76 | response = await client.get(f"/oembed?url={embed_url}&maxwidth=400") |
| 77 | assert response.status_code == 200 |
| 78 | |
| 79 | data = response.json() |
| 80 | assert data["width"] == 400 |
| 81 | assert 'width="400"' in data["html"] |
| 82 | |
| 83 | |
| 84 | @pytest.mark.anyio |
| 85 | async def test_oembed_xml_format_501(client: AsyncClient) -> None: |
| 86 | """Requesting XML format returns 501 Not Implemented.""" |
| 87 | repo_id = "aaaabbbb-cccc-dddd-eeee-000011112222" |
| 88 | ref = "feedface" |
| 89 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 90 | |
| 91 | response = await client.get(f"/oembed?url={embed_url}&format=xml") |
| 92 | assert response.status_code == 501 |
| 93 | |
| 94 | |
| 95 | @pytest.mark.anyio |
| 96 | async def test_oembed_no_auth_required(client: AsyncClient) -> None: |
| 97 | """oEmbed endpoint must not require a JWT — CMS platforms call it unauthenticated.""" |
| 98 | repo_id = "bbbbcccc-dddd-eeee-ffff-000011112222" |
| 99 | ref = "aabbccdd" |
| 100 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 101 | |
| 102 | response = await client.get(f"/oembed?url={embed_url}") |
| 103 | assert response.status_code != 401 |
| 104 | assert response.status_code == 200 |
| 105 | |
| 106 | |
| 107 | @pytest.mark.anyio |
| 108 | async def test_oembed_title_contains_short_ref(client: AsyncClient) -> None: |
| 109 | """oEmbed title includes the first 8 characters of the ref for human readability.""" |
| 110 | repo_id = "ccccdddd-eeee-ffff-0000-111122223333" |
| 111 | ref = "1234567890abcdef" |
| 112 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 113 | |
| 114 | response = await client.get(f"/oembed?url={embed_url}") |
| 115 | assert response.status_code == 200 |
| 116 | |
| 117 | data = response.json() |
| 118 | assert ref[:8] in data["title"] |
| 119 | |
| 120 | |
| 121 | @pytest.mark.anyio |
| 122 | async def test_oembed_musehub_extension_fields(client: AsyncClient) -> None: |
| 123 | """Response includes all musehub:* extension fields defined.""" |
| 124 | repo_id = "ddddeeee-ffff-0000-1111-222233334444" |
| 125 | ref = "beefcafe1234" |
| 126 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 127 | |
| 128 | response = await client.get(f"/oembed?url={embed_url}") |
| 129 | assert response.status_code == 200 |
| 130 | |
| 131 | data = response.json() |
| 132 | # All musehub:* extension fields must be present (may be null if not yet resolved) |
| 133 | extension_fields = [ |
| 134 | "musehub:key", |
| 135 | "musehub:tempo_bpm", |
| 136 | "musehub:time_signature", |
| 137 | "musehub:duration_beats", |
| 138 | "musehub:instruments", |
| 139 | "musehub:license", |
| 140 | "musehub:genre", |
| 141 | "musehub:commit_id", |
| 142 | "musehub:audio_url", |
| 143 | ] |
| 144 | for field in extension_fields: |
| 145 | assert field in data, f"Missing extension field: {field}" |
| 146 | |
| 147 | # commit_id is always populated from the ref |
| 148 | assert data["musehub:commit_id"] == ref[:8] |
| 149 | # audio_url must be a string URL pointing to the render endpoint |
| 150 | assert isinstance(data["musehub:audio_url"], str) |
| 151 | assert repo_id in data["musehub:audio_url"] |
| 152 | |
| 153 | |
| 154 | @pytest.mark.anyio |
| 155 | async def test_oembed_standard_fields_complete(client: AsyncClient) -> None: |
| 156 | """Response contains the full set of standard oEmbed rich-type fields.""" |
| 157 | repo_id = "eeeeffff-0000-1111-2222-333344445555" |
| 158 | ref = "d00dcafe" |
| 159 | embed_url = f"/musehub/ui/{repo_id}/embed/{ref}" |
| 160 | |
| 161 | response = await client.get(f"/oembed?url={embed_url}") |
| 162 | assert response.status_code == 200 |
| 163 | |
| 164 | data = response.json() |
| 165 | required_fields = [ |
| 166 | "version", "type", "title", |
| 167 | "provider_name", "provider_url", |
| 168 | "thumbnail_url", "thumbnail_width", "thumbnail_height", |
| 169 | "html", "width", "height", |
| 170 | ] |
| 171 | for field in required_fields: |
| 172 | assert field in data, f"Missing required oEmbed field: {field}" |
| 173 | |
| 174 | assert data["provider_name"] == "MuseHub" |
| 175 | assert data["provider_url"] == "https://musehub.stori.app" |
| 176 | assert isinstance(data["thumbnail_width"], int) |
| 177 | assert isinstance(data["thumbnail_height"], int) |
| 178 | assert repo_id in data["thumbnail_url"] |
| 179 | |
| 180 | |
| 181 | @pytest.mark.anyio |
| 182 | async def test_oembed_commit_endpoint(client: AsyncClient) -> None: |
| 183 | """GET /oembed/commit returns 200 JSON for a valid commit URL.""" |
| 184 | repo_id = "aaaabbbb-cccc-dddd-eeee-111122223333" |
| 185 | sha = "abc1234567890def" |
| 186 | commit_url = f"/musehub/ui/{repo_id}/commit/{sha}" |
| 187 | |
| 188 | response = await client.get(f"/oembed/commit?url={commit_url}") |
| 189 | assert response.status_code == 200 |
| 190 | assert "application/json" in response.headers["content-type"] |
| 191 | |
| 192 | data = response.json() |
| 193 | assert data["version"] == "1.0" |
| 194 | assert data["type"] == "rich" |
| 195 | assert sha[:8] in data["title"] |
| 196 | assert data["provider_name"] == "MuseHub" |
| 197 | assert "html" in data |
| 198 | assert data["musehub:commit_id"] == sha[:8] |
| 199 | |
| 200 | |
| 201 | @pytest.mark.anyio |
| 202 | async def test_oembed_commit_unknown_url_404(client: AsyncClient) -> None: |
| 203 | """/oembed/commit returns 404 for a URL that doesn't match a commit pattern.""" |
| 204 | response = await client.get("/oembed/commit?url=https://example.com/not-a-commit") |
| 205 | assert response.status_code == 404 |
| 206 | |
| 207 | |
| 208 | @pytest.mark.anyio |
| 209 | async def test_oembed_commit_iframe_uses_sha(client: AsyncClient) -> None: |
| 210 | """The /oembed/commit endpoint's iframe src contains the commit SHA as the ref.""" |
| 211 | repo_id = "bbbbcccc-dddd-eeee-ffff-111122223333" |
| 212 | sha = "deadbeefcafe0001" |
| 213 | commit_url = f"/musehub/ui/{repo_id}/commit/{sha}" |
| 214 | |
| 215 | response = await client.get(f"/oembed/commit?url={commit_url}") |
| 216 | assert response.status_code == 200 |
| 217 | |
| 218 | html = response.json()["html"] |
| 219 | # The embed player uses the full SHA as the ref so the snapshot is pinned |
| 220 | assert sha in html |
| 221 | assert f"/musehub/ui/{repo_id}/embed/{sha}" in html |
| 222 | |
| 223 | |
| 224 | @pytest.mark.anyio |
| 225 | async def test_oembed_commit_xml_format_501(client: AsyncClient) -> None: |
| 226 | """/oembed/commit returns 501 for non-JSON format requests.""" |
| 227 | repo_id = "ccccdddd-eeee-ffff-0000-111122223333" |
| 228 | sha = "cafebabe12345678" |
| 229 | commit_url = f"/musehub/ui/{repo_id}/commit/{sha}" |
| 230 | |
| 231 | response = await client.get(f"/oembed/commit?url={commit_url}&format=xml") |
| 232 | assert response.status_code == 501 |