diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bc878e6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +funcsigs==1.0.2 +mock==2.0.0 +pbr==5.1.3 +six==1.12.0 diff --git a/sauceclient.py b/sauceclient.py index 9c3c2d1..108efdb 100644 --- a/sauceclient.py +++ b/sauceclient.py @@ -38,6 +38,8 @@ def __init__(self, *args, **kwargs): super(SauceException, self).__init__(*args) self.response = kwargs.get('response') +class PathException(Exception): + """SauceClient exception.""" class SauceClient(object): """SauceClient class.""" @@ -86,6 +88,32 @@ def request(self, method, url, body=None, content_type='application/json'): response.status, response.reason), response=response) return json.loads(data.decode('utf-8')) + def request_content(self, url, filename, dirpath=None,content_type=''): + """Send http request for asset content""" + headers = self.make_auth_headers(content_type) + connection = http_client.HTTPSConnection(self.apibase) + full_url = url + filename + connection.request('GET', full_url, headers=headers) + response = connection.getresponse() + data = response.read() + + if dirpath: + if os.path.exists(dirpath): + full_path = os.path.join(dirpath, filename) + with open(full_path, 'wb') as file_handle: + file_handle.write(data) + else: + raise PathException("Path does not exist") + else: + with open(filename, 'wb') as file_handle: + file_handle.write(data) + + connection.close() + if response.status not in [200, 201]: + raise SauceException('{}: {}.\nSauce Status NOT OK'.format( + response.status, response.reason), response=response) + return True + class Account(object): """Account Methods @@ -474,9 +502,16 @@ def get_job_assets(self, job_id): def get_job_asset_url(self, job_id, filename): """Get details about the static assets collected for a specific job.""" - return 'https://saucelabs.com/rest/v1/{}/jobs/{}/assets/{}'.format( + return 'https://{}/rest/v1/{}/jobs/{}/assets/{}'.format(self.client.apibase, self.client.sauce_username, job_id, filename) + def get_job_asset_content(self, job_id, filename, dirpath=None): + """Get content collected for a specific asset on a specific job.""" + endpoint = '/rest/v1/{}/jobs/{}/assets/'.format( + self.client.sauce_username, job_id) + + return self.client.request_content(endpoint,filename,dirpath) + def delete_job_assets(self, job_id): """Delete all the assets captured during a test run.""" method = 'DELETE' diff --git a/tests.py b/tests.py index daaeb2a..5d8c4c7 100644 --- a/tests.py +++ b/tests.py @@ -264,6 +264,41 @@ def test_jobs_get_job_assets(self, mocked): def test_jobs_get_job_asset_url(self, mocked): resp = self.sc.jobs.get_job_asset_url('job-id', '0000screenshot.jpg') self.assertIsInstance(resp, str) + self.assertEqual(resp,"https://saucelabs.com/rest/v1/sauce-username/jobs/job-id/assets/0000screenshot.jpg") + + @patch('sauceclient.os.path') + @patch('sauceclient.open', create=True) + def test_jobs_get_job_asset_content(self, mocked_open, mocked_path, mocked): + + mocked.return_value.status = 200 + mocked.return_value.reason = 'OK' + mocked.return_value.read.return_value = b'' + + mocked_path.path.exists.return_value = True + + mocked_open.read_data(b'aaa') + + resp = self.sc.jobs.get_job_asset_content('job-id',"0000screenshot.jpg") + self.assertTrue(resp) + + @patch('sauceclient.os.path') + @patch('sauceclient.open', create=True) + def test_jobs_get_job_asset_content_specific_dir(self, mocked_open, mocked_path, mocked): + mocked.return_value.status = 200 + mocked.return_value.reason = 'OK' + mocked.return_value.read.return_value = b'' + + mocked_path.path.exists.return_value = True + + mocked_open.read_data(b'aaa') + + resp = self.sc.jobs.get_job_asset_content('job-id',"0000screenshot.jpg","/path/to/somewhere") + self.assertTrue(resp) + + def test_jobs_get_job_asset_content_nonexistent_dir(self, mocked): + + with self.assertRaises(sauceclient.PathException): + self.sc.jobs.get_job_asset_content('job-id',"0000screenshot.jpg","/path/nowhere/") def test_jobs_delete_job_assets(self, mocked): mocked.return_value.status = 200