"""Unit test suite for docx.image.jpeg module""" import io import pytest from docx.image.constants import JPEG_MARKER_CODE, MIME_TYPE from docx.image.helpers import BIG_ENDIAN, StreamReader from docx.image.jpeg import ( Exif, Jfif, Jpeg, _App0Marker, _App1Marker, _JfifMarkers, _Marker, _MarkerFactory, _MarkerFinder, _MarkerParser, _SofMarker, ) from docx.image.tiff import Tiff from ..unitutil.mock import ( ANY, call, class_mock, initializer_mock, instance_mock, method_mock, ) class DescribeJpeg: def it_knows_its_content_type(self): jpeg = Jpeg(None, None, None, None) assert jpeg.content_type == MIME_TYPE.JPEG def it_knows_its_default_ext(self): jpeg = Jpeg(None, None, None, None) assert jpeg.default_ext == "jpg" class DescribeExif: def it_can_construct_from_an_exif_stream(self, from_exif_fixture): # fixture ---------------------- stream_, _JfifMarkers_, cx, cy, horz_dpi, vert_dpi = from_exif_fixture # exercise --------------------- exif = Exif.from_stream(stream_) # verify ----------------------- _JfifMarkers_.from_stream.assert_called_once_with(stream_) assert isinstance(exif, Exif) assert exif.px_width == cx assert exif.px_height == cy assert exif.horz_dpi == horz_dpi assert exif.vert_dpi == vert_dpi class DescribeJfif: def it_can_construct_from_a_jfif_stream(self, from_jfif_fixture): stream_, _JfifMarkers_, cx, cy, horz_dpi, vert_dpi = from_jfif_fixture jfif = Jfif.from_stream(stream_) _JfifMarkers_.from_stream.assert_called_once_with(stream_) assert isinstance(jfif, Jfif) assert jfif.px_width == cx assert jfif.px_height == cy assert jfif.horz_dpi == horz_dpi assert jfif.vert_dpi == vert_dpi # fixtures ------------------------------------------------------- @pytest.fixture def from_exif_fixture(self, stream_, _JfifMarkers_, jfif_markers_): px_width, px_height = 111, 222 horz_dpi, vert_dpi = 333, 444 jfif_markers_.sof.px_width = px_width jfif_markers_.sof.px_height = px_height jfif_markers_.app1.horz_dpi = horz_dpi jfif_markers_.app1.vert_dpi = vert_dpi return (stream_, _JfifMarkers_, px_width, px_height, horz_dpi, vert_dpi) @pytest.fixture def from_jfif_fixture(self, stream_, _JfifMarkers_, jfif_markers_): px_width, px_height = 111, 222 horz_dpi, vert_dpi = 333, 444 jfif_markers_.sof.px_width = px_width jfif_markers_.sof.px_height = px_height jfif_markers_.app0.horz_dpi = horz_dpi jfif_markers_.app0.vert_dpi = vert_dpi return (stream_, _JfifMarkers_, px_width, px_height, horz_dpi, vert_dpi) @pytest.fixture def _JfifMarkers_(self, request, jfif_markers_): _JfifMarkers_ = class_mock(request, "docx.image.jpeg._JfifMarkers") _JfifMarkers_.from_stream.return_value = jfif_markers_ return _JfifMarkers_ @pytest.fixture def jfif_markers_(self, request): return instance_mock(request, _JfifMarkers) @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_JfifMarkers: def it_can_construct_from_a_jfif_stream( self, stream_, _MarkerParser_, _JfifMarkers__init_, soi_, app0_, sof_, sos_ ): marker_lst = [soi_, app0_, sof_, sos_] jfif_markers = _JfifMarkers.from_stream(stream_) _MarkerParser_.from_stream.assert_called_once_with(stream_) _JfifMarkers__init_.assert_called_once_with(ANY, marker_lst) assert isinstance(jfif_markers, _JfifMarkers) def it_can_find_the_APP0_marker(self, app0_fixture): jfif_markers, app0_ = app0_fixture app0 = jfif_markers.app0 assert app0 is app0_ def it_can_find_the_APP1_marker(self, app1_fixture): jfif_markers, app1_ = app1_fixture app1 = jfif_markers.app1 assert app1 is app1_ def it_raises_if_it_cant_find_the_APP0_marker(self, no_app0_fixture): jfif_markers = no_app0_fixture with pytest.raises(KeyError): jfif_markers.app0 def it_raises_if_it_cant_find_the_APP1_marker(self, no_app1_fixture): jfif_markers = no_app1_fixture with pytest.raises(KeyError): jfif_markers.app1 def it_can_find_the_SOF_marker(self, sof_fixture): jfif_markers, sof_ = sof_fixture sof = jfif_markers.sof assert sof is sof_ def it_raises_if_it_cant_find_the_SOF_marker(self, no_sof_fixture): jfif_markers = no_sof_fixture with pytest.raises(KeyError): jfif_markers.sof # fixtures ------------------------------------------------------- @pytest.fixture def app0_(self, request): return instance_mock(request, _App0Marker, marker_code=JPEG_MARKER_CODE.APP0) @pytest.fixture def app1_(self, request): return instance_mock(request, _App1Marker, marker_code=JPEG_MARKER_CODE.APP1) @pytest.fixture def app0_fixture(self, soi_, app0_, eoi_): markers = (soi_, app0_, eoi_) jfif_markers = _JfifMarkers(markers) return jfif_markers, app0_ @pytest.fixture def app1_fixture(self, soi_, app1_, eoi_): markers = (soi_, app1_, eoi_) jfif_markers = _JfifMarkers(markers) return jfif_markers, app1_ @pytest.fixture def eoi_(self, request): return instance_mock(request, _SofMarker, marker_code=JPEG_MARKER_CODE.EOI) @pytest.fixture def _JfifMarkers__init_(self, request): return initializer_mock(request, _JfifMarkers) @pytest.fixture def marker_parser_(self, request, markers_all_): marker_parser_ = instance_mock(request, _MarkerParser) marker_parser_.iter_markers.return_value = markers_all_ return marker_parser_ @pytest.fixture def _MarkerParser_(self, request, marker_parser_): _MarkerParser_ = class_mock(request, "docx.image.jpeg._MarkerParser") _MarkerParser_.from_stream.return_value = marker_parser_ return _MarkerParser_ @pytest.fixture def markers_all_(self, request, soi_, app0_, sof_, sos_, eoi_): return [soi_, app0_, sof_, sos_, eoi_] @pytest.fixture def no_app0_fixture(self, soi_, eoi_): markers = (soi_, eoi_) return _JfifMarkers(markers) @pytest.fixture def no_app1_fixture(self, soi_, eoi_): markers = (soi_, eoi_) return _JfifMarkers(markers) @pytest.fixture def no_sof_fixture(self, soi_, eoi_): markers = (soi_, eoi_) return _JfifMarkers(markers) @pytest.fixture def sof_(self, request): return instance_mock(request, _SofMarker, marker_code=JPEG_MARKER_CODE.SOF0) @pytest.fixture def sof_fixture(self, soi_, sof_, eoi_): markers = (soi_, sof_, eoi_) jfif_markers = _JfifMarkers(markers) return jfif_markers, sof_ @pytest.fixture def soi_(self, request): return instance_mock(request, _Marker, marker_code=JPEG_MARKER_CODE.SOI) @pytest.fixture def sos_(self, request): return instance_mock(request, _Marker, marker_code=JPEG_MARKER_CODE.SOS) @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_Marker: def it_can_construct_from_a_stream_and_offset(self, from_stream_fixture): stream, marker_code, offset, _Marker__init_, length = from_stream_fixture marker = _Marker.from_stream(stream, marker_code, offset) _Marker__init_.assert_called_once_with(ANY, marker_code, offset, length) assert isinstance(marker, _Marker) # fixtures ------------------------------------------------------- @pytest.fixture( params=[ (JPEG_MARKER_CODE.SOI, 2, 0), (JPEG_MARKER_CODE.APP0, 4, 16), ] ) def from_stream_fixture(self, request, _Marker__init_): marker_code, offset, length = request.param bytes_ = b"\xff\xd8\xff\xe0\x00\x10" stream_reader = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) return stream_reader, marker_code, offset, _Marker__init_, length @pytest.fixture def _Marker__init_(self, request): return initializer_mock(request, _Marker) class Describe_App0Marker: def it_can_construct_from_a_stream_and_offset(self, _App0Marker__init_): bytes_ = b"\x00\x10JFIF\x00\x01\x01\x01\x00\x2a\x00\x18" marker_code, offset, length = JPEG_MARKER_CODE.APP0, 0, 16 density_units, x_density, y_density = 1, 42, 24 stream = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) app0_marker = _App0Marker.from_stream(stream, marker_code, offset) _App0Marker__init_.assert_called_once_with( ANY, marker_code, offset, length, density_units, x_density, y_density ) assert isinstance(app0_marker, _App0Marker) def it_knows_the_image_dpi(self, dpi_fixture): density_units, x_density, y_density, horz_dpi, vert_dpi = dpi_fixture app0 = _App0Marker(None, None, None, density_units, x_density, y_density) assert app0.horz_dpi == horz_dpi assert app0.vert_dpi == vert_dpi # fixtures ------------------------------------------------------- @pytest.fixture def _App0Marker__init_(self, request): return initializer_mock(request, _App0Marker) @pytest.fixture( params=[ (0, 100, 200, 72, 72), (1, 100, 200, 100, 200), (2, 100, 200, 254, 508), ] ) def dpi_fixture(self, request): density_units, x_density, y_density, horz_dpi, vert_dpi = request.param return density_units, x_density, y_density, horz_dpi, vert_dpi class Describe_App1Marker: def it_can_construct_from_a_stream_and_offset( self, _App1Marker__init_, _tiff_from_exif_segment_ ): bytes_ = b"\x00\x42Exif\x00\x00" marker_code, offset, length = JPEG_MARKER_CODE.APP1, 0, 66 horz_dpi, vert_dpi = 42, 24 stream = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) app1_marker = _App1Marker.from_stream(stream, marker_code, offset) _tiff_from_exif_segment_.assert_called_once_with(stream, offset, length) _App1Marker__init_.assert_called_once_with( ANY, marker_code, offset, length, horz_dpi, vert_dpi ) assert isinstance(app1_marker, _App1Marker) def it_can_construct_from_non_Exif_APP1_segment(self, _App1Marker__init_): bytes_ = b"\x00\x42Foobar" marker_code, offset, length = JPEG_MARKER_CODE.APP1, 0, 66 stream = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) app1_marker = _App1Marker.from_stream(stream, marker_code, offset) _App1Marker__init_.assert_called_once_with(ANY, marker_code, offset, length, 72, 72) assert isinstance(app1_marker, _App1Marker) def it_gets_a_tiff_from_its_Exif_segment_to_help_construct(self, get_tiff_fixture): stream, offset, length = get_tiff_fixture[:3] BytesIO_, segment_bytes, substream_ = get_tiff_fixture[3:6] Tiff_, tiff_ = get_tiff_fixture[6:] tiff = _App1Marker._tiff_from_exif_segment(stream, offset, length) BytesIO_.assert_called_once_with(segment_bytes) Tiff_.from_stream.assert_called_once_with(substream_) assert tiff is tiff_ def it_knows_the_image_dpi(self): horz_dpi, vert_dpi = 42, 24 app1 = _App1Marker(None, None, None, horz_dpi, vert_dpi) assert app1.horz_dpi == horz_dpi assert app1.vert_dpi == vert_dpi # fixtures ------------------------------------------------------- @pytest.fixture def _App1Marker__init_(self, request): return initializer_mock(request, _App1Marker) @pytest.fixture def get_tiff_fixture(self, request, substream_, Tiff_, tiff_): bytes_ = b"xfillerxMM\x00*\x00\x00\x00\x42" stream_reader = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) BytesIO_ = class_mock(request, "docx.image.jpeg.io.BytesIO", return_value=substream_) offset, segment_length, segment_bytes = 0, 16, bytes_[8:] return ( stream_reader, offset, segment_length, BytesIO_, segment_bytes, substream_, Tiff_, tiff_, ) @pytest.fixture def substream_(self, request): return instance_mock(request, io.BytesIO) @pytest.fixture def Tiff_(self, request, tiff_): Tiff_ = class_mock(request, "docx.image.jpeg.Tiff") Tiff_.from_stream.return_value = tiff_ return Tiff_ @pytest.fixture def tiff_(self, request): return instance_mock(request, Tiff, horz_dpi=42, vert_dpi=24) @pytest.fixture def _tiff_from_exif_segment_(self, request, tiff_): return method_mock( request, _App1Marker, "_tiff_from_exif_segment", autospec=False, return_value=tiff_, ) class Describe_SofMarker: def it_can_construct_from_a_stream_and_offset(self, request, _SofMarker__init_): bytes_ = b"\x00\x11\x00\x00\x2a\x00\x18" marker_code, offset, length = JPEG_MARKER_CODE.SOF0, 0, 17 px_width, px_height = 24, 42 stream = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) sof_marker = _SofMarker.from_stream(stream, marker_code, offset) _SofMarker__init_.assert_called_once_with( ANY, marker_code, offset, length, px_width, px_height ) assert isinstance(sof_marker, _SofMarker) def it_knows_the_image_width_and_height(self): sof = _SofMarker(None, None, None, 42, 24) assert sof.px_width == 42 assert sof.px_height == 24 # fixtures ------------------------------------------------------- @pytest.fixture def _SofMarker__init_(self, request): return initializer_mock(request, _SofMarker) class Describe_MarkerFactory: def it_constructs_the_appropriate_marker_object(self, call_fixture): marker_code, stream_, offset_, marker_cls_ = call_fixture marker = _MarkerFactory(marker_code, stream_, offset_) marker_cls_.from_stream.assert_called_once_with(stream_, marker_code, offset_) assert marker is marker_cls_.from_stream.return_value # fixtures ------------------------------------------------------- @pytest.fixture( params=[ JPEG_MARKER_CODE.APP0, JPEG_MARKER_CODE.APP1, JPEG_MARKER_CODE.SOF0, JPEG_MARKER_CODE.SOF7, JPEG_MARKER_CODE.SOS, ] ) def call_fixture( self, request, stream_, offset_, _App0Marker_, _App1Marker_, _SofMarker_, _Marker_, ): marker_code = request.param if marker_code == JPEG_MARKER_CODE.APP0: marker_cls_ = _App0Marker_ elif marker_code == JPEG_MARKER_CODE.APP1: marker_cls_ = _App1Marker_ elif marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES: marker_cls_ = _SofMarker_ else: marker_cls_ = _Marker_ return marker_code, stream_, offset_, marker_cls_ @pytest.fixture def _App0Marker_(self, request): return class_mock(request, "docx.image.jpeg._App0Marker") @pytest.fixture def _App1Marker_(self, request): return class_mock(request, "docx.image.jpeg._App1Marker") @pytest.fixture def _Marker_(self, request): return class_mock(request, "docx.image.jpeg._Marker") @pytest.fixture def offset_(self, request): return instance_mock(request, int) @pytest.fixture def _SofMarker_(self, request): return class_mock(request, "docx.image.jpeg._SofMarker") @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_MarkerFinder: def it_can_construct_from_a_stream(self, stream_, _MarkerFinder__init_): marker_finder = _MarkerFinder.from_stream(stream_) _MarkerFinder__init_.assert_called_once_with(ANY, stream_) assert isinstance(marker_finder, _MarkerFinder) def it_can_find_the_next_marker_after_a_given_offset(self, next_fixture): marker_finder, start, expected_code_and_offset = next_fixture marker_code, segment_offset = marker_finder.next(start) assert (marker_code, segment_offset) == expected_code_and_offset # fixtures ------------------------------------------------------- @pytest.fixture def _MarkerFinder__init_(self, request): return initializer_mock(request, _MarkerFinder) @pytest.fixture( params=[ (0, JPEG_MARKER_CODE.SOI, 2), (1, JPEG_MARKER_CODE.APP0, 4), (2, JPEG_MARKER_CODE.APP0, 4), (3, JPEG_MARKER_CODE.EOI, 12), (4, JPEG_MARKER_CODE.EOI, 12), (6, JPEG_MARKER_CODE.EOI, 12), (8, JPEG_MARKER_CODE.EOI, 12), ] ) def next_fixture(self, request): start, marker_code, segment_offset = request.param bytes_ = b"\xff\xd8\xff\xe0\x00\x01\xff\x00\xff\xff\xff\xd9" stream_reader = StreamReader(io.BytesIO(bytes_), BIG_ENDIAN) marker_finder = _MarkerFinder(stream_reader) expected_code_and_offset = (marker_code, segment_offset) return marker_finder, start, expected_code_and_offset @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) class Describe_MarkerParser: def it_can_construct_from_a_jfif_stream( self, stream_, StreamReader_, _MarkerParser__init_, stream_reader_ ): marker_parser = _MarkerParser.from_stream(stream_) StreamReader_.assert_called_once_with(stream_, BIG_ENDIAN) _MarkerParser__init_.assert_called_once_with(ANY, stream_reader_) assert isinstance(marker_parser, _MarkerParser) def it_can_iterate_over_the_jfif_markers_in_its_stream(self, iter_markers_fixture): ( marker_parser, stream_, _MarkerFinder_, marker_finder_, _MarkerFactory_, marker_codes, offsets, marker_lst, ) = iter_markers_fixture markers = list(marker_parser.iter_markers()) _MarkerFinder_.from_stream.assert_called_once_with(stream_) assert marker_finder_.next.call_args_list == [call(0), call(2), call(20)] assert _MarkerFactory_.call_args_list == [ call(marker_codes[0], stream_, offsets[0]), call(marker_codes[1], stream_, offsets[1]), call(marker_codes[2], stream_, offsets[2]), ] assert markers == marker_lst # fixtures ------------------------------------------------------- @pytest.fixture def app0_(self, request): return instance_mock(request, _App0Marker, segment_length=16) @pytest.fixture def eoi_(self, request): return instance_mock(request, _Marker, segment_length=0) @pytest.fixture def iter_markers_fixture( self, stream_reader_, _MarkerFinder_, marker_finder_, _MarkerFactory_, soi_, app0_, eoi_, ): marker_parser = _MarkerParser(stream_reader_) offsets = [2, 4, 22] marker_lst = [soi_, app0_, eoi_] marker_finder_.next.side_effect = [ (JPEG_MARKER_CODE.SOI, offsets[0]), (JPEG_MARKER_CODE.APP0, offsets[1]), (JPEG_MARKER_CODE.EOI, offsets[2]), ] marker_codes = [ JPEG_MARKER_CODE.SOI, JPEG_MARKER_CODE.APP0, JPEG_MARKER_CODE.EOI, ] return ( marker_parser, stream_reader_, _MarkerFinder_, marker_finder_, _MarkerFactory_, marker_codes, offsets, marker_lst, ) @pytest.fixture def _MarkerFactory_(self, request, soi_, app0_, eoi_): return class_mock( request, "docx.image.jpeg._MarkerFactory", side_effect=[soi_, app0_, eoi_] ) @pytest.fixture def _MarkerFinder_(self, request, marker_finder_): _MarkerFinder_ = class_mock(request, "docx.image.jpeg._MarkerFinder") _MarkerFinder_.from_stream.return_value = marker_finder_ return _MarkerFinder_ @pytest.fixture def marker_finder_(self, request): return instance_mock(request, _MarkerFinder) @pytest.fixture def _MarkerParser__init_(self, request): return initializer_mock(request, _MarkerParser) @pytest.fixture def soi_(self, request): return instance_mock(request, _Marker, segment_length=0) @pytest.fixture def stream_(self, request): return instance_mock(request, io.BytesIO) @pytest.fixture def StreamReader_(self, request, stream_reader_): return class_mock(request, "docx.image.jpeg.StreamReader", return_value=stream_reader_) @pytest.fixture def stream_reader_(self, request): return instance_mock(request, StreamReader)