diff --git a/README.md b/README.md index a4f412e..e0e8816 100644 --- a/README.md +++ b/README.md @@ -61,4 +61,4 @@ source venv/bin/activate ``` * Note `beta` track on Google Play, that's our way to show to people on Play Store that it's not a finished product. We don't use the "production" track for Nightly, unlike beta and release. -1. If all goes well, add `--commit` (or `--keep` for Amazon) to the command line and rerun it. +1. If all goes well, add `--commit` to the command line and rerun it. diff --git a/mozapkpublisher/common/store.py b/mozapkpublisher/common/store.py index 1b50110..07cef78 100644 --- a/mozapkpublisher/common/store.py +++ b/mozapkpublisher/common/store.py @@ -16,14 +16,6 @@ logger = logging.getLogger(__name__) -BASE_AMAZON_URL = 'https://developer.amazon.com/api/appstore/v1' -AMAZON_RECENT_CHANGES = { - 'en-US': 'Bug fixes and technical improvements.', - 'de-DE': 'Fehlerkorrekturen und Technische Verbesserungen.', - 'fr-FR': 'Correction de bugs et amélioration des techniques.', -} - - def add_general_google_play_arguments(parser): parser.add_argument('--service-account', help='The service account email', required=True) parser.add_argument('--credentials', dest='google_play_credentials_filename', @@ -45,124 +37,6 @@ def execute(self): return self._return_value -def http(expected_status, method, url, **kwargs): - response = requests.request( - method=method, - url=url, - **kwargs, - ) - if response.status_code != expected_status: - raise RuntimeError(f'Expected "{method}" to "{url}" to have status code ' - f'"{expected_status}", but received "{response.status_code}" ' - f'("{response.text}") instead') - return response - - -class AmazonAuth(requests.auth.AuthBase): - def __init__(self, access_token): - self._access_token = access_token - - def __call__(self, request: requests.Request): - request.headers['Authorization'] = f'Bearer {self._access_token}' - return request - - -class AmazonStoreEdit: - def __init__(self, auth, edit_it, package_name): - self._auth = auth - self._edit_id = edit_it - self._package_name = package_name - - def _http(self, expected_status, method, endpoint, **kwargs): - url = f'{BASE_AMAZON_URL}/applications/{self._package_name}/edits/{self._edit_id}' + endpoint - - return http(expected_status, method, url, auth=self._auth, **kwargs) - - def update_app(self, extracted_apks): - body = self._http(200, 'get', '/apks').json() - existing_apk_ids = [apk['id'] for apk in body] - - for apk_id in existing_apk_ids: - response = self._http(200, 'get', f'/apks/{apk_id}') - etag = response.headers['ETag'] - self._http(204, 'delete', f'/apks/{apk_id}', headers={'If-Match': etag}) - - # TODO: simplify update_app(...) so it just takes [apks], rather than [(apk, _unused)] - for apk, _ in extracted_apks: - self._http(200, 'post', '/apks/upload', data=apk, - headers={'Content-Type': 'application/octet-stream'}) - - response = self._http(200, 'get', '/listings') - languages = response.json()['listings'].keys() - for locale in languages: - response = self._http(200, 'get', f'/listings/{locale}') - etag = response.headers['ETag'] - listing = response.json() - listing['recentChanges'] = AMAZON_RECENT_CHANGES.get(locale, '✔') - - self._http(200, 'put', f'/listings/{locale}', headers={'If-Match': etag}, json=listing) - - def validate(self): - self._http(200, 'post', '/validate') - - def cancel(self): - response = self._http(200, 'get', '') - etag = response.headers['ETag'] - - self._http(204, 'delete', '', headers={'If-Match': etag}) - - @staticmethod - @contextmanager - def transaction(client_id, client_secret, package_name, *, contact_server, dry_run): - if contact_server: - response = http(200, 'post', 'https://api.amazon.com/auth/o2/token', data={ - 'client_id': client_id, - 'client_secret': client_secret, - 'grant_type': 'client_credentials', - 'scope': 'appstore::apps:readwrite', - }) - auth = AmazonAuth(response.json()['access_token']) - - response = http(200, 'get', f'{BASE_AMAZON_URL}/applications/{package_name}/edits', auth=auth) - if response.text != '{}': - # Only one "upcoming version" is allowed at a time. We could automatically delete - # the existing one and create a new one, but that could cause loss of data (e.g.: - # if someone is manually creating a release) - raise RuntimeError(f'The app "{package_name}" already has an "upcoming ' - f'version". Please submit or delete that upcoming ' - f'version from the Amazon Developer Console.') - - response = http(200, 'post', f'{BASE_AMAZON_URL}/applications/{package_name}/edits', auth=auth) - edit_id = response.json()['id'] - edit = AmazonStoreEdit(auth, edit_id, package_name) - else: - logger.warning('Not a single request to Amazon will be made, since `contact_server` ' - 'was set to `False`') - edit = MockAmazonStoreEdit() - - try: - yield edit - edit.validate() - if dry_run: - logger.warning('`try_run` was `True`. Cancelling the edit...') - edit.cancel() - except BaseException: - logger.warning('An error was encountered, cancelling the edit...') - edit.cancel() - raise - - -class MockAmazonStoreEdit: - def update_app(self, apks): - pass - - def validate(self): - pass - - def cancel(self): - pass - - class GooglePlayEdit: """Represents an "edit" to an app on the Google Play store diff --git a/mozapkpublisher/common/utils.py b/mozapkpublisher/common/utils.py index 8bf0894..7fc80a7 100644 --- a/mozapkpublisher/common/utils.py +++ b/mozapkpublisher/common/utils.py @@ -48,14 +48,25 @@ def is_firefox_version_nightly(firefox_version): def add_push_arguments(parser): parser.add_argument('--username', required=True, - help='Either the amazon client id or the google service account') + help='Google service account') parser.add_argument('--secret', required=True, - help='Either the amazon client secret or the file that contains ' - 'google credentials') + help='File that contains google credentials') parser.add_argument('--do-not-contact-server', action='store_false', dest='contact_server', help='''Prevent any request to reach the APK server. Use this option if you want to run the script without any valid credentials nor valid APKs. --service-account and --credentials must still be provided (you can just fill them with random string and file).''') + parser.add_argument('track', help='Track on which to upload') + parser.add_argument( + '--rollout-percentage', + type=int, + choices=range(0, 101), + metavar='[0-100]', + default=None, + help='The percentage of user who will get the update. Specify only if track is rollout' + ) + parser.add_argument('--commit', action='store_false', dest='dry_run', + help='Commit new release on Google Play. This action cannot be ' + 'reverted') def metadata_by_package_name(metadata_dict): diff --git a/mozapkpublisher/push_aab.py b/mozapkpublisher/push_aab.py index c80b76a..097666e 100755 --- a/mozapkpublisher/push_aab.py +++ b/mozapkpublisher/push_aab.py @@ -34,11 +34,6 @@ def push_aab( contact_server (bool): `False` to avoid communicating with the Google Play server. Useful if you're using mock credentials. """ - if track is None: - # The Google store allows multiple stability "tracks" to exist for a single app, so it - # requires you to disambiguate which track you'd like to publish to. - raise WrongArgumentGiven('The track must be provided') - # We want to tune down some logs, even when push_aab() isn't called from the command line main_logging.init() @@ -64,40 +59,19 @@ def push_aab( def main(): parser = argparse.ArgumentParser(description='Upload AABs on the Google Play Store.') - - # TODO: move these to add_push_arguments when Amazon support is removed - parser.add_argument('track', help='Track on which to upload') - parser.add_argument( - '--rollout-percentage', - type=int, - choices=range(0, 101), - metavar='[0-100]', - default=None, - help='The percentage of users who will get the update. Specify only if track is rollout' - ) - parser.add_argument('--commit', action='store_false', dest='dry_run', - help='Commit new release on Google Play. This action cannot be ' - 'reverted') - add_push_arguments(parser) add_aab_checks_arguments(parser) config = parser.parse_args() - track = config.track - rollout_percentage = config.rollout_percentage - - try: - push_aab( - config.aabs, - config.username, - config.secret, - track, - rollout_percentage, - config.dry_run, - config.contact_server, - ) - except WrongArgumentGiven as e: - parser.error(e) + push_aab( + config.aabs, + config.username, + config.secret, + config.track, + config.rollout_percentage, + config.dry_run, + config.contact_server, + ) __name__ == '__main__' and main() diff --git a/mozapkpublisher/push_apk.py b/mozapkpublisher/push_apk.py index 922ccf3..382f29d 100755 --- a/mozapkpublisher/push_apk.py +++ b/mozapkpublisher/push_apk.py @@ -5,26 +5,18 @@ from mozapkpublisher.common import main_logging from mozapkpublisher.common.apk import add_apk_checks_arguments, extract_and_check_apks_metadata -from mozapkpublisher.common.exceptions import WrongArgumentGiven -from mozapkpublisher.common.store import AmazonStoreEdit, GooglePlayEdit +from mozapkpublisher.common.store import GooglePlayEdit from mozapkpublisher.common.utils import add_push_arguments, metadata_by_package_name logger = logging.getLogger(__name__) -_STORE_PER_TARGET_PLATFORM = { - 'amazon': AmazonStoreEdit, - 'google': GooglePlayEdit, -} - - def push_apk( apks, - target_store, username, secret, expected_package_names, - track=None, + track, rollout_percentage=None, dry_run=True, contact_server=True, @@ -36,44 +28,22 @@ def push_apk( """ Args: apks: list of APK files - target_store (str): either "google" or "amazon", affects what other parameters will need - to be provided to this function - username (str): Google Play service account or Amazon Store client ID - secret (str): Filename of Google Play Credentials file or contents of Amazon Store - client secret + username (str): Google Play service account + secret (str): Filename of Google Play Credentials file expected_package_names (list of str): defines what the expected package names must be. - track (str): (only when `target_store` is "google") Google Play track to deploy - to (e.g.: "nightly"). If "rollout" is chosen, the parameter `rollout_percentage` must - be specified as well - rollout_percentage (int): percentage of users to roll out this update to. Must be a number - in (0-100]. This option is only valid if `target_store` is "google" and - `track` is set to "rollout" + track (str): Google Play track to deploy to (e.g.: "nightly"). If "rollout" is chosen, the parameter + `rollout_percentage` must be specified as well + rollout_percentage (int): percentage of users to roll out this update to. Must be a number in (0-100]. This + option is only valid if `track` is set to "rollout" dry_run (bool): `True` to do a dry-run - contact_server (bool): `False` to avoid communicating with the Google Play server or Amazon - Store server. Useful if you're using mock credentials. + contact_server (bool): `False` to avoid communicating with the Google Play server. Useful if you're using mock + credentials. skip_checks_fennec (bool): skip Fennec-specific checks skip_check_same_locales (bool): skip check to ensure all APKs have the same locales skip_check_multiple_locales (bool): skip check to ensure all APKs have more than one locale skip_check_ordered_version_codes (bool): skip check to ensure that ensures all APKs have different version codes and that the x86 version code > the arm version code """ - if target_store == "google" and track is None: - # The Google store allows multiple stability "tracks" to exist for a single app, so it - # requires you to disambiguate which track you'd like to publish to. - raise WrongArgumentGiven('When "target_store" is "google", the track must be provided') - if target_store == "amazon": - # The Amazon app doesn't have a stability "tracks" tool like Google. It _does_ have a - # "Live App Testing" mechanism, but you have to use the website to use it (the API - # doesn't support it). So, it's always the "production" app that's updated, and there's - # no need to specify "track" - # Source: "Play Store API supports additional resources (such as testers and tracks) that - # the [Amazon] Appstore currently does not support." - # https://developer.amazon.com/docs/app-submission-api/migrate.html - if track is not None: - raise WrongArgumentGiven('Tracks are not supported on Amazon') - if rollout_percentage is not None: - raise WrongArgumentGiven('Rollout percentage is not supported on Amazon') - # We want to tune down some logs, even when push_apk() isn't called from the command line main_logging.init() @@ -99,66 +69,31 @@ def push_apk( # by package name here. apks_by_package_name = metadata_by_package_name(apks_metadata_per_paths) for package_name, extracted_apks in apks_by_package_name.items(): - store = _STORE_PER_TARGET_PLATFORM[target_store] - with store.transaction(username, secret, package_name, contact_server=contact_server, + with GooglePlayEdit.transaction(username, secret, package_name, contact_server=contact_server, dry_run=dry_run) as edit: edit.update_app(extracted_apks, **update_app_kwargs) def main(): parser = argparse.ArgumentParser(description='Upload APKs on the Google Play Store.') - - subparsers = parser.add_subparsers(dest='target_store', title='Target Store') - - # TODO: move these to add_push_arguments when Amazon support is removed - google_parser = subparsers.add_parser('google') - google_parser.add_argument('track', help='Track on which to upload') - google_parser.add_argument( - '--rollout-percentage', - type=int, - choices=range(0, 101), - metavar='[0-100]', - default=None, - help='The percentage of user who will get the update. Specify only if track is rollout' - ) - google_parser.add_argument('--commit', action='store_false', dest='dry_run', - help='Commit new release on Google Play. This action cannot be ' - 'reverted') - - amazon_parser = subparsers.add_parser('amazon') - amazon_parser.add_argument('--keep', action='store_false', dest='dry_run', - help='Keep "upcoming version" on Amazon to be submitted or ' - 'cancelled on the Amazon Developer Web UI.') - add_push_arguments(parser) add_apk_checks_arguments(parser) config = parser.parse_args() - if config.target_store == 'google': - track = config.track - rollout_percentage = config.rollout_percentage - else: - track = None - rollout_percentage = None - - try: - push_apk( - config.apks, - config.target_store, - config.username, - config.secret, - config.expected_package_names, - track, - rollout_percentage, - config.dry_run, - config.contact_server, - config.skip_check_ordered_version_codes, - config.skip_check_multiple_locales, - config.skip_check_same_locales, - config.skip_checks_fennec - ) - except WrongArgumentGiven as e: - parser.error(e) + push_apk( + config.apks, + config.username, + config.secret, + config.expected_package_names, + config.track, + config.rollout_percentage, + config.dry_run, + config.contact_server, + config.skip_check_ordered_version_codes, + config.skip_check_multiple_locales, + config.skip_check_same_locales, + config.skip_checks_fennec + ) __name__ == '__main__' and main() diff --git a/mozapkpublisher/test/common/test_store.py b/mozapkpublisher/test/common/test_store.py index 1a0a2f3..f8eb2ad 100644 --- a/mozapkpublisher/test/common/test_store.py +++ b/mozapkpublisher/test/common/test_store.py @@ -12,7 +12,7 @@ from mozapkpublisher.common import store from mozapkpublisher.common.exceptions import WrongArgumentGiven from mozapkpublisher.common.store import add_general_google_play_arguments, \ - GooglePlayEdit, _create_google_edit_resource, AmazonStoreEdit, AmazonAuth, MockAmazonStoreEdit + GooglePlayEdit, _create_google_edit_resource from mozapkpublisher.test import does_not_raise @@ -38,170 +38,6 @@ def test_http_return_response(_): assert store.http(200, 'get', 'http://fake').text == 'body' -def test_amazon_auth(): - auth = AmazonAuth('token') - request = Mock(headers={}) - auth(request) - assert request.headers['Authorization'] == 'Bearer token' - - -@patch.object(store, 'http', return_value=Mock(status_code=200)) -def test_edit_http(mock_http): - edit = AmazonStoreEdit('auth', 'edit_id', 'dummy_package_name') - edit._http(200, 'get', '/endpoint') - mock_http.assert_called_once_with(200, 'get', 'https://developer.amazon.com/api/appstore/v1/' - 'applications/dummy_package_name/edits/edit_id/' - 'endpoint', auth='auth') - - -@patch.object(store, 'http') -def test_amazon_do_not_contact_server(http_mock): - with AmazonStoreEdit.transaction('client id', 'client secret', 'package.name', - contact_server=False, dry_run=False) as edit: - edit.update_app(('apk', None)) - - with AmazonStoreEdit.transaction('client id', 'client secret', 'package.name', - contact_server=False, dry_run=True) as edit: - edit.update_app(('apk', None)) - - http_mock.assert_not_called() - - -def test_amazon_update_app(): - edit = AmazonStoreEdit(None, None, 'dummy_package_name') - with patch.object(edit, '_http') as mock_http: - mock_http.side_effect = [ - Mock(json=lambda: [{'id': 'apk_id'}]), - Mock(headers={'ETag': 'apk etag'}), - None, - None, - Mock(json=lambda: {'listings': {'en-US': {}}}), - Mock(headers={'ETag': 'listing etag'}, json=lambda: {}), - None, - ] - - edit.update_app([('apk', None)]) - mock_http.assert_any_call(200, 'post', '/apks/upload', data='apk', - headers={'Content-Type': 'application/octet-stream'}) - - -def test_amazon_release_notes(): - edit = AmazonStoreEdit(None, None, 'dummy_package_name') - with patch.object(edit, '_http') as mock_http: - mock_http.side_effect = [ - Mock(json=lambda: [{'id': 'apk_id'}]), - Mock(headers={'ETag': 'apk etag'}), - None, - None, - Mock(json=lambda: {'listings': {'sv-SE': {}, 'fr-FR': {}}}), - Mock(headers={'ETag': 'listing etag'}, json=lambda: {}), - None, - Mock(headers={'ETag': 'listing etag'}, json=lambda: {}), - None, - ] - edit.update_app([('apk', None)]) - - mock_http.assert_any_call( - 200, - 'put', - '/listings/fr-FR', - headers={'If-Match': 'listing etag'}, - json={'recentChanges': 'Correction de bugs et amélioration des techniques.'} - ) - mock_http.assert_any_call(200, 'put', '/listings/sv-SE', - headers={'If-Match': 'listing etag'}, - json={'recentChanges': '✔'}) - - -def test_amazon_transaction_contact_and_keep(): - with patch.object(store, 'http') as mock_http: - mock_http.side_effect = [ - Mock(status_code=200, json=lambda: {'access_token': 'token'}), - Mock(status_code=200, text='{}'), - Mock(status_code=200, json=lambda: {'id': 'edit_id'}), - Mock(status_code=200, headers={'ETag': 'edit_etag'}), - Mock(status_code=200), - ] - with AmazonStoreEdit.transaction('client id', 'client secret', 'dummy_package_name', - contact_server=True, dry_run=False): - pass - - mock_http.assert_any_call(200, 'post', 'https://api.amazon.com/auth/o2/token', data={ - 'client_id': 'client id', - 'client_secret': 'client secret', - 'grant_type': ANY, - 'scope': ANY - }) - - assert call(200, 'post', 'https://developer.amazon.com/api/appstore/v1/applications/' - 'dummy_package_name/edits/edit_id/commit', - headers={'If-Match': 'edit_etag'}, - auth=ANY) not in mock_http.call_args_list - - -def test_amazon_transaction_contact_dry_run(): - with patch.object(store, 'http') as mock_http: - mock_http.side_effect = [ - Mock(status_code=200, json=lambda: {'access_token': 'token'}), - Mock(status_code=200, text='{}'), - Mock(status_code=200, json=lambda: {'id': 'edit_id'}), - Mock(status_code=200), - Mock(status_code=200, headers={'ETag': 'edit_etag'}), - Mock(status_code=204), - ] - with AmazonStoreEdit.transaction('client id', 'client secret', 'dummy_package_name', - contact_server=True, dry_run=True): - pass - - mock_http.assert_any_call(200, 'post', 'https://developer.amazon.com/api/appstore/v1/' - 'applications/dummy_package_name/edits/edit_id/' - 'validate', - auth=ANY) - mock_http.assert_any_call(204, 'delete', 'https://developer.amazon.com/api/appstore/v1/' - 'applications/dummy_package_name/edits/edit_id', - headers={'If-Match': 'edit_etag'}, - auth=ANY) - - -def test_amazon_transaction_existing_upcoming_version(): - with patch.object(store, 'http') as mock_http: - mock_http.side_effect = [ - Mock(status_code=200, json=lambda: {'access_token': 'token'}), - Mock(status_code=200, text='{"id": "...", "status": "IN_PROGRESS"}'), - ] - - with pytest.raises(RuntimeError): - with AmazonStoreEdit.transaction('client id', 'client secret', 'dummy_package_name', - contact_server=True, dry_run=False): - pass - - -def test_amazon_transaction_cancel_on_exception(): - with patch.object(store, 'http') as mock_http: - mock_http.side_effect = [ - Mock(status_code=200, json=lambda: {'access_token': 'token'}), - Mock(status_code=200, text='{}'), - Mock(status_code=200, json=lambda: {'id': 'edit_id'}), - Mock(status_code=200, headers={'ETag': 'edit_etag'}), - Mock(status_code=204), - ] - with pytest.raises(RuntimeError): - with AmazonStoreEdit.transaction('client id', 'client secret', 'dummy_package_name', - contact_server=True, dry_run=True): - raise RuntimeError('oops') - - mock_http.assert_any_call(204, 'delete', 'https://developer.amazon.com/api/appstore/v1/' - 'applications/dummy_package_name/edits/edit_id', - headers={'If-Match': 'edit_etag'}, - auth=ANY) - - -def test_amazon_transaction_do_not_contact(): - with AmazonStoreEdit.transaction(None, None, 'dummy_package_name', contact_server=False, - dry_run=True) as edit: - assert isinstance(edit, MockAmazonStoreEdit) - - def test_google_edit_resource_for_options_contact(monkeypatch): service_mock = MagicMock() service_mock.edits.return_value = 'edit resource' diff --git a/mozapkpublisher/test/test_push_apk.py b/mozapkpublisher/test/test_push_apk.py index 532bc84..aa4e13c 100644 --- a/mozapkpublisher/test/test_push_apk.py +++ b/mozapkpublisher/test/test_push_apk.py @@ -88,23 +88,13 @@ def fake_transaction(_, __, ___, *, contact_server, dry_run): def test_google_no_track(): with pytest.raises(WrongArgumentGiven): - push_apk(APKS, 'google', SERVICE_ACCOUNT, credentials, []) - - -def test_amazon_with_track(): - with pytest.raises(WrongArgumentGiven): - push_apk(APKS, 'amazon', CLIENT_ID, CLIENT_SECRET, [], 'alpha') - - -def test_amazon_with_rollout(): - with pytest.raises(WrongArgumentGiven): - push_apk(APKS, 'amazon', CLIENT_ID, CLIENT_SECRET, [], rollout_percentage=50) + push_apk(APKS, SERVICE_ACCOUNT, credentials, []) def test_google(monkeypatch): mock_metadata = patch_extract_metadata(monkeypatch) edit_mock = patch_store_transaction(monkeypatch, store.GooglePlayEdit) - push_apk(APKS, 'google', SERVICE_ACCOUNT, credentials, [], 'rollout', rollout_percentage=50, + push_apk(APKS, SERVICE_ACCOUNT, credentials, [], 'rollout', rollout_percentage=50, contact_server=False) edit_mock.update_app.assert_called_once_with([ (apk_arm, mock_metadata[apk_arm]), @@ -112,24 +102,13 @@ def test_google(monkeypatch): ], 'rollout', 50) -def test_amazon(monkeypatch): - mock_metadata = patch_extract_metadata(monkeypatch) - mock_edit = patch_store_transaction(monkeypatch, store.AmazonStoreEdit) - - push_apk(APKS, 'amazon', CLIENT_ID, CLIENT_SECRET, [], contact_server=False) - mock_edit.update_app.assert_called_once_with([ - (apk_arm, mock_metadata[apk_arm]), - (apk_x86, mock_metadata[apk_x86]), - ]) - - def test_push_apk_tunes_down_logs(monkeypatch): main_logging_mock = MagicMock() monkeypatch.setattr('mozapkpublisher.push_apk.main_logging', main_logging_mock) monkeypatch.setattr('mozapkpublisher.push_apk.extract_and_check_apks_metadata', MagicMock()) monkeypatch.setattr('mozapkpublisher.common.utils.metadata_by_package_name', MagicMock()) - push_apk(APKS, 'google', SERVICE_ACCOUNT, credentials, [], 'alpha', contact_server=False) + push_apk(APKS, SERVICE_ACCOUNT, credentials, [], 'alpha', contact_server=False) main_logging_mock.init.assert_called_once_with() @@ -147,7 +126,6 @@ def test_main_google(monkeypatch): 'script', '--username', 'foo@developer.gserviceaccount.com', '--secret', file, - 'google', 'alpha', file, '--expected-package-name=org.mozilla.fennec_aurora', @@ -159,7 +137,6 @@ def test_main_google(monkeypatch): mock_push_apk.assert_called_once_with( ANY, - 'google', 'foo@developer.gserviceaccount.com', file, ['org.mozilla.fennec_aurora'], @@ -172,35 +149,3 @@ def test_main_google(monkeypatch): False, False, ) - - -def test_main_amazon(monkeypatch): - file = os.path.join(os.path.dirname(__file__), 'data', 'blob') - fail_manual_validation_args = [ - 'script', - '--username', CLIENT_ID, - '--secret', CLIENT_SECRET, - 'amazon', - file, - '--expected-package-name=org.mozilla.fennec_aurora', - ] - - with patch.object(mozapkpublisher.push_apk, 'push_apk') as mock_push_apk: - monkeypatch.setattr(sys, 'argv', fail_manual_validation_args) - main() - - mock_push_apk.assert_called_once_with( - ANY, - 'amazon', - 'client', - 'secret', - ['org.mozilla.fennec_aurora'], - None, - None, - True, - True, - False, - False, - False, - False, - )