1# -*- coding: utf-8 -*-
2# Copyright 2017 Google Inc. All Rights Reserved.
3#
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and associated documentation files (the
6# "Software"), to deal in the Software without restriction, including
7# without limitation the rights to use, copy, modify, merge, publish, dis-
8# tribute, sublicense, and/or sell copies of the Software, and to permit
9# persons to whom the Software is furnished to do so, subject to the fol-
10# lowing conditions:
11#
12# The above copyright notice and this permission notice shall be included
13# in all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21# IN THE SOFTWARE.
22"""Tests for media helper functions and classes for GCS JSON API."""
23
24from __future__ import absolute_import
25from __future__ import print_function
26from __future__ import division
27from __future__ import unicode_literals
28
29from gslib.gcs_json_media import BytesTransferredContainer
30from gslib.gcs_json_media import UploadCallbackConnectionClassFactory
31import gslib.tests.testcase as testcase
32import six
33
34from six import add_move, MovedModule
35add_move(MovedModule('mock', 'mock', 'unittest.mock'))
36from six.moves import mock
37
38# Assigning string representations of the appropriate package, used for
39# '@mock.patch` methods that take a string in the following format:
40# "package.module.ClassName"
41if six.PY2:
42  https_connection = 'httplib.HTTPSConnection'
43else:
44  https_connection = 'http.client.HTTPSConnection'
45
46
47class TestUploadCallbackConnection(testcase.GsUtilUnitTestCase):
48  """Tests for the upload callback connection."""
49
50  def setUp(self):
51    super(TestUploadCallbackConnection, self).setUp()
52    self.bytes_container = BytesTransferredContainer()
53    self.class_factory = UploadCallbackConnectionClassFactory(
54        self.bytes_container,
55        buffer_size=50,
56        total_size=100,
57        progress_callback='Sample')
58    self.instance = self.class_factory.GetConnectionClass()('host')
59
60  @mock.patch(https_connection)
61  def testHeaderDefaultBehavior(self, mock_conn):
62    """Test the size modifier is correct under expected headers."""
63    mock_conn.putheader.return_value = None
64    self.instance.putheader('content-encoding', 'gzip')
65    self.instance.putheader('content-length', '10')
66    self.instance.putheader('content-range', 'bytes 0-104/*')
67    # Ensure the modifier is as expected.
68    self.assertAlmostEqual(self.instance.size_modifier, 10.5)
69
70  @mock.patch(https_connection)
71  def testHeaderIgnoreWithoutGzip(self, mock_conn):
72    """Test that the gzip content-encoding is required to modify size."""
73    mock_conn.putheader.return_value = None
74    self.instance.putheader('content-length', '10')
75    self.instance.putheader('content-range', 'bytes 0-99/*')
76    # Ensure the modifier is unchanged.
77    self.assertAlmostEqual(self.instance.size_modifier, 1.0)
78
79  @mock.patch(https_connection)
80  def testHeaderRangeFormatX_YSlashStar(self, mock_conn):
81    """Test content-range header format X-Y/* """
82    self.instance.putheader('content-encoding', 'gzip')
83    self.instance.putheader('content-length', '10')
84    self.instance.putheader('content-range', 'bytes 0-99/*')
85    # Ensure the modifier is as expected.
86    self.assertAlmostEqual(self.instance.size_modifier, 10.0)
87
88  @mock.patch(https_connection)
89  def testHeaderRangeFormatStarSlash100(self, mock_conn):
90    """Test content-range header format */100 """
91    self.instance.putheader('content-encoding', 'gzip')
92    self.instance.putheader('content-length', '10')
93    self.instance.putheader('content-range', 'bytes */100')
94    # Ensure the modifier is as expected.
95    self.assertAlmostEqual(self.instance.size_modifier, 1.0)
96
97  @mock.patch(https_connection)
98  def testHeaderRangeFormat0_99Slash100(self, mock_conn):
99    """Test content-range header format 0-99/100 """
100    self.instance.putheader('content-encoding', 'gzip')
101    self.instance.putheader('content-length', '10')
102    self.instance.putheader('content-range', 'bytes 0-99/100')
103    # Ensure the modifier is as expected.
104    self.assertAlmostEqual(self.instance.size_modifier, 10.0)
105
106  @mock.patch(https_connection)
107  def testHeaderParseFailure(self, mock_conn):
108    """Test incorrect header values do not raise exceptions."""
109    mock_conn.putheader.return_value = None
110    self.instance.putheader('content-encoding', 'gzip')
111    self.instance.putheader('content-length', 'bytes 10')
112    self.instance.putheader('content-range', 'not a number')
113    # Ensure the modifier is unchanged.
114    self.assertAlmostEqual(self.instance.size_modifier, 1.0)
115
116  @mock.patch('gslib.progress_callback.ProgressCallbackWithTimeout')
117  @mock.patch('httplib2.HTTPSConnectionWithTimeout')
118  def testSendDefaultBehavior(self, mock_conn, mock_callback):
119    mock_conn.send.return_value = None
120    self.instance.size_modifier = 2
121    self.instance.processed_initial_bytes = True
122    self.instance.callback_processor = mock_callback
123    # Send 10 bytes of data.
124    sample_data = b'0123456789'
125    self.instance.send(sample_data)
126    # Ensure the data is fully sent since the buffer size is 50 bytes.
127    self.assertTrue(mock_conn.send.called)
128    (_, sent_data), _ = mock_conn.send.call_args_list[0]
129    self.assertEqual(sent_data, sample_data)
130    # Ensure the progress callback is correctly scaled.
131    self.assertTrue(mock_callback.Progress.called)
132    [sent_bytes], _ = mock_callback.Progress.call_args_list[0]
133    self.assertEqual(sent_bytes, 20)
134