/
/
/
1"""Tests for the JSON helpers."""
2
3from __future__ import annotations
4
5import pytest
6
7from music_assistant.helpers.json import strip_code_fence
8
9
10@pytest.mark.parametrize(
11 ("text", "expected"),
12 [
13 ('```json\n{"a": 1}\n```', '{"a": 1}'),
14 ('```JSON\n{"a": 1}\n```', '{"a": 1}'),
15 ('```\n{"a": 1}\n```', '{"a": 1}'),
16 ('````json\n{"a": 1}\n````', '{"a": 1}'),
17 (' ```json\r\n{"a": 1}\r\n``` ', '{"a": 1}'),
18 ('```json\n\n{"a": 1}\n\n```', '{"a": 1}'),
19 ],
20)
21def test_strip_code_fence_unwraps_a_single_fenced_block(text: str, expected: str) -> None:
22 """Return the content of one code fence wrapping the complete text."""
23 assert strip_code_fence(text) == expected
24
25
26@pytest.mark.parametrize(
27 "text",
28 [
29 '{"a": 1}',
30 "not fenced at all",
31 # A fence around only part of the text must not be unwrapped.
32 'Here you go:\n```json\n{"a": 1}\n```',
33 '```json\n{"a": 1}\n```\nHope that helps!',
34 '```json\nnot json\n```\n```json\n{"a": 1}\n```',
35 # An unterminated or mismatched fence is not a fenced block.
36 '```json\n{"a": 1}',
37 '````json\n{"a": 1}\n```',
38 '```json\n{"a": 1}\n````',
39 "```",
40 "``````",
41 # The opening line must carry nothing but a language tag.
42 '```json {"a": 1}\n```',
43 '```{"a": 1}\n```',
44 ],
45)
46def test_strip_code_fence_only_trims_whitespace_of_other_text(text: str) -> None:
47 """Leave text that is not exactly one fenced block untouched apart from whitespace."""
48 assert strip_code_fence(f" {text} ") == text
49
50
51def test_strip_code_fence_handles_long_backtick_runs() -> None:
52 """
53 Return long runs of backticks unchanged instead of scanning them for a fence body.
54
55 Matching a fence with a backtracking pattern took seconds on these, which would block
56 the event loop of a caller parsing an AI response.
57 """
58 assert strip_code_fence("`" * 4096) == "`" * 4096
59 assert strip_code_fence("`" * 4087 + "jsonjson") == "`" * 4087 + "jsonjson"
60