def setUp(self): self.bucket_name = 'my-bucket' self.simulator = RawSimulator() self.account_info = StubAccountInfo() self.api = B2Api(self.account_info, raw_api=self.simulator) (self.account_id, self.master_key) = self.simulator.create_account() self.api.authorize_account('production', self.account_id, self.master_key) self.api_url = self.account_info.get_api_url() self.account_auth_token = self.account_info.get_account_auth_token() self.bucket = self.api.create_bucket('my-bucket', 'allPublic') self.bucket_id = self.bucket.id_
def setUp(self): self.bucket_name = 'my-bucket' self.simulator = RawSimulator() self.account_info = StubAccountInfo() self.api = B2Api(self.account_info, raw_api=self.simulator) self.api.authorize_account('production', 'my-account', 'good-app-key') self.bucket = self.api.create_bucket('my-bucket', 'allPublic')
class TestCaseWithBucket(TestBase): def setUp(self): self.bucket_name = 'my-bucket' self.simulator = RawSimulator() self.account_info = StubAccountInfo() self.api = B2Api(self.account_info, raw_api=self.simulator) (self.account_id, self.master_key) = self.simulator.create_account() self.api.authorize_account('production', self.account_id, self.master_key) self.api_url = self.account_info.get_api_url() self.account_auth_token = self.account_info.get_account_auth_token() self.bucket = self.api.create_bucket('my-bucket', 'allPublic') self.bucket_id = self.bucket.id_ def assertBucketContents(self, expected, *args, **kwargs): """ *args and **kwargs are passed to self.bucket.ls() """ actual = [(info.file_name, info.size, info.action, folder) for (info, folder) in self.bucket.ls(*args, **kwargs)] self.assertEqual(expected, actual)
def setUp(self): self.account_info = StubAccountInfo() self.cache = InMemoryCache() self.raw_api = RawSimulator() self.b2_api = B2Api(self.account_info, self.cache, self.raw_api)
class TestConsoleTool(TestBase): def setUp(self): self.account_info = StubAccountInfo() self.cache = InMemoryCache() self.raw_api = RawSimulator() self.b2_api = B2Api(self.account_info, self.cache, self.raw_api) def test_authorize_with_bad_key(self): expected_stdout = ''' Using http://production.example.com ''' expected_stderr = ''' ERROR: unable to authorize account: Invalid authorization token. Server said: invalid application key: bad-app-key (bad_auth_token) ''' self._run_command(['authorize_account', 'my-account', 'bad-app-key'], expected_stdout, expected_stderr, 1) def test_authorize_with_good_key(self): # Initial condition assert self.account_info.get_account_auth_token() is None # Authorize an account with a good api key. expected_stdout = """ Using http://production.example.com """ self._run_command(['authorize_account', 'my-account', 'good-app-key'], expected_stdout, '', 0) # Auth token should be in account info now assert self.account_info.get_account_auth_token() is not None def test_help_with_bad_args(self): expected_stderr = ''' b2 create_bucket <bucketName> [allPublic | allPrivate] Creates a new bucket. Prints the ID of the bucket created. ''' self._run_command(['create_bucket'], '', expected_stderr, 1) def test_clear_account(self): # Initial condition self._authorize_account() assert self.account_info.get_account_auth_token() is not None # Clearing the account should remove the auth token # from the account info. self._run_command(['clear_account'], '', '', 0) assert self.account_info.get_account_auth_token() is None def test_buckets(self): self._authorize_account() # Make a bucket with an illegal name expected_stdout = 'ERROR: Bad request: illegal bucket name: bad/bucket/name\n' self._run_command(['create_bucket', 'bad/bucket/name', 'allPublic'], '', expected_stdout, 1) # Make two buckets self._run_command(['create_bucket', 'my-bucket', 'allPrivate'], 'bucket_0\n', '', 0) self._run_command(['create_bucket', 'your-bucket', 'allPrivate'], 'bucket_1\n', '', 0) # Update one of them expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketName": "my-bucket", "bucketType": "allPublic" } ''' self._run_command(['update_bucket', 'my-bucket', 'allPublic'], expected_stdout, '', 0) # Make sure they are there expected_stdout = ''' bucket_0 allPublic my-bucket bucket_1 allPrivate your-bucket ''' self._run_command(['list_buckets'], expected_stdout, '', 0) # Delete one expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_1", "bucketName": "your-bucket", "bucketType": "allPrivate" } ''' self._run_command(['delete_bucket', 'your-bucket'], expected_stdout, '', 0) def test_cancel_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file1', 'text/plain', {}) self._run_command(['cancel_large_file', file.file_id], '9999 canceled\n', '', 0) def test_cancel_all_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.start_large_file('file1', 'text/plain', {}) bucket.start_large_file('file2', 'text/plain', {}) expected_stdout = ''' 9999 canceled 9998 canceled ''' self._run_command(['cancel_all_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0) def test_files(self): self._authorize_account() self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) with TempDir() as temp_dir: local_file1 = self._make_local_file(temp_dir, 'file1.txt') # Upload a file expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/file1.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ''' self._run_command([ 'upload_file', '--noProgress', 'my-bucket', local_file1, 'file1.txt' ], expected_stdout, '', 0) # Download by name local_download1 = os.path.join(temp_dir, 'download1.txt') expected_stdout = ''' File name: file1.txt File id: 9999 File size: 11 Content type: b2/x-auto Content sha1: 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed checksum matches ''' self._run_command([ 'download_file_by_name', '--noProgress', 'my-bucket', 'file1.txt', local_download1 ], expected_stdout, '', 0) self.assertEquals(six.b('hello world'), self._read_file(local_download1)) # Download file by ID. (Same expected output as downloading by name) local_download2 = os.path.join(temp_dir, 'download2.txt') self._run_command([ 'download_file_by_id', '--noProgress', '9999', local_download2 ], expected_stdout, '', 0) self.assertEquals(six.b('hello world'), self._read_file(local_download2)) # Hide the file expected_stdout = ''' { "action": "hide", "fileId": "9998", "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 } ''' self._run_command(['hide_file', 'my-bucket', 'file1.txt'], expected_stdout, '', 0) # List the file versions expected_stdout = ''' { "files": [ { "action": "hide", "contentSha1": "none", "contentType": null, "fileId": "9998", "fileInfo": {}, "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 }, { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": {}, "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileId": null, "nextFileName": null } ''' self._run_command(['list_file_versions', 'my-bucket'], expected_stdout, '', 0) # List the file names expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # Delete one file version, passing the name in expected_stdout = ''' { "action": "delete", "fileId": "9998", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', 'file1.txt', '9998'], expected_stdout, '', 0) # Delete one file version, not passing the name in expected_stdout = ''' { "action": "delete", "fileId": "9999", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', '9999'], expected_stdout, '', 0) def test_list_parts_with_none(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) self._run_command(['list_parts', file.file_id], '', '', 0) def test_list_parts_with_parts(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) content = six.b('hello world') large_file_upload_state = mock.MagicMock() large_file_upload_state.has_error.return_value = False bucket._upload_part(file.file_id, 1, (0, 11), UploadSourceBytes(content), large_file_upload_state) bucket._upload_part(file.file_id, 3, (0, 11), UploadSourceBytes(content), large_file_upload_state) expected_stdout = ''' 1 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed 3 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed ''' self._run_command(['list_parts', file.file_id], expected_stdout, '', 0) def test_list_unfinished_large_files_with_none(self): self._authorize_account() self._create_my_bucket() self._run_command(['list_unfinished_large_files', 'my-bucket'], '', '', 0) def test_list_unfinished_large_files_with_some(self): self._authorize_account() self._create_my_bucket() api_url = self.account_info.get_api_url() auth_token = self.account_info.get_account_auth_token() self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file1', 'text/plain', {}) self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file2', 'text/plain', {'color': 'blue'}) self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file3', 'application/json', {}) expected_stdout = ''' 9999 file1 text/plain 9998 file2 text/plain color=blue 9997 file3 application/json ''' self._run_command(['list_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0) def test_upload_large_file(self): self._authorize_account() self._create_my_bucket() min_part_size = self.account_info.get_minimum_part_size() file_size = min_part_size * 3 with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') text = six.u('*') * file_size with open(file_path, 'wb') as f: f.write(text.encode('utf-8')) expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/test.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "test.txt", "size": 600, "uploadTimestamp": 5000 } ''' self._run_command([ 'upload_file', '--noProgress', '--threads', '5', 'my-bucket', file_path, 'test.txt' ], expected_stdout, '', 0) def test_sync(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') with open(file_path, 'wb') as f: f.write(six.u('hello world').encode('utf-8')) expected_stdout = ''' upload test.txt ''' command = [ 'sync', '--threads', '5', '--noProgress', temp_dir, 'b2://my-bucket' ] self._run_command(command, expected_stdout, '', 0) def test_sync_syntax_error(self): self._authorize_account() self._create_my_bucket() expected_stderr = 'ERROR: --includeRegex cannot be used without --excludeRegex at the same time\n' self._run_command([ 'sync', '--includeRegex', '.incl', 'non-existent-local-folder', 'b2://my-bucket' ], expected_stderr=expected_stderr, expected_status=1) def test_sync_dry_run(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: temp_file = self._make_local_file(temp_dir, 'test-dry-run.txt') # dry-run expected_stdout = ''' upload test-dry-run.txt ''' command = [ 'sync', '--noProgress', '--dryRun', temp_dir, 'b2://my-bucket' ] self._run_command(command, expected_stdout, '', 0) # file should not have been uploaded expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # upload file expected_stdout = ''' upload test-dry-run.txt ''' command = ['sync', '--noProgress', temp_dir, 'b2://my-bucket'] self._run_command(command, expected_stdout, '', 0) # file should have been uploaded mtime = file_mod_time_millis(temp_file) expected_stdout = ''' { "files": [ { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "%d" }, "fileName": "test-dry-run.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileName": null } ''' % (mtime) self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) def _authorize_account(self): """ Prepare for a test by authorizing an account and getting an account auth token """ self._run_command_no_checks( ['authorize_account', 'my-account', 'good-app-key']) def _create_my_bucket(self): self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) def _run_command(self, argv, expected_stdout='', expected_stderr='', expected_status=0): """ Runs one command using the ConsoleTool, checking stdout, stderr, and the returned status code. The ConsoleTool is stateless, so we can make a new one for each call, with a fresh stdout and stderr """ expected_stdout = self._trim_leading_spaces(expected_stdout) expected_stderr = self._trim_leading_spaces(expected_stderr) stdout = six.StringIO() stderr = six.StringIO() console_tool = ConsoleTool(self.b2_api, stdout, stderr) actual_status = console_tool.run_command(['b2'] + argv) # The json module in Python 2.6 includes trailing spaces. Later version of Python don't. actual_stdout = self._trim_trailing_spaces(stdout.getvalue()) actual_stderr = self._trim_trailing_spaces(stderr.getvalue()) if expected_stdout != actual_stdout: print(repr(expected_stdout)) print(repr(actual_stdout)) if expected_stderr != actual_stderr: print(repr(expected_stderr)) print(repr(actual_stderr)) self.assertEqual(expected_stdout, actual_stdout, 'stdout') self.assertEqual(expected_stderr, actual_stderr, 'stderr') self.assertEqual(expected_status, actual_status, 'exit status code') def _run_command_no_checks(self, argv): ConsoleTool(self.b2_api, six.StringIO(), six.StringIO()).run_command(['b2'] + argv) def _trim_leading_spaces(self, s): """ Takes the contents of a triple-quoted string, and removes the leading newline and leading spaces that come from it being indented with code. """ # The first line starts on the line following the triple # quote, so the first line after splitting can be discarded. lines = s.split('\n') if lines[0] == '': lines = lines[1:] if len(lines) == 0: return '' # Count the leading spaces space_count = min( self._leading_spaces(line) for line in lines if line != '') # Remove the leading spaces from each line, based on the line # with the fewest leading spaces leading_spaces = ' ' * space_count assert all( line.startswith(leading_spaces) or line == '' for line in lines), 'all lines have leading spaces' return '\n'.join('' if line == '' else line[space_count:] for line in lines) def _leading_spaces(self, s): space_count = 0 while space_count < len(s) and s[space_count] == ' ': space_count += 1 return space_count def _trim_trailing_spaces(self, s): return '\n'.join(line.rstrip() for line in s.split('\n')) def _make_local_file(self, temp_dir, file_name): local_path = os.path.join(temp_dir, file_name) with open(local_path, 'wb') as f: f.write(six.b('hello world')) return local_path def _read_file(self, local_path): with open(local_path, 'rb') as f: return f.read()
class TestConsoleTool(TestBase): def setUp(self): self.account_info = StubAccountInfo() self.cache = InMemoryCache() self.raw_api = RawSimulator() self.b2_api = B2Api(self.account_info, self.cache, self.raw_api) def test_authorize_with_bad_key(self): expected_stdout = ''' Using http://production.example.com ''' expected_stderr = ''' ERROR: unable to authorize account: Invalid authorization token. Server said: invalid application key: bad-app-key (bad_auth_token) ''' self._run_command(['authorize_account', 'my-account', 'bad-app-key'], expected_stdout, expected_stderr, 1) def test_authorize_with_good_key_using_hyphen(self): # Initial condition assert self.account_info.get_account_auth_token() is None # Authorize an account with a good api key. expected_stdout = """ Using http://production.example.com """ self._run_command(['authorize-account', 'my-account', 'good-app-key'], expected_stdout, '', 0) # Auth token should be in account info now assert self.account_info.get_account_auth_token() is not None def test_authorize_with_good_key_using_underscore(self): # Initial condition assert self.account_info.get_account_auth_token() is None # Authorize an account with a good api key. expected_stdout = """ Using http://production.example.com """ self._run_command(['authorize-account', 'my-account', 'good-app-key'], expected_stdout, '', 0) # Auth token should be in account info now assert self.account_info.get_account_auth_token() is not None def test_help_with_bad_args(self): expected_stderr = ''' b2 list-parts <largeFileId> Lists all of the parts that have been uploaded for the given large file, which must be a file that was started but not finished or canceled. ''' self._run_command(['list_parts'], '', expected_stderr, 1) def test_clear_account(self): # Initial condition self._authorize_account() assert self.account_info.get_account_auth_token() is not None # Clearing the account should remove the auth token # from the account info. self._run_command(['clear-account'], '', '', 0) assert self.account_info.get_account_auth_token() is None def test_buckets(self): self._authorize_account() # Make a bucket with an illegal name expected_stdout = 'ERROR: Bad request: illegal bucket name: bad/bucket/name\n' self._run_command(['create_bucket', 'bad/bucket/name', 'allPublic'], '', expected_stdout, 1) # Make two buckets self._run_command(['create_bucket', 'my-bucket', 'allPrivate'], 'bucket_0\n', '', 0) self._run_command(['create_bucket', 'your-bucket', 'allPrivate'], 'bucket_1\n', '', 0) # Update one of them expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "lifecycleRules": [], "revision": 2 } ''' self._run_command(['update_bucket', 'my-bucket', 'allPublic'], expected_stdout, '', 0) # Make sure they are there expected_stdout = ''' bucket_0 allPublic my-bucket bucket_1 allPrivate your-bucket ''' self._run_command(['list_buckets'], expected_stdout, '', 0) # Delete one expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_1", "bucketInfo": {}, "bucketName": "your-bucket", "bucketType": "allPrivate", "corsRules": [], "lifecycleRules": [], "revision": 1 } ''' self._run_command(['delete_bucket', 'your-bucket'], expected_stdout, '', 0) def test_bucket_info_from_json(self): self._authorize_account() self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) bucket_info = {'color': 'blue'} expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": { "color": "blue" }, "bucketName": "my-bucket", "bucketType": "allPrivate", "corsRules": [], "lifecycleRules": [], "revision": 2 } ''' self._run_command([ 'update_bucket', '--bucketInfo', json.dumps(bucket_info), 'my-bucket', 'allPrivate' ], expected_stdout, '', 0) def test_cancel_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file1', 'text/plain', {}) self._run_command(['cancel_large_file', file.file_id], '9999 canceled\n', '', 0) def test_cancel_all_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.start_large_file('file1', 'text/plain', {}) bucket.start_large_file('file2', 'text/plain', {}) expected_stdout = ''' 9999 canceled 9998 canceled ''' self._run_command(['cancel_all_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0) def test_files(self): self._authorize_account() self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) with TempDir() as temp_dir: local_file1 = self._make_local_file(temp_dir, 'file1.txt') # For this test, use a mod time without millis. My mac truncates # millis and just leaves seconds. mod_time = 1500111222 os.utime(local_file1, (mod_time, mod_time)) self.assertEqual(1500111222, os.path.getmtime(local_file1)) # Upload a file expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/file1.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ''' self._run_command([ 'upload_file', '--noProgress', 'my-bucket', local_file1, 'file1.txt' ], expected_stdout, '', 0) # Get file info mod_time_str = str(int(os.path.getmtime(local_file1) * 1000)) expected_stdout = ''' { "accountId": "my-account", "action": "upload", "bucketId": "bucket_0", "contentLength": 11, "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "1500111222000" }, "fileName": "file1.txt", "uploadTimestamp": 5000 } ''' self._run_command(['get_file_info', '9999'], expected_stdout, '', 0) # Download by name local_download1 = os.path.join(temp_dir, 'download1.txt') expected_stdout = ''' File name: file1.txt File id: 9999 File size: 11 Content type: b2/x-auto Content sha1: 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed INFO src_last_modified_millis: 1500111222000 checksum matches ''' self._run_command([ 'download_file_by_name', '--noProgress', 'my-bucket', 'file1.txt', local_download1 ], expected_stdout, '', 0) self.assertEquals(six.b('hello world'), self._read_file(local_download1)) self.assertEquals(mod_time, os.path.getmtime(local_download1)) # Download file by ID. (Same expected output as downloading by name) local_download2 = os.path.join(temp_dir, 'download2.txt') self._run_command([ 'download_file_by_id', '--noProgress', '9999', local_download2 ], expected_stdout, '', 0) self.assertEquals(six.b('hello world'), self._read_file(local_download2)) # Hide the file expected_stdout = ''' { "action": "hide", "fileId": "9998", "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 } ''' self._run_command(['hide_file', 'my-bucket', 'file1.txt'], expected_stdout, '', 0) # List the file versions expected_stdout = ''' { "files": [ { "action": "hide", "contentSha1": "none", "contentType": null, "fileId": "9998", "fileInfo": {}, "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 }, { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "%s" }, "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileId": null, "nextFileName": null } ''' % (mod_time_str, ) self._run_command(['list_file_versions', 'my-bucket'], expected_stdout, '', 0) # List the file names expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # Delete one file version, passing the name in expected_stdout = ''' { "action": "delete", "fileId": "9998", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', 'file1.txt', '9998'], expected_stdout, '', 0) # Delete one file version, not passing the name in expected_stdout = ''' { "action": "delete", "fileId": "9999", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', '9999'], expected_stdout, '', 0) def test_get_download_auth_defaults(self): self._authorize_account() self._create_my_bucket() self._run_command(['get_download_auth', 'my-bucket'], 'fake_download_auth_token_bucket_0__86400\n', '', 0) def test_get_download_auth_explicit(self): self._authorize_account() self._create_my_bucket() self._run_command([ 'get_download_auth', '--prefix', 'prefix', '--duration', '12345', 'my-bucket' ], 'fake_download_auth_token_bucket_0_prefix_12345\n', '', 0) def test_get_download_auth_url(self): self._authorize_account() self._create_my_bucket() self._run_command([ 'get-download-url-with-auth', '--duration', '12345', 'my-bucket', 'my-file' ], 'http://download.example.com/file/my-bucket/my-file?Authorization=fake_download_auth_token_bucket_0_my-file_12345\n', '', 0) def test_get_download_auth_url_with_encoding(self): self._authorize_account() self._create_my_bucket() self._run_command([ 'get-download-url-with-auth', '--duration', '12345', 'my-bucket', u'\u81ea' ], u'http://download.example.com/file/my-bucket/%E8%87%AA?Authorization=fake_download_auth_token_bucket_0_%E8%87%AA_12345\n', '', 0) def test_list_parts_with_none(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) self._run_command(['list_parts', file.file_id], '', '', 0) def test_list_parts_with_parts(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) content = six.b('hello world') large_file_upload_state = mock.MagicMock() large_file_upload_state.has_error.return_value = False bucket._upload_part(file.file_id, 1, (0, 11), UploadSourceBytes(content), large_file_upload_state) bucket._upload_part(file.file_id, 3, (0, 11), UploadSourceBytes(content), large_file_upload_state) expected_stdout = ''' 1 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed 3 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed ''' self._run_command(['list_parts', file.file_id], expected_stdout, '', 0) def test_list_unfinished_large_files_with_none(self): self._authorize_account() self._create_my_bucket() self._run_command(['list_unfinished_large_files', 'my-bucket'], '', '', 0) def test_list_unfinished_large_files_with_some(self): self._authorize_account() self._create_my_bucket() api_url = self.account_info.get_api_url() auth_token = self.account_info.get_account_auth_token() self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file1', 'text/plain', {}) self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file2', 'text/plain', {'color': 'blue'}) self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file3', 'application/json', {}) expected_stdout = ''' 9999 file1 text/plain 9998 file2 text/plain color=blue 9997 file3 application/json ''' self._run_command(['list_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0) def test_upload_large_file(self): self._authorize_account() self._create_my_bucket() min_part_size = self.account_info.get_minimum_part_size() file_size = min_part_size * 3 with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') text = six.u('*') * file_size with open(file_path, 'wb') as f: f.write(text.encode('utf-8')) expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/test.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "test.txt", "size": 600, "uploadTimestamp": 5000 } ''' self._run_command([ 'upload_file', '--noProgress', '--threads', '5', 'my-bucket', file_path, 'test.txt' ], expected_stdout, '', 0) def test_get_account_info(self): self._authorize_account() expected_stdout = ''' { "accountAuthToken": "AUTH:my-account", "accountId": "my-account", "apiUrl": "http://api.example.com", "applicationKey": "good-app-key", "downloadUrl": "http://download.example.com" } ''' self._run_command(['get-account-info'], expected_stdout, '', 0) def test_get_bucket(self): self._authorize_account() self._create_my_bucket() expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "lifecycleRules": [], "revision": 1 } ''' self._run_command(['get-bucket', 'my-bucket'], expected_stdout, '', 0) def test_get_bucket_empty_show_size(self): self._authorize_account() self._create_my_bucket() expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "fileCount": 0, "lifecycleRules": [], "revision": 1, "totalSize": 0 } ''' self._run_command(['get-bucket', '--showSize', 'my-bucket'], expected_stdout, '', 0) def test_get_bucket_one_item_show_size(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: # Upload a standard test file. local_file1 = self._make_local_file(temp_dir, 'file1.txt') expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/file1.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ''' self._run_command([ 'upload_file', '--noProgress', 'my-bucket', local_file1, 'file1.txt' ], expected_stdout, '', 0) # Now check the output of get-bucket against the canon. expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "fileCount": 1, "lifecycleRules": [], "revision": 1, "totalSize": 11 } ''' self._run_command(['get-bucket', '--showSize', 'my-bucket'], expected_stdout, '', 0) def test_get_bucket_with_versions(self): self._authorize_account() self._create_my_bucket() # Put many versions of a file into the test bucket. Unroll the loop here for convenience. bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') # Now check the output of get-bucket against the canon. expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "fileCount": 10, "lifecycleRules": [], "revision": 1, "totalSize": 40 } ''' self._run_command(['get-bucket', '--showSize', 'my-bucket'], expected_stdout, '', 0) def test_get_bucket_with_folders(self): self._authorize_account() self._create_my_bucket() # Create a hierarchical structure within the test bucket. Unroll the loop here for # convenience. bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), '1/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/4/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/4/5/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/4/5/6/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/4/5/6/7/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/4/5/6/7/8/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/4/5/6/7/8/9/test') bucket.upload(UploadSourceBytes(b'check'), 'check') bucket.upload(UploadSourceBytes(b'check'), '1/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/5/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/5/6/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/5/6/7/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/5/6/7/8/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/5/6/7/8/9/check') # Now check the output of get-bucket against the canon. expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "fileCount": 20, "lifecycleRules": [], "revision": 1, "totalSize": 90 } ''' self._run_command(['get-bucket', '--showSize', 'my-bucket'], expected_stdout, '', 0) def test_get_bucket_with_hidden(self): self._authorize_account() self._create_my_bucket() # Put some files into the test bucket. Unroll the loop for convenience. bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.upload(UploadSourceBytes(b'test'), 'upload1') bucket.upload(UploadSourceBytes(b'test'), 'upload2') bucket.upload(UploadSourceBytes(b'test'), 'upload3') bucket.upload(UploadSourceBytes(b'test'), 'upload4') bucket.upload(UploadSourceBytes(b'test'), 'upload5') bucket.upload(UploadSourceBytes(b'test'), 'upload6') # Hide some new files. Don't check the results here; it will be clear enough that # something has failed if the output of 'get-bucket' does not match the canon. stdout, stderr = self._get_stdouterr() console_tool = ConsoleTool(self.b2_api, stdout, stderr) console_tool.run_command(['b2', 'hide_file', 'my-bucket', 'hidden1']) console_tool.run_command(['b2', 'hide_file', 'my-bucket', 'hidden2']) console_tool.run_command(['b2', 'hide_file', 'my-bucket', 'hidden3']) console_tool.run_command(['b2', 'hide_file', 'my-bucket', 'hidden4']) # Now check the output of get-bucket against the canon. expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "fileCount": 10, "lifecycleRules": [], "revision": 1, "totalSize": 24 } ''' self._run_command(['get-bucket', '--showSize', 'my-bucket'], expected_stdout, '', 0) def test_get_bucket_complex(self): self._authorize_account() self._create_my_bucket() # Create a hierarchical structure within the test bucket. Unroll the loop here for # convenience. bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), 'test') bucket.upload(UploadSourceBytes(b'test'), '1/test') bucket.upload(UploadSourceBytes(b'test'), '1/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/test') bucket.upload(UploadSourceBytes(b'test'), '1/2/3/test') bucket.upload(UploadSourceBytes(b'check'), 'check') bucket.upload(UploadSourceBytes(b'check'), 'check') bucket.upload(UploadSourceBytes(b'check'), '1/check') bucket.upload(UploadSourceBytes(b'check'), '1/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/check') bucket.upload(UploadSourceBytes(b'check'), '1/2/3/4/check') # Hide some new files. Don't check the results here; it will be clear enough that # something has failed if the output of 'get-bucket' does not match the canon. stdout, stderr = self._get_stdouterr() console_tool = ConsoleTool(self.b2_api, stdout, stderr) console_tool.run_command(['b2', 'hide_file', 'my-bucket', '1/hidden1']) console_tool.run_command(['b2', 'hide_file', 'my-bucket', '1/hidden1']) console_tool.run_command(['b2', 'hide_file', 'my-bucket', '1/hidden2']) console_tool.run_command( ['b2', 'hide_file', 'my-bucket', '1/2/hidden3']) console_tool.run_command( ['b2', 'hide_file', 'my-bucket', '1/2/hidden3']) console_tool.run_command( ['b2', 'hide_file', 'my-bucket', '1/2/hidden3']) console_tool.run_command( ['b2', 'hide_file', 'my-bucket', '1/2/hidden3']) # Now check the output of get-bucket against the canon. expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "corsRules": [], "fileCount": 29, "lifecycleRules": [], "revision": 1, "totalSize": 99 } ''' self._run_command(['get-bucket', '--showSize', 'my-bucket'], expected_stdout, '', 0) def test_sync(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') with open(file_path, 'wb') as f: f.write(six.u('hello world').encode('utf-8')) expected_stdout = ''' upload test.txt ''' command = [ 'sync', '--threads', '5', '--noProgress', temp_dir, 'b2://my-bucket' ] self._run_command(command, expected_stdout, '', 0) def test_sync_empty_folder_when_not_enabled(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: command = [ 'sync', '--threads', '1', '--noProgress', temp_dir, 'b2://my-bucket' ] expected_stderr = 'ERROR: Directory %s is empty. Use --allowEmptySource to sync anyway.\n' % temp_dir self._run_command(command, '', expected_stderr, 1) def test_sync_empty_folder_when_enabled(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: command = [ 'sync', '--threads', '1', '--noProgress', '--allowEmptySource', temp_dir, 'b2://my-bucket' ] self._run_command(command, '', '', 0) def test_sync_syntax_error(self): self._authorize_account() self._create_my_bucket() expected_stderr = 'ERROR: --includeRegex cannot be used without --excludeRegex at the same time\n' self._run_command([ 'sync', '--includeRegex', '.incl', 'non-existent-local-folder', 'b2://my-bucket' ], expected_stderr=expected_stderr, expected_status=1) def test_sync_dry_run(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: temp_file = self._make_local_file(temp_dir, 'test-dry-run.txt') # dry-run expected_stdout = ''' upload test-dry-run.txt ''' command = [ 'sync', '--noProgress', '--dryRun', temp_dir, 'b2://my-bucket' ] self._run_command(command, expected_stdout, '', 0) # file should not have been uploaded expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # upload file expected_stdout = ''' upload test-dry-run.txt ''' command = ['sync', '--noProgress', temp_dir, 'b2://my-bucket'] self._run_command(command, expected_stdout, '', 0) # file should have been uploaded mtime = file_mod_time_millis(temp_file) expected_stdout = ''' { "files": [ { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "%d" }, "fileName": "test-dry-run.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileName": null } ''' % (mtime) self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) def test_ls(self): self._authorize_account() self._create_my_bucket() # Check with no files self._run_command(['ls', 'my-bucket'], '', '', 0) # Create some files, including files in a folder bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.upload(UploadSourceBytes(b''), 'a') bucket.upload(UploadSourceBytes(b' '), 'b/b1') bucket.upload(UploadSourceBytes(b' '), 'b/b2') bucket.upload(UploadSourceBytes(b' '), 'c') bucket.upload(UploadSourceBytes(b' '), 'c') # Condensed output expected_stdout = ''' a b/ c ''' self._run_command(['ls', 'my-bucket'], expected_stdout, '', 0) # Recursive output expected_stdout = ''' a b/b1 b/b2 c ''' self._run_command(['ls', '--recursive', 'my-bucket'], expected_stdout, '', 0) # Check long output. (The format expects full-length file ids, so it causes whitespace here) expected_stdout = ''' 9999 upload 1970-01-01 00:00:05 0 a - - - - 0 b/ 9995 upload 1970-01-01 00:00:05 6 c ''' self._run_command(['ls', '--long', 'my-bucket'], expected_stdout, '', 0) # Check long versions output (The format expects full-length file ids, so it causes whitespace here) expected_stdout = ''' 9999 upload 1970-01-01 00:00:05 0 a - - - - 0 b/ 9995 upload 1970-01-01 00:00:05 6 c 9996 upload 1970-01-01 00:00:05 5 c ''' self._run_command(['ls', '--long', '--versions', 'my-bucket'], expected_stdout, '', 0) def _authorize_account(self): """ Prepare for a test by authorizing an account and getting an account auth token """ self._run_command_no_checks( ['authorize_account', 'my-account', 'good-app-key']) def _create_my_bucket(self): self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) def _run_command(self, argv, expected_stdout='', expected_stderr='', expected_status=0): """ Runs one command using the ConsoleTool, checking stdout, stderr, and the returned status code. The ConsoleTool is stateless, so we can make a new one for each call, with a fresh stdout and stderr """ expected_stdout = self._trim_leading_spaces(expected_stdout) expected_stderr = self._trim_leading_spaces(expected_stderr) stdout, stderr = self._get_stdouterr() console_tool = ConsoleTool(self.b2_api, stdout, stderr) actual_status = console_tool.run_command(['b2'] + argv) # The json module in Python 2.6 includes trailing spaces. Later version of Python don't. actual_stdout = self._trim_trailing_spaces(stdout.getvalue()) actual_stderr = self._trim_trailing_spaces(stderr.getvalue()) if expected_stdout != actual_stdout: print('EXPECTED STDOUT:', repr(expected_stdout)) print('ACTUAL STDOUT: ', repr(actual_stdout)) if expected_stderr != actual_stderr: print('EXPECTED STDERR:', repr(expected_stderr)) print('ACTUAL STDERR: ', repr(actual_stderr)) self.assertEqual(expected_stdout, actual_stdout, 'stdout') self.assertEqual(expected_stderr, actual_stderr, 'stderr') self.assertEqual(expected_status, actual_status, 'exit status code') def test_bad_terminal(self): stdout = mock.MagicMock() stdout.write = mock.MagicMock(side_effect=[ UnicodeEncodeError('codec', u'foo', 100, 105, 'artificial UnicodeEncodeError') ] + list(range(25))) stderr = mock.MagicMock() console_tool = ConsoleTool(self.b2_api, stdout, stderr) console_tool.run_command( ['b2', 'authorize_account', 'my-account', 'good-app-key']) def _get_stdouterr(self): class MyStringIO(six.StringIO): if six.PY2: # python3 already has this attribute encoding = 'fake_encoding' stdout = MyStringIO() stderr = MyStringIO() return stdout, stderr def _run_command_no_checks(self, argv): stdout, stderr = self._get_stdouterr() ConsoleTool(self.b2_api, stdout, stderr).run_command(['b2'] + argv) def _trim_leading_spaces(self, s): """ Takes the contents of a triple-quoted string, and removes the leading newline and leading spaces that come from it being indented with code. """ # The first line starts on the line following the triple # quote, so the first line after splitting can be discarded. lines = s.split('\n') if lines[0] == '': lines = lines[1:] if len(lines) == 0: return '' # Count the leading spaces space_count = min( self._leading_spaces(line) for line in lines if line != '') # Remove the leading spaces from each line, based on the line # with the fewest leading spaces leading_spaces = ' ' * space_count assert all( line.startswith(leading_spaces) or line == '' for line in lines), 'all lines have leading spaces' return '\n'.join('' if line == '' else line[space_count:] for line in lines) def _leading_spaces(self, s): space_count = 0 while space_count < len(s) and s[space_count] == ' ': space_count += 1 return space_count def _trim_trailing_spaces(self, s): return '\n'.join(line.rstrip() for line in s.split('\n')) def _make_local_file(self, temp_dir, file_name): local_path = os.path.join(temp_dir, file_name) with open(local_path, 'wb') as f: f.write(six.b('hello world')) return local_path def _read_file(self, local_path): with open(local_path, 'rb') as f: return f.read()
def setUp(self): self.account_info = InMemoryAccountInfo() self.cache = DummyCache() self.raw_api = RawSimulator() self.api = B2Api(self.account_info, self.cache, self.raw_api) (self.account_id, self.master_key) = self.raw_api.create_account()
class TestApi(TestBase): def setUp(self): self.account_info = InMemoryAccountInfo() self.cache = DummyCache() self.raw_api = RawSimulator() self.api = B2Api(self.account_info, self.cache, self.raw_api) (self.account_id, self.master_key) = self.raw_api.create_account() def test_list_buckets(self): self._authorize_account() self.api.create_bucket('bucket1', 'allPrivate') self.api.create_bucket('bucket2', 'allPrivate') self.assertEqual( ['bucket1', 'bucket2'], [b.name for b in self.api.list_buckets()], ) def test_list_buckets_with_name(self): self._authorize_account() self.api.create_bucket('bucket1', 'allPrivate') self.api.create_bucket('bucket2', 'allPrivate') self.assertEqual( ['bucket1'], [b.name for b in self.api.list_buckets(bucket_name='bucket1')], ) def test_list_buckets_with_restriction(self): self._authorize_account() bucket1 = self.api.create_bucket('bucket1', 'allPrivate') self.api.create_bucket('bucket2', 'allPrivate') key = self.api.create_key(['listBuckets'], 'key1', bucket_id=bucket1.id_) self.api.authorize_account('production', key['applicationKeyId'], key['applicationKey']) self.assertEqual( ['bucket1'], [b.name for b in self.api.list_buckets(bucket_name=bucket1.name)], ) def test_get_bucket_by_name_with_bucket_restriction(self): self._authorize_account() bucket1 = self.api.create_bucket('bucket1', 'allPrivate') key = self.api.create_key(['listBuckets'], 'key1', bucket_id=bucket1.id_) self.api.authorize_account('production', key['applicationKeyId'], key['applicationKey']) self.assertEqual( bucket1.id_, self.api.get_bucket_by_name('bucket1').id_, ) def test_list_buckets_with_restriction_and_wrong_name(self): self._authorize_account() bucket1 = self.api.create_bucket('bucket1', 'allPrivate') bucket2 = self.api.create_bucket('bucket2', 'allPrivate') key = self.api.create_key(['listBuckets'], 'key1', bucket_id=bucket1.id_) self.api.authorize_account('production', key['applicationKeyId'], key['applicationKey']) with self.assertRaises(RestrictedBucket): self.api.list_buckets(bucket_name=bucket2.name) def test_list_buckets_with_restriction_and_no_name(self): self._authorize_account() bucket1 = self.api.create_bucket('bucket1', 'allPrivate') self.api.create_bucket('bucket2', 'allPrivate') key = self.api.create_key(['listBuckets'], 'key1', bucket_id=bucket1.id_) self.api.authorize_account('production', key['applicationKeyId'], key['applicationKey']) with self.assertRaises(RestrictedBucket): self.api.list_buckets() def _authorize_account(self): self.api.authorize_account('production', self.account_id, self.master_key)
class TestConsoleTool(unittest.TestCase): def setUp(self): self.account_info = StubAccountInfo() self.cache = InMemoryCache() self.raw_api = RawSimulator() self.b2_api = B2Api(self.account_info, self.cache, self.raw_api) def test_authorize_with_bad_key(self): expected_stdout = ''' Using http://production.example.com ''' expected_stderr = ''' ERROR: unable to authorize account: Invalid authorization token. Server said: invalid application key: bad-app-key (bad_auth_token) ''' self._run_command( ['authorize_account', 'my-account', 'bad-app-key'], expected_stdout, expected_stderr, 1 ) def test_authorize_with_good_key(self): # Initial condition assert self.account_info.get_account_auth_token() is None # Authorize an account with a good api key. expected_stdout = """ Using http://production.example.com """ self._run_command( ['authorize_account', 'my-account', 'good-app-key'], expected_stdout, '', 0 ) # Auth token should be in account info now assert self.account_info.get_account_auth_token() is not None def test_help_with_bad_args(self): expected_stderr = ''' b2 create_bucket <bucketName> [allPublic | allPrivate] Creates a new bucket. Prints the ID of the bucket created. ''' self._run_command(['create_bucket'], '', expected_stderr, 1) def test_clear_account(self): # Initial condition self._authorize_account() assert self.account_info.get_account_auth_token() is not None # Clearing the account should remove the auth token # from the account info. self._run_command(['clear_account'], '', '', 0) assert self.account_info.get_account_auth_token() is None def test_buckets(self): self._authorize_account() # Make a bucket with an illegal name expected_stdout = 'ERROR: Bad request: illegal bucket name: bad/bucket/name\n' self._run_command(['create_bucket', 'bad/bucket/name', 'allPublic'], '', expected_stdout, 1) # Make two buckets self._run_command(['create_bucket', 'my-bucket', 'allPrivate'], 'bucket_0\n', '', 0) self._run_command(['create_bucket', 'your-bucket', 'allPrivate'], 'bucket_1\n', '', 0) # Update one of them expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketName": "my-bucket", "bucketType": "allPublic" } ''' self._run_command(['update_bucket', 'my-bucket', 'allPublic'], expected_stdout, '', 0) # Make sure they are there expected_stdout = ''' bucket_0 allPublic my-bucket bucket_1 allPrivate your-bucket ''' self._run_command(['list_buckets'], expected_stdout, '', 0) # Delete one expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_1", "bucketName": "your-bucket", "bucketType": "allPrivate" } ''' self._run_command(['delete_bucket', 'your-bucket'], expected_stdout, '', 0) def test_cancel_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file1', 'text/plain', {}) self._run_command(['cancel_large_file', file.file_id], '9999 canceled\n', '', 0) def test_cancel_all_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.start_large_file('file1', 'text/plain', {}) bucket.start_large_file('file2', 'text/plain', {}) expected_stdout = ''' 9999 canceled 9998 canceled ''' self._run_command( ['cancel_all_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0 ) def test_files(self): self._authorize_account() self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) with TempDir() as temp_dir: local_file1 = self._make_local_file(temp_dir, 'file1.txt') # Upload a file expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/file1.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ''' self._run_command( [ 'upload_file', '--noProgress', 'my-bucket', local_file1, 'file1.txt' ], expected_stdout, '', 0 ) # Download by name local_download1 = os.path.join(temp_dir, 'download1.txt') expected_stdout = ''' File name: file1.txt File id: 9999 File size: 11 Content type: b2/x-auto Content sha1: 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed checksum matches ''' self._run_command( [ 'download_file_by_name', '--noProgress', 'my-bucket', 'file1.txt', local_download1 ], expected_stdout, '', 0 ) self.assertEquals(six.b('hello world'), self._read_file(local_download1)) # Download file by ID. (Same expected output as downloading by name) local_download2 = os.path.join(temp_dir, 'download2.txt') self._run_command( [ 'download_file_by_id', '--noProgress', '9999', local_download2 ], expected_stdout, '', 0 ) self.assertEquals(six.b('hello world'), self._read_file(local_download2)) # Hide the file expected_stdout = ''' { "action": "hide", "fileId": "9998", "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 } ''' self._run_command(['hide_file', 'my-bucket', 'file1.txt'], expected_stdout, '', 0) # List the file versions expected_stdout = ''' { "files": [ { "action": "hide", "contentSha1": "none", "contentType": null, "fileId": "9998", "fileInfo": {}, "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 }, { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": {}, "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileId": null, "nextFileName": null } ''' self._run_command(['list_file_versions', 'my-bucket'], expected_stdout, '', 0) # List the file names expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # Delete one file version expected_stdout = ''' { "action": "delete", "fileId": "9998", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', 'file1.txt', '9998'], expected_stdout, '', 0) def test_list_parts_with_none(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) self._run_command(['list_parts', file.file_id], '', '', 0) def test_list_parts_with_parts(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) content = six.b('hello world') large_file_upload_state = mock.MagicMock() large_file_upload_state.has_error.return_value = False bucket._upload_part( file.file_id, 1, (0, 11), UploadSourceBytes(content), large_file_upload_state ) bucket._upload_part( file.file_id, 3, (0, 11), UploadSourceBytes(content), large_file_upload_state ) expected_stdout = ''' 1 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed 3 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed ''' self._run_command(['list_parts', file.file_id], expected_stdout, '', 0) def test_list_unfinished_large_files_with_none(self): self._authorize_account() self._create_my_bucket() self._run_command(['list_unfinished_large_files', 'my-bucket'], '', '', 0) def test_list_unfinished_large_files_with_some(self): self._authorize_account() self._create_my_bucket() api_url = self.account_info.get_api_url() auth_token = self.account_info.get_account_auth_token() self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file1', 'text/plain', {}) self.raw_api.start_large_file( api_url, auth_token, 'bucket_0', 'file2', 'text/plain', {'color': 'blue'} ) self.raw_api.start_large_file( api_url, auth_token, 'bucket_0', 'file3', 'application/json', {} ) expected_stdout = ''' 9999 file1 text/plain 9998 file2 text/plain color=blue 9997 file3 application/json ''' self._run_command(['list_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0) def test_upload_large_file(self): self._authorize_account() self._create_my_bucket() min_part_size = self.account_info.get_minimum_part_size() file_size = min_part_size * 3 with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') text = six.u('*') * file_size with open(file_path, 'wb') as f: f.write(text.encode('utf-8')) expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/test.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "test.txt", "size": 600, "uploadTimestamp": 5000 } ''' self._run_command( [ 'upload_file', '--noProgress', '--threads', '5', 'my-bucket', file_path, 'test.txt' ], expected_stdout, '', 0 ) def test_sync(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') with open(file_path, 'wb') as f: f.write(six.u('hello world').encode('utf-8')) expected_stdout = ''' upload test.txt ''' command = ['sync', '--threads', '5', '--noProgress', temp_dir, 'b2://my-bucket'] self._run_command(command, expected_stdout, '', 0) def _authorize_account(self): """ Prepare for a test by authorizing an account and getting an account auth token """ self._run_command_no_checks(['authorize_account', 'my-account', 'good-app-key']) def _create_my_bucket(self): self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) def _run_command(self, argv, expected_stdout='', expected_stderr='', expected_status=0): """ Runs one command using the ConsoleTool, checking stdout, stderr, and the returned status code. The ConsoleTool is stateless, so we can make a new one for each call, with a fresh stdout and stderr """ expected_stdout = self._trim_leading_spaces(expected_stdout) expected_stderr = self._trim_leading_spaces(expected_stderr) stdout = six.StringIO() stderr = six.StringIO() console_tool = ConsoleTool(self.b2_api, stdout, stderr) actual_status = console_tool.run_command(['b2'] + argv) # The json module in Python 2.6 includes trailing spaces. Later version of Python don't. actual_stdout = self._trim_trailing_spaces(stdout.getvalue()) actual_stderr = self._trim_trailing_spaces(stderr.getvalue()) if expected_stdout != actual_stdout: print(repr(expected_stdout)) print(repr(actual_stdout)) if expected_stderr != actual_stderr: print(repr(expected_stderr)) print(repr(actual_stderr)) self.assertEqual(expected_stdout, actual_stdout, 'stdout') self.assertEqual(expected_stderr, actual_stderr, 'stderr') self.assertEqual(expected_status, actual_status, 'exit status code') def _run_command_no_checks(self, argv): ConsoleTool(self.b2_api, six.StringIO(), six.StringIO()).run_command(['b2'] + argv) def _trim_leading_spaces(self, s): """ Takes the contents of a triple-quoted string, and removes the leading newline and leading spaces that come from it being indented with code. """ # The first line starts on the line following the triple # quote, so the first line after splitting can be discarded. lines = s.split('\n') if lines[0] == '': lines = lines[1:] if len(lines) == 0: return '' # Count the leading spaces space_count = min(self._leading_spaces(line) for line in lines if line != '') # Remove the leading spaces from each line, based on the line # with the fewest leading spaces leading_spaces = ' ' * space_count assert all(line.startswith(leading_spaces) or line == '' for line in lines), 'all lines have leading spaces' return '\n'.join('' if line == '' else line[space_count:] for line in lines) def _leading_spaces(self, s): space_count = 0 while space_count < len(s) and s[space_count] == ' ': space_count += 1 return space_count def _trim_trailing_spaces(self, s): return '\n'.join(line.rstrip() for line in s.split('\n')) def _make_local_file(self, temp_dir, file_name): local_path = os.path.join(temp_dir, file_name) with open(local_path, 'wb') as f: f.write(six.b('hello world')) return local_path def _read_file(self, local_path): with open(local_path, 'rb') as f: return f.read()
class TestConsoleTool(TestBase): def setUp(self): self.account_info = StubAccountInfo() self.cache = InMemoryCache() self.raw_api = RawSimulator() self.b2_api = B2Api(self.account_info, self.cache, self.raw_api) def test_authorize_with_bad_key(self): expected_stdout = ''' Using http://production.example.com ''' expected_stderr = ''' ERROR: unable to authorize account: Invalid authorization token. Server said: invalid application key: bad-app-key (bad_auth_token) ''' self._run_command( ['authorize_account', 'my-account', 'bad-app-key'], expected_stdout, expected_stderr, 1 ) def test_authorize_with_good_key_using_hyphen(self): # Initial condition assert self.account_info.get_account_auth_token() is None # Authorize an account with a good api key. expected_stdout = """ Using http://production.example.com """ self._run_command( ['authorize-account', 'my-account', 'good-app-key'], expected_stdout, '', 0 ) # Auth token should be in account info now assert self.account_info.get_account_auth_token() is not None def test_authorize_with_good_key_using_underscore(self): # Initial condition assert self.account_info.get_account_auth_token() is None # Authorize an account with a good api key. expected_stdout = """ Using http://production.example.com """ self._run_command( ['authorize-account', 'my-account', 'good-app-key'], expected_stdout, '', 0 ) # Auth token should be in account info now assert self.account_info.get_account_auth_token() is not None def test_help_with_bad_args(self): expected_stderr = ''' b2 list-parts <largeFileId> Lists all of the parts that have been uploaded for the given large file, which must be a file that was started but not finished or canceled. ''' self._run_command(['list_parts'], '', expected_stderr, 1) def test_clear_account(self): # Initial condition self._authorize_account() assert self.account_info.get_account_auth_token() is not None # Clearing the account should remove the auth token # from the account info. self._run_command(['clear-account'], '', '', 0) assert self.account_info.get_account_auth_token() is None def test_buckets(self): self._authorize_account() # Make a bucket with an illegal name expected_stdout = 'ERROR: Bad request: illegal bucket name: bad/bucket/name\n' self._run_command(['create_bucket', 'bad/bucket/name', 'allPublic'], '', expected_stdout, 1) # Make two buckets self._run_command(['create_bucket', 'my-bucket', 'allPrivate'], 'bucket_0\n', '', 0) self._run_command(['create_bucket', 'your-bucket', 'allPrivate'], 'bucket_1\n', '', 0) # Update one of them expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": {}, "bucketName": "my-bucket", "bucketType": "allPublic", "lifecycleRules": [], "revision": 2 } ''' self._run_command(['update_bucket', 'my-bucket', 'allPublic'], expected_stdout, '', 0) # Make sure they are there expected_stdout = ''' bucket_0 allPublic my-bucket bucket_1 allPrivate your-bucket ''' self._run_command(['list_buckets'], expected_stdout, '', 0) # Delete one expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_1", "bucketInfo": {}, "bucketName": "your-bucket", "bucketType": "allPrivate", "lifecycleRules": [], "revision": 1 } ''' self._run_command(['delete_bucket', 'your-bucket'], expected_stdout, '', 0) def test_bucket_info_from_json(self): self._authorize_account() self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) bucket_info = {'color': 'blue'} expected_stdout = ''' { "accountId": "my-account", "bucketId": "bucket_0", "bucketInfo": { "color": "blue" }, "bucketName": "my-bucket", "bucketType": "allPrivate", "lifecycleRules": [], "revision": 2 } ''' self._run_command( ['update_bucket', '--bucketInfo', json.dumps(bucket_info), 'my-bucket', 'allPrivate'], expected_stdout, '', 0 ) def test_cancel_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file1', 'text/plain', {}) self._run_command(['cancel_large_file', file.file_id], '9999 canceled\n', '', 0) def test_cancel_all_large_file(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') bucket.start_large_file('file1', 'text/plain', {}) bucket.start_large_file('file2', 'text/plain', {}) expected_stdout = ''' 9999 canceled 9998 canceled ''' self._run_command( ['cancel_all_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0 ) def test_files(self): self._authorize_account() self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) with TempDir() as temp_dir: local_file1 = self._make_local_file(temp_dir, 'file1.txt') # Upload a file expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/file1.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ''' self._run_command( ['upload_file', '--noProgress', 'my-bucket', local_file1, 'file1.txt'], expected_stdout, '', 0 ) # Get file info mod_time_str = str(int(os.path.getmtime(local_file1) * 1000)) expected_stdout = ''' { "accountId": "my-account", "action": "upload", "bucketId": "bucket_0", "contentLength": 11, "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "%s" }, "fileName": "file1.txt", "uploadTimestamp": 5000 } ''' % (mod_time_str,) self._run_command(['get_file_info', '9999'], expected_stdout, '', 0) # Download by name local_download1 = os.path.join(temp_dir, 'download1.txt') expected_stdout = ''' File name: file1.txt File id: 9999 File size: 11 Content type: b2/x-auto Content sha1: 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed INFO src_last_modified_millis: %s checksum matches ''' % (mod_time_str,) self._run_command( [ 'download_file_by_name', '--noProgress', 'my-bucket', 'file1.txt', local_download1 ], expected_stdout, '', 0 ) self.assertEquals(six.b('hello world'), self._read_file(local_download1)) # Download file by ID. (Same expected output as downloading by name) local_download2 = os.path.join(temp_dir, 'download2.txt') self._run_command( ['download_file_by_id', '--noProgress', '9999', local_download2], expected_stdout, '', 0 ) self.assertEquals(six.b('hello world'), self._read_file(local_download2)) # Hide the file expected_stdout = ''' { "action": "hide", "fileId": "9998", "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 } ''' self._run_command(['hide_file', 'my-bucket', 'file1.txt'], expected_stdout, '', 0) # List the file versions expected_stdout = ''' { "files": [ { "action": "hide", "contentSha1": "none", "contentType": null, "fileId": "9998", "fileInfo": {}, "fileName": "file1.txt", "size": 0, "uploadTimestamp": 5001 }, { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "%s" }, "fileName": "file1.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileId": null, "nextFileName": null } ''' % (mod_time_str,) self._run_command(['list_file_versions', 'my-bucket'], expected_stdout, '', 0) # List the file names expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # Delete one file version, passing the name in expected_stdout = ''' { "action": "delete", "fileId": "9998", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', 'file1.txt', '9998'], expected_stdout, '', 0) # Delete one file version, not passing the name in expected_stdout = ''' { "action": "delete", "fileId": "9999", "fileName": "file1.txt" } ''' self._run_command(['delete_file_version', '9999'], expected_stdout, '', 0) def test_get_download_auth_defaults(self): self._authorize_account() self._create_my_bucket() self._run_command( ['get_download_auth', 'my-bucket'], 'fake_download_auth_token_bucket_0__86400\n', '', 0 ) def test_get_download_auth_explicit(self): self._authorize_account() self._create_my_bucket() self._run_command( ['get_download_auth', '--prefix', 'prefix', '--duration', '12345', 'my-bucket'], 'fake_download_auth_token_bucket_0_prefix_12345\n', '', 0 ) def test_list_parts_with_none(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) self._run_command(['list_parts', file.file_id], '', '', 0) def test_list_parts_with_parts(self): self._authorize_account() self._create_my_bucket() bucket = self.b2_api.get_bucket_by_name('my-bucket') file = bucket.start_large_file('file', 'text/plain', {}) content = six.b('hello world') large_file_upload_state = mock.MagicMock() large_file_upload_state.has_error.return_value = False bucket._upload_part( file.file_id, 1, (0, 11), UploadSourceBytes(content), large_file_upload_state ) bucket._upload_part( file.file_id, 3, (0, 11), UploadSourceBytes(content), large_file_upload_state ) expected_stdout = ''' 1 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed 3 11 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed ''' self._run_command(['list_parts', file.file_id], expected_stdout, '', 0) def test_list_unfinished_large_files_with_none(self): self._authorize_account() self._create_my_bucket() self._run_command(['list_unfinished_large_files', 'my-bucket'], '', '', 0) def test_list_unfinished_large_files_with_some(self): self._authorize_account() self._create_my_bucket() api_url = self.account_info.get_api_url() auth_token = self.account_info.get_account_auth_token() self.raw_api.start_large_file(api_url, auth_token, 'bucket_0', 'file1', 'text/plain', {}) self.raw_api.start_large_file( api_url, auth_token, 'bucket_0', 'file2', 'text/plain', {'color': 'blue'} ) self.raw_api.start_large_file( api_url, auth_token, 'bucket_0', 'file3', 'application/json', {} ) expected_stdout = ''' 9999 file1 text/plain 9998 file2 text/plain color=blue 9997 file3 application/json ''' self._run_command(['list_unfinished_large_files', 'my-bucket'], expected_stdout, '', 0) def test_upload_large_file(self): self._authorize_account() self._create_my_bucket() min_part_size = self.account_info.get_minimum_part_size() file_size = min_part_size * 3 with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') text = six.u('*') * file_size with open(file_path, 'wb') as f: f.write(text.encode('utf-8')) expected_stdout = ''' URL by file name: http://download.example.com/file/my-bucket/test.txt URL by fileId: http://download.example.com/b2api/v1/b2_download_file_by_id?fileId=9999 { "action": "upload", "fileId": "9999", "fileName": "test.txt", "size": 600, "uploadTimestamp": 5000 } ''' self._run_command( [ 'upload_file', '--noProgress', '--threads', '5', 'my-bucket', file_path, 'test.txt' ], expected_stdout, '', 0 ) def test_show_account_info(self): self._authorize_account() expected_stdout = ''' Account ID: my-account Application Key: good-app-key Account Auth Token: AUTH:my-account API URL: http://api.example.com Download URL: http://download.example.com ''' self._run_command(['show-account-info'], expected_stdout, '', 0) def test_sync(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: file_path = os.path.join(temp_dir, 'test.txt') with open(file_path, 'wb') as f: f.write(six.u('hello world').encode('utf-8')) expected_stdout = ''' upload test.txt ''' command = ['sync', '--threads', '5', '--noProgress', temp_dir, 'b2://my-bucket'] self._run_command(command, expected_stdout, '', 0) def test_sync_syntax_error(self): self._authorize_account() self._create_my_bucket() expected_stderr = 'ERROR: --includeRegex cannot be used without --excludeRegex at the same time\n' self._run_command( ['sync', '--includeRegex', '.incl', 'non-existent-local-folder', 'b2://my-bucket'], expected_stderr=expected_stderr, expected_status=1 ) def test_sync_dry_run(self): self._authorize_account() self._create_my_bucket() with TempDir() as temp_dir: temp_file = self._make_local_file(temp_dir, 'test-dry-run.txt') # dry-run expected_stdout = ''' upload test-dry-run.txt ''' command = ['sync', '--noProgress', '--dryRun', temp_dir, 'b2://my-bucket'] self._run_command(command, expected_stdout, '', 0) # file should not have been uploaded expected_stdout = ''' { "files": [], "nextFileName": null } ''' self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) # upload file expected_stdout = ''' upload test-dry-run.txt ''' command = ['sync', '--noProgress', temp_dir, 'b2://my-bucket'] self._run_command(command, expected_stdout, '', 0) # file should have been uploaded mtime = file_mod_time_millis(temp_file) expected_stdout = ''' { "files": [ { "action": "upload", "contentSha1": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", "contentType": "b2/x-auto", "fileId": "9999", "fileInfo": { "src_last_modified_millis": "%d" }, "fileName": "test-dry-run.txt", "size": 11, "uploadTimestamp": 5000 } ], "nextFileName": null } ''' % (mtime) self._run_command(['list_file_names', 'my-bucket'], expected_stdout, '', 0) def _authorize_account(self): """ Prepare for a test by authorizing an account and getting an account auth token """ self._run_command_no_checks(['authorize_account', 'my-account', 'good-app-key']) def _create_my_bucket(self): self._run_command(['create_bucket', 'my-bucket', 'allPublic'], 'bucket_0\n', '', 0) def _run_command(self, argv, expected_stdout='', expected_stderr='', expected_status=0): """ Runs one command using the ConsoleTool, checking stdout, stderr, and the returned status code. The ConsoleTool is stateless, so we can make a new one for each call, with a fresh stdout and stderr """ expected_stdout = self._trim_leading_spaces(expected_stdout) expected_stderr = self._trim_leading_spaces(expected_stderr) stdout, stderr = self._get_stdouterr() console_tool = ConsoleTool(self.b2_api, stdout, stderr) actual_status = console_tool.run_command(['b2'] + argv) # The json module in Python 2.6 includes trailing spaces. Later version of Python don't. actual_stdout = self._trim_trailing_spaces(stdout.getvalue()) actual_stderr = self._trim_trailing_spaces(stderr.getvalue()) if expected_stdout != actual_stdout: print(repr(expected_stdout)) print(repr(actual_stdout)) if expected_stderr != actual_stderr: print(repr(expected_stderr)) print(repr(actual_stderr)) self.assertEqual(expected_stdout, actual_stdout, 'stdout') self.assertEqual(expected_stderr, actual_stderr, 'stderr') self.assertEqual(expected_status, actual_status, 'exit status code') def test_bad_terminal(self): stdout = mock.MagicMock() stdout.write = mock.MagicMock( side_effect=[ UnicodeEncodeError('codec', u'foo', 100, 105, 'artificial UnicodeEncodeError') ] + list(range(25)) ) stderr = mock.MagicMock() console_tool = ConsoleTool(self.b2_api, stdout, stderr) console_tool.run_command(['b2', 'authorize_account', 'my-account', 'good-app-key']) def _get_stdouterr(self): class MyStringIO(six.StringIO): if six.PY2: # python3 already has this attribute encoding = 'fake_encoding' stdout = MyStringIO() stderr = MyStringIO() return stdout, stderr def _run_command_no_checks(self, argv): stdout, stderr = self._get_stdouterr() ConsoleTool(self.b2_api, stdout, stderr).run_command(['b2'] + argv) def _trim_leading_spaces(self, s): """ Takes the contents of a triple-quoted string, and removes the leading newline and leading spaces that come from it being indented with code. """ # The first line starts on the line following the triple # quote, so the first line after splitting can be discarded. lines = s.split('\n') if lines[0] == '': lines = lines[1:] if len(lines) == 0: return '' # Count the leading spaces space_count = min(self._leading_spaces(line) for line in lines if line != '') # Remove the leading spaces from each line, based on the line # with the fewest leading spaces leading_spaces = ' ' * space_count assert all(line.startswith(leading_spaces) or line == '' for line in lines), 'all lines have leading spaces' return '\n'.join('' if line == '' else line[space_count:] for line in lines) def _leading_spaces(self, s): space_count = 0 while space_count < len(s) and s[space_count] == ' ': space_count += 1 return space_count def _trim_trailing_spaces(self, s): return '\n'.join(line.rstrip() for line in s.split('\n')) def _make_local_file(self, temp_dir, file_name): local_path = os.path.join(temp_dir, file_name) with open(local_path, 'wb') as f: f.write(six.b('hello world')) return local_path def _read_file(self, local_path): with open(local_path, 'rb') as f: return f.read()