import re
-from typing import Any
+from typing import TYPE_CHECKING, Any, Callable, TypeVar
from unittest.mock import patch
-from click.testing import CliRunner
import pytest
+from click.testing import CliRunner
-from tests.util import read_data, DETERMINISTIC_HEADER
+from tests.util import DETERMINISTIC_HEADER, read_data
try:
- import blackd
- from aiohttp.test_utils import AioHTTPTestCase
from aiohttp import web
+ from aiohttp.test_utils import AioHTTPTestCase
+
+ import blackd
except ImportError as e:
raise RuntimeError("Please install Black with the 'd' extra") from e
-try:
- from aiohttp.test_utils import unittest_run_loop
-except ImportError:
- # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and aiohttp 4
- # removed it. To maintain compatibility we can make our own no-op decorator.
- def unittest_run_loop(func: Any, *args: Any, **kwargs: Any) -> Any:
- return func
+if TYPE_CHECKING:
+ F = TypeVar("F", bound=Callable[..., Any])
+
+ unittest_run_loop: Callable[[F], F] = lambda x: x
+else:
+ try:
+ from aiohttp.test_utils import unittest_run_loop
+ except ImportError:
+ # unittest_run_loop is unnecessary and a no-op since aiohttp 3.8, and
+ # aiohttp 4 removed it. To maintain compatibility we can make our own
+ # no-op decorator.
+ def unittest_run_loop(func, *args, **kwargs):
+ return func
@pytest.mark.blackd
-class BlackDTestCase(AioHTTPTestCase):
+class BlackDTestCase(AioHTTPTestCase): # type: ignore[misc]
def test_blackd_main(self) -> None:
with patch("blackd.web.run_app"):
result = CliRunner().invoke(blackd.main, [])
async def test_blackd_invalid_python_variant(self) -> None:
async def check(header_value: str, expected_status: int = 400) -> None:
response = await self.client.post(
- "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
+ "/",
+ data=b"what",
+ headers={blackd.PYTHON_VARIANT_HEADER: header_value},
)
self.assertEqual(response.status, expected_status)
@unittest_run_loop
async def test_blackd_invalid_line_length(self) -> None:
response = await self.client.post(
- "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
+ "/",
+ data=b'print("hello")\n',
+ headers={blackd.LINE_LENGTH_HEADER: "NaN"},
)
self.assertEqual(response.status, 400)
+ @unittest_run_loop
+ async def test_blackd_skip_first_source_line(self) -> None:
+ invalid_first_line = b"Header will be skipped\r\ni = [1,2,3]\nj = [1,2,3]\n"
+ expected_result = b"Header will be skipped\r\ni = [1, 2, 3]\nj = [1, 2, 3]\n"
+ response = await self.client.post("/", data=invalid_first_line)
+ self.assertEqual(response.status, 400)
+ response = await self.client.post(
+ "/",
+ data=invalid_first_line,
+ headers={blackd.SKIP_SOURCE_FIRST_LINE: "true"},
+ )
+ self.assertEqual(response.status, 200)
+ self.assertEqual(await response.read(), expected_result)
+
+ @unittest_run_loop
+ async def test_blackd_preview(self) -> None:
+ response = await self.client.post(
+ "/", data=b'print("hello")\n', headers={blackd.PREVIEW: "true"}
+ )
+ self.assertEqual(response.status, 204)
+
@unittest_run_loop
async def test_blackd_response_black_version_header(self) -> None:
response = await self.client.post("/")
response = await self.client.post("/", headers={"Origin": "*"})
self.assertIsNotNone(response.headers.get("Access-Control-Allow-Origin"))
self.assertIsNotNone(response.headers.get("Access-Control-Expose-Headers"))
+
+ @unittest_run_loop
+ async def test_preserves_line_endings(self) -> None:
+ for data in (b"c\r\nc\r\n", b"l\nl\n"):
+ # test preserved newlines when reformatted
+ response = await self.client.post("/", data=data + b" ")
+ self.assertEqual(await response.text(), data.decode())
+ # test 204 when no change
+ response = await self.client.post("/", data=data)
+ self.assertEqual(response.status, 204)
+
+ @unittest_run_loop
+ async def test_normalizes_line_endings(self) -> None:
+ for data, expected in ((b"c\r\nc\n", "c\r\nc\r\n"), (b"l\nl\r\n", "l\nl\n")):
+ response = await self.client.post("/", data=data)
+ self.assertEqual(await response.text(), expected)
+ self.assertEqual(response.status, 200)