"""Unit test suite for docx.image.png module.""" import io import pytest from docx.image.constants import MIME_TYPE, PNG_CHUNK_TYPE from docx.image.exceptions import InvalidImageStreamError from docx.image.helpers import BIG_ENDIAN, StreamReader from docx.image.png import ( Png, _Chunk, _ChunkFactory, _ChunkParser, _Chunks, _IHDRChunk, _pHYsChunk, _PngParser, ) from ..unitutil.mock import ( ANY, call, class_mock, function_mock, initializer_mock, instance_mock, method_mock, ) class DescribePng: def it_can_construct_from_a_png_stream(self, stream_, _PngParser_, png_parser_, Png__init__): px_width, px_height, horz_dpi, vert_dpi = 42, 24, 36, 63 png_parser_.px_width = px_width png_parser_.px_height = px_height png_parser_.horz_dpi = horz_dpi png_parser_.vert_dpi = vert_dpi png = Png.from_stream(stream_) _PngParser_.parse.assert_called_once_with(stream_) Png__init__.assert_called_once_with(ANY, px_width, px_height, horz_dpi, vert_dpi) assert isinstance(png, Png) def it_knows_its_content_type(self): png = Png(None, None, None, None) assert png.content_type == MIME_TYPE.PNG def it_knows_its_default_ext(self): png = Png(None, None, None, None) assert png.default_ext == "png" # fixtures ------------------------------------------------------- @pytest.fixture def Png__init__(self, request): return initializer_mock(request, Png) @pytest.fixture def _PngParser_(self, request, png_parser_): _PngParser_ = class_mock(request, "docx.image.png._PngParser") _PngParser_.parse.return_value = png_parser_ return _PngParser_ @pytest.fixture def png_parser_(self, request): return instance_mock(request, _PngParser) @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_PngParser: def it_can_parse_the_headers_of_a_PNG_stream( self, stream_, _Chunks_, _PngParser__init_, chunks_ ): png_parser = _PngParser.parse(stream_) _Chunks_.from_stream.assert_called_once_with(stream_) _PngParser__init_.assert_called_once_with(ANY, chunks_) assert isinstance(png_parser, _PngParser) def it_knows_the_image_width_and_height(self, dimensions_fixture): png_parser, px_width, px_height = dimensions_fixture assert png_parser.px_width == px_width assert png_parser.px_height == px_height def it_knows_the_image_dpi(self, dpi_fixture): png_parser, horz_dpi, vert_dpi = dpi_fixture assert png_parser.horz_dpi == horz_dpi assert png_parser.vert_dpi == vert_dpi def it_defaults_image_dpi_to_72(self, no_dpi_fixture): png_parser = no_dpi_fixture assert png_parser.horz_dpi == 72 assert png_parser.vert_dpi == 72 # fixtures ------------------------------------------------------- @pytest.fixture def _Chunks_(self, request, chunks_): _Chunks_ = class_mock(request, "docx.image.png._Chunks") _Chunks_.from_stream.return_value = chunks_ return _Chunks_ @pytest.fixture def chunks_(self, request): return instance_mock(request, _Chunks) @pytest.fixture def dimensions_fixture(self, chunks_): px_width, px_height = 12, 34 chunks_.IHDR.px_width = px_width chunks_.IHDR.px_height = px_height png_parser = _PngParser(chunks_) return png_parser, px_width, px_height @pytest.fixture def dpi_fixture(self, chunks_): horz_px_per_unit, vert_px_per_unit, units_specifier = 1654, 945, 1 horz_dpi, vert_dpi = 42, 24 chunks_.pHYs.horz_px_per_unit = horz_px_per_unit chunks_.pHYs.vert_px_per_unit = vert_px_per_unit chunks_.pHYs.units_specifier = units_specifier png_parser = _PngParser(chunks_) return png_parser, horz_dpi, vert_dpi @pytest.fixture(params=[(-1, -1), (0, 1000), (None, 1000), (1, 0), (1, None)]) def no_dpi_fixture(self, request, chunks_): """ Scenarios are: 1) no pHYs chunk in PNG stream, 2) units specifier other than 1; 3) px_per_unit is 0; 4) px_per_unit is None """ units_specifier, px_per_unit = request.param if units_specifier == -1: chunks_.pHYs = None else: chunks_.pHYs.horz_px_per_unit = px_per_unit chunks_.pHYs.vert_px_per_unit = px_per_unit chunks_.pHYs.units_specifier = units_specifier png_parser = _PngParser(chunks_) return png_parser @pytest.fixture def _PngParser__init_(self, request): return initializer_mock(request, _PngParser) @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_Chunks: def it_can_construct_from_a_stream(self, stream_, _ChunkParser_, chunk_parser_, _Chunks__init_): chunk_lst = [1, 2] chunk_parser_.iter_chunks.return_value = iter(chunk_lst) chunks = _Chunks.from_stream(stream_) _ChunkParser_.from_stream.assert_called_once_with(stream_) chunk_parser_.iter_chunks.assert_called_once_with() _Chunks__init_.assert_called_once_with(ANY, chunk_lst) assert isinstance(chunks, _Chunks) def it_provides_access_to_the_IHDR_chunk(self, IHDR_fixture): chunks, IHDR_chunk_ = IHDR_fixture assert IHDR_chunk_ == chunks.IHDR def it_provides_access_to_the_pHYs_chunk(self, pHYs_fixture): chunks, expected_chunk = pHYs_fixture assert chunks.pHYs == expected_chunk def it_raises_if_theres_no_IHDR_chunk(self, no_IHDR_fixture): chunks = no_IHDR_fixture with pytest.raises(InvalidImageStreamError): chunks.IHDR # fixtures ------------------------------------------------------- @pytest.fixture def _ChunkParser_(self, request, chunk_parser_): _ChunkParser_ = class_mock(request, "docx.image.png._ChunkParser") _ChunkParser_.from_stream.return_value = chunk_parser_ return _ChunkParser_ @pytest.fixture def chunk_parser_(self, request): return instance_mock(request, _ChunkParser) @pytest.fixture def _Chunks__init_(self, request): return initializer_mock(request, _Chunks) @pytest.fixture def IHDR_fixture(self, IHDR_chunk_, pHYs_chunk_): chunks = (IHDR_chunk_, pHYs_chunk_) chunks = _Chunks(chunks) return chunks, IHDR_chunk_ @pytest.fixture def IHDR_chunk_(self, request): return instance_mock(request, _IHDRChunk, type_name=PNG_CHUNK_TYPE.IHDR) @pytest.fixture def no_IHDR_fixture(self, pHYs_chunk_): chunks = (pHYs_chunk_,) chunks = _Chunks(chunks) return chunks @pytest.fixture def pHYs_chunk_(self, request): return instance_mock(request, _pHYsChunk, type_name=PNG_CHUNK_TYPE.pHYs) @pytest.fixture(params=[True, False]) def pHYs_fixture(self, request, IHDR_chunk_, pHYs_chunk_): has_pHYs_chunk = request.param chunks = [IHDR_chunk_] if has_pHYs_chunk: chunks.append(pHYs_chunk_) expected_chunk = pHYs_chunk_ if has_pHYs_chunk else None chunks = _Chunks(chunks) return chunks, expected_chunk @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_ChunkParser: def it_can_construct_from_a_stream( self, stream_, StreamReader_, stream_rdr_, _ChunkParser__init_ ): chunk_parser = _ChunkParser.from_stream(stream_) StreamReader_.assert_called_once_with(stream_, BIG_ENDIAN) _ChunkParser__init_.assert_called_once_with(ANY, stream_rdr_) assert isinstance(chunk_parser, _ChunkParser) def it_can_iterate_over_the_chunks_in_its_png_stream( self, _iter_chunk_offsets_, _ChunkFactory_, stream_rdr_, chunk_, chunk_2_ ): offsets = [2, 4, 6] chunk_lst = [chunk_, chunk_2_] chunk_parser = _ChunkParser(stream_rdr_) chunks = list(chunk_parser.iter_chunks()) _iter_chunk_offsets_.assert_called_once_with(chunk_parser) assert _ChunkFactory_.call_args_list == [ call(PNG_CHUNK_TYPE.IHDR, stream_rdr_, offsets[0]), call(PNG_CHUNK_TYPE.pHYs, stream_rdr_, offsets[1]), ] assert chunks == chunk_lst def it_iterates_over_the_chunk_offsets_to_help_parse(self, iter_offsets_fixture): chunk_parser, expected_chunk_offsets = iter_offsets_fixture chunk_offsets = list(chunk_parser._iter_chunk_offsets()) assert chunk_offsets == expected_chunk_offsets # fixtures ------------------------------------------------------- @pytest.fixture def chunk_(self, request): return instance_mock(request, _Chunk) @pytest.fixture def chunk_2_(self, request): return instance_mock(request, _Chunk) @pytest.fixture def _ChunkFactory_(self, request, chunk_lst_): return function_mock(request, "docx.image.png._ChunkFactory", side_effect=chunk_lst_) @pytest.fixture def chunk_lst_(self, chunk_, chunk_2_): return [chunk_, chunk_2_] @pytest.fixture def _ChunkParser__init_(self, request): return initializer_mock(request, _ChunkParser) @pytest.fixture def _iter_chunk_offsets_(self, request): chunk_offsets = ( (PNG_CHUNK_TYPE.IHDR, 2), (PNG_CHUNK_TYPE.pHYs, 4), ) return method_mock( request, _ChunkParser, "_iter_chunk_offsets", return_value=iter(chunk_offsets), ) @pytest.fixture def iter_offsets_fixture(self): bytes_ = b"-filler-\x00\x00\x00\x00IHDRxxxx\x00\x00\x00\x00IEND" stream_rdr = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) chunk_parser = _ChunkParser(stream_rdr) expected_chunk_offsets = [ (PNG_CHUNK_TYPE.IHDR, 16), (PNG_CHUNK_TYPE.IEND, 28), ] return chunk_parser, expected_chunk_offsets @pytest.fixture def StreamReader_(self, request, stream_rdr_): return class_mock(request, "docx.image.png.StreamReader", return_value=stream_rdr_) @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) @pytest.fixture def stream_rdr_(self, request): return instance_mock(request, StreamReader) class Describe_ChunkFactory: def it_constructs_the_appropriate_Chunk_subclass(self, call_fixture): chunk_type, stream_rdr_, offset, chunk_cls_ = call_fixture chunk = _ChunkFactory(chunk_type, stream_rdr_, offset) chunk_cls_.from_offset.assert_called_once_with(chunk_type, stream_rdr_, offset) assert isinstance(chunk, _Chunk) # fixtures ------------------------------------------------------- @pytest.fixture( params=[ PNG_CHUNK_TYPE.IHDR, PNG_CHUNK_TYPE.pHYs, PNG_CHUNK_TYPE.IEND, ] ) def call_fixture(self, request, _IHDRChunk_, _pHYsChunk_, _Chunk_, stream_rdr_): chunk_type = request.param chunk_cls_ = { PNG_CHUNK_TYPE.IHDR: _IHDRChunk_, PNG_CHUNK_TYPE.pHYs: _pHYsChunk_, PNG_CHUNK_TYPE.IEND: _Chunk_, }[chunk_type] offset = 999 return chunk_type, stream_rdr_, offset, chunk_cls_ @pytest.fixture def _Chunk_(self, request, chunk_): _Chunk_ = class_mock(request, "docx.image.png._Chunk") _Chunk_.from_offset.return_value = chunk_ return _Chunk_ @pytest.fixture def chunk_(self, request): return instance_mock(request, _Chunk) @pytest.fixture def _IHDRChunk_(self, request, ihdr_chunk_): _IHDRChunk_ = class_mock(request, "docx.image.png._IHDRChunk") _IHDRChunk_.from_offset.return_value = ihdr_chunk_ return _IHDRChunk_ @pytest.fixture def ihdr_chunk_(self, request): return instance_mock(request, _IHDRChunk) @pytest.fixture def _pHYsChunk_(self, request, phys_chunk_): _pHYsChunk_ = class_mock(request, "docx.image.png._pHYsChunk") _pHYsChunk_.from_offset.return_value = phys_chunk_ return _pHYsChunk_ @pytest.fixture def phys_chunk_(self, request): return instance_mock(request, _pHYsChunk) @pytest.fixture def stream_rdr_(self, request): return instance_mock(request, StreamReader) class Describe_Chunk: def it_can_construct_from_a_stream_and_offset(self): chunk_type = "fOOB" chunk = _Chunk.from_offset(chunk_type, None, None) assert isinstance(chunk, _Chunk) assert chunk.type_name == chunk_type class Describe_IHDRChunk: def it_can_construct_from_a_stream_and_offset(self, from_offset_fixture): stream_rdr, offset, px_width, px_height = from_offset_fixture ihdr_chunk = _IHDRChunk.from_offset(None, stream_rdr, offset) assert isinstance(ihdr_chunk, _IHDRChunk) assert ihdr_chunk.px_width == px_width assert ihdr_chunk.px_height == px_height # fixtures ------------------------------------------------------- @pytest.fixture def from_offset_fixture(self): bytes_ = b"\x00\x00\x00\x2a\x00\x00\x00\x18" stream_rdr = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) offset, px_width, px_height = 0, 42, 24 return stream_rdr, offset, px_width, px_height class Describe_pHYsChunk: def it_can_construct_from_a_stream_and_offset(self, from_offset_fixture): stream_rdr, offset = from_offset_fixture[:2] horz_px_per_unit, vert_px_per_unit = from_offset_fixture[2:4] units_specifier = from_offset_fixture[4] pHYs_chunk = _pHYsChunk.from_offset(None, stream_rdr, offset) assert isinstance(pHYs_chunk, _pHYsChunk) assert pHYs_chunk.horz_px_per_unit == horz_px_per_unit assert pHYs_chunk.vert_px_per_unit == vert_px_per_unit assert pHYs_chunk.units_specifier == units_specifier # fixtures ------------------------------------------------------- @pytest.fixture def from_offset_fixture(self): bytes_ = b"\x00\x00\x00\x2a\x00\x00\x00\x18\x01" stream_rdr = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) offset, horz_px_per_unit, vert_px_per_unit, units_specifier = (0, 42, 24, 1) return (stream_rdr, offset, horz_px_per_unit, vert_px_per_unit, units_specifier)