diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fb69ded..7526873 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ v1.6.3 * Add confirmation email resend API (`email_resend_confirmation`) * Add account lookup API (`account_lookup`) * Add `policy` param to control notification sources for `push_subscription_set` +* Add ability to get detailed signup error to `create_account` v1.6.2 ------ diff --git a/TODO.md b/TODO.md index bf73f06..7be1c70 100644 --- a/TODO.md +++ b/TODO.md @@ -33,7 +33,7 @@ Refer to mastodon changelog and API docs for details when implementing, add or m * [x] Add POST /api/v1/emails/confirmations to REST API * [x] Add GET /api/v1/accounts/lookup to REST API * [x] Add policy param to POST /api/v1/push/subscriptions in REST API -* [ ] Add details to error response for POST /api/v1/accounts in REST API +* [x] Add details to error response for POST /api/v1/accounts in REST API 3.4.2 ----- diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 12a054a..ae560c5 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -605,8 +605,7 @@ class Mastodon: params['scope'] = " ".join(scopes) try: - response = self.__api_request( - 'POST', '/oauth/token', params, do_ratelimiting=False) + response = self.__api_request('POST', '/oauth/token', params, do_ratelimiting=False) self.access_token = response['access_token'] self.__set_refresh_token(response.get('refresh_token')) self.__set_token_expired(int(response.get('expires_in', 0))) @@ -658,8 +657,8 @@ class Mastodon: self.access_token = None self.__logged_in_id = None - @api_version("2.7.0", "2.7.0", "2.7.0") - def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None): + @api_version("2.7.0", "2.7.0", "3.4.0") + def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None, return_detailed_error=False): """ Creates a new user account with the given username, password and email. "agreement" must be set to true (after showing the user the instance's user agreement and having @@ -674,6 +673,26 @@ class Mastodon: Returns an access token (just like log_in), which it can also persist to to_file, and sets it internally so that the user is now logged in. Note that this token can only be used after the user has confirmed their email. + + By default, the function will throw if the account could not be created. Alternately, + when `return_detailed_error` is passed, Mastodon.py will return the detailed error + response that the API provides (Starting from version 3.4.0 - not checked here) as an dict with + error details as the second return value and the token returned as `None` in case of error. + The dict will contain a text `error` values as well as a `details` value which is a dict with + one optional key for each potential field (`username`, `password`, `email` and `agreement`), + each if present containing a dict with an `error` category and free text `description`. + Valid error categories are: + + * ERR_BLOCKED - When e-mail provider is not allowed + * ERR_UNREACHABLE - When e-mail address does not resolve to any IP via DNS (MX, A, AAAA) + * ERR_TAKEN - When username or e-mail are already taken + * ERR_RESERVED - When a username is reserved, e.g. "webmaster" or "admin" + * ERR_ACCEPTED - When agreement has not been accepted + * ERR_BLANK - When a required attribute is blank + * ERR_INVALID - When an attribute is malformed, e.g. wrong characters or invalid e-mail address + * ERR_TOO_LONG - When an attribute is over the character limit + * ERR_TOO_SHORT - When an attribute is under the character requirement + * ERR_INCLUSION - When an attribute is not one of the allowed values, e.g. unsupported locale """ params = self.__generate_params(locals(), ['to_file', 'scopes']) params['client_id'] = self.client_id @@ -690,8 +709,7 @@ class Mastodon: oauth_params['client_secret'] = self.client_secret oauth_params['grant_type'] = 'client_credentials' - response = self.__api_request( - 'POST', '/oauth/token', oauth_params, do_ratelimiting=False) + response = self.__api_request('POST', '/oauth/token', oauth_params, do_ratelimiting=False) temp_access_token = response['access_token'] except Exception as e: raise MastodonIllegalArgumentError( @@ -699,13 +717,16 @@ class Mastodon: # Step 2: Use that to create a user try: - response = self.__api_request('POST', '/api/v1/accounts', params, do_ratelimiting=False, - access_token_override=temp_access_token) + response = self.__api_request('POST', '/api/v1/accounts', params, do_ratelimiting=False, access_token_override=temp_access_token, skip_error_check=True) + if "error" in response: + if return_detailed_error: + return None, response + raise MastodonIllegalArgumentError('Invalid request: %s' % e) self.access_token = response['access_token'] self.__set_refresh_token(response.get('refresh_token')) - self.__set_token_expired(int(response.get('expires_in', 0))) + self.__set_token_expired(int(response.get('expires_in', 0))) except Exception as e: - raise MastodonIllegalArgumentError('Invalid request: %s' % e) + raise MastodonIllegalArgumentError('Invalid request') # Step 3: Check scopes, persist, et cetera received_scopes = response["scope"].split(" ") @@ -714,8 +735,7 @@ class Mastodon: received_scopes += self.__SCOPE_SETS[scope_set] if not set(scopes) <= set(received_scopes): - raise MastodonAPIError( - 'Granted scopes "' + " ".join(received_scopes) + '" do not contain all of the requested scopes "' + " ".join(scopes) + '".') + raise MastodonAPIError('Granted scopes "' + " ".join(received_scopes) + '" do not contain all of the requested scopes "' + " ".join(scopes) + '".') if to_file is not None: with open(to_file, 'w') as token_file: @@ -724,7 +744,10 @@ class Mastodon: self.__logged_in_id = None - return response['access_token'] + if return_detailed_error: + return response['access_token'], {} + else: + return response['access_token'] @api_version("3.4.0", "3.4.0", "3.4.0") def email_resend_confirmation(self): @@ -2775,7 +2798,7 @@ class Mastodon: """ if not policy in ['all', 'none', 'follower', 'followed']: raise MastodonIllegalArgumentError("Valid values for policy are 'all', 'none', 'follower' or 'followed'.") - + endpoint = Mastodon.__protocolize(endpoint) push_pubkey_b64 = base64.b64encode(encrypt_params['pubkey']) @@ -3506,7 +3529,7 @@ class Mastodon: isotime = isotime[:-2] + ":" + isotime[-2:] return isotime - def __api_request(self, method, endpoint, params={}, files={}, headers={}, access_token_override=None, base_url_override=None, do_ratelimiting=True, use_json=False, parse=True, return_response_object=False): + def __api_request(self, method, endpoint, params={}, files={}, headers={}, access_token_override=None, base_url_override=None, do_ratelimiting=True, use_json=False, parse=True, return_response_object=False, skip_error_check=False): """ Internal API request helper. """ @@ -3657,35 +3680,32 @@ class Mastodon: time.sleep(to_next) request_complete = False continue + + if skip_error_check == False: + if response_object.status_code == 404: + ex_type = MastodonNotFoundError + if not error_msg: + error_msg = 'Endpoint not found.' + # this is for compatibility with older versions + # which raised MastodonAPIError('Endpoint not found.') + # on any 404 + elif response_object.status_code == 401: + ex_type = MastodonUnauthorizedError + elif response_object.status_code == 500: + ex_type = MastodonInternalServerError + elif response_object.status_code == 502: + ex_type = MastodonBadGatewayError + elif response_object.status_code == 503: + ex_type = MastodonServiceUnavailableError + elif response_object.status_code == 504: + ex_type = MastodonGatewayTimeoutError + elif response_object.status_code >= 500 and \ + response_object.status_code <= 511: + ex_type = MastodonServerError + else: + ex_type = MastodonAPIError - if response_object.status_code == 404: - ex_type = MastodonNotFoundError - if not error_msg: - error_msg = 'Endpoint not found.' - # this is for compatibility with older versions - # which raised MastodonAPIError('Endpoint not found.') - # on any 404 - elif response_object.status_code == 401: - ex_type = MastodonUnauthorizedError - elif response_object.status_code == 500: - ex_type = MastodonInternalServerError - elif response_object.status_code == 502: - ex_type = MastodonBadGatewayError - elif response_object.status_code == 503: - ex_type = MastodonServiceUnavailableError - elif response_object.status_code == 504: - ex_type = MastodonGatewayTimeoutError - elif response_object.status_code >= 500 and \ - response_object.status_code <= 511: - ex_type = MastodonServerError - else: - ex_type = MastodonAPIError - - raise ex_type( - 'Mastodon API returned error', - response_object.status_code, - response_object.reason, - error_msg) + raise ex_type('Mastodon API returned error', response_object.status_code, response_object.reason, error_msg) if return_response_object: return response_object diff --git a/tests/cassettes/test_app_account_create_invalid.yaml b/tests/cassettes/test_app_account_create_invalid.yaml new file mode 100644 index 0000000..bbf4843 --- /dev/null +++ b/tests/cassettes/test_app_account_create_invalid.yaml @@ -0,0 +1,245 @@ +interactions: +- request: + body: client_name=mastodon.py+generated+test+app&scopes=read+write+follow+push&redirect_uris=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '122' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.28.1 + method: POST + uri: http://localhost:3000/api/v1/apps + response: + body: + string: '{"id":"4","name":"mastodon.py generated test app","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"OueO_7-PGuI_wJwqr3gJCO3Mfp-CnB7ntLm2BDNgwoE","client_secret":"i74hCQQyscpk-AoXt8gL1lBYLlgsQw7r-vP_cuKWquA","vapid_key":"BFu6DBpfcm8_h8gm3rHUkfaOLg7azvYN_auFI4KcNuh5SLBVMhTkKKvUaLENtA_c6v5Hmrucvh0WwsN1o9NFQRU="}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-eWGkzakxkoJqETI+J7AOfA==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"e57c57d0ecbadb4122581beb6057bf7b" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - c5d33842-2156-4850-ac91-154f71f72d2e + X-Runtime: + - '0.017495' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - mastodonpy + method: GET + uri: http://localhost:3000/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.0.0rc2","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":4,"status_count":5,"domain_count":0},"thumbnail":"http://localhost:3000/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":true,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-pTVYgmBqNUhkaog/3BIWrg==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 19 Nov 2022 00:05:11 GMT + ETag: + - W/"58e74f5c697f043d86089eae87509b84" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 3f3580e9-a01c-4cb3-a8ff-e933a5b0a878 + X-Runtime: + - '0.038202' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: scope=read+write+follow+push&client_id=OueO_7-PGuI_wJwqr3gJCO3Mfp-CnB7ntLm2BDNgwoE&client_secret=i74hCQQyscpk-AoXt8gL1lBYLlgsQw7r-vP_cuKWquA&grant_type=client_credentials + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '170' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - mastodonpy + method: POST + uri: http://localhost:3000/oauth/token + response: + body: + string: '{"access_token":"dDfMnIWbtYShiuOouwPaH1j8ZzGRdcg39Ns0nV5jXpo","token_type":"Bearer","scope":"read + write follow push","created_at":1668816311}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-2oP2TTFLzbSaiZa7hgho0w==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"d29d21f6cdf89562d22826c8794a7259" + Pragma: + - no-cache + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 10f9efb8-85eb-4b30-ad16-9656fe53fd44 + X-Runtime: + - '0.022478' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: username=coolguy73878&password=&email=email%40localhost73878&locale=en&client_id=OueO_7-PGuI_wJwqr3gJCO3Mfp-CnB7ntLm2BDNgwoE&client_secret=i74hCQQyscpk-AoXt8gL1lBYLlgsQw7r-vP_cuKWquA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer dDfMnIWbtYShiuOouwPaH1j8ZzGRdcg39Ns0nV5jXpo + Connection: + - keep-alive + Content-Length: + - '182' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - mastodonpy + method: POST + uri: http://localhost:3000/api/v1/accounts + response: + body: + string: '{"error":"Validation failed: Password can''t be blank, Service agreement + must be accepted","details":{"password":[{"error":"ERR_BLANK","description":"can''t + be blank"}],"agreement":[{"error":"ERR_ACCEPTED","description":"must be accepted"}]}}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-OtVYUk9hlx5v/TcbXUduUA==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 48f3d7ec-0b4f-48ce-bf0c-acbec7479c71 + X-Runtime: + - '0.561200' + X-XSS-Protection: + - 1; mode=block + status: + code: 422 + message: Unprocessable Entity +version: 1 diff --git a/tests/cassettes/test_push_set.yaml b/tests/cassettes/test_push_set.yaml deleted file mode 100644 index 4995277..0000000 --- a/tests/cassettes/test_push_set.yaml +++ /dev/null @@ -1,122 +0,0 @@ -interactions: -- request: - body: subscription%5Bendpoint%5D=https%3A%2F%2Fexample.com&subscription%5Bkeys%5D%5Bp256dh%5D=BKFO5w6Uf%2B%2F2wo89ovbphk5Zrb0mcAKjZrIyrX66f2IAijRtuXx4yK6J9hR%2FemKnF2DyQcyx7%2F4IGhKHBk0OTEk%3D&subscription%5Bkeys%5D%5Bauth%5D=6T8gVmd01DhQUbejRj%2Bxmg%3D%3D&policy=none&data%5Balerts%5D%5Bfollow%5D=1&data%5Balerts%5D%5Bfavourite%5D=1&data%5Balerts%5D%5Breblog%5D=1&data%5Balerts%5D%5Bmention%5D=1&data%5Balerts%5D%5Bpoll%5D=1&data%5Balerts%5D%5Bfollow_request%5D=1&data%5Balerts%5D%5Bstatus%5D=1 - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Authorization: - - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN - Connection: - - keep-alive - Content-Length: - - '489' - Content-Type: - - application/x-www-form-urlencoded - User-Agent: - - tests/v311 - method: POST - uri: http://localhost:3000/api/v1/push/subscription - response: - body: - string: '{"id":5,"endpoint":"https://example.com","alerts":{"mention":true,"status":true,"reblog":true,"follow":true,"follow_request":true,"favourite":true,"poll":true},"server_key":"BFu6DBpfcm8_h8gm3rHUkfaOLg7azvYN_auFI4KcNuh5SLBVMhTkKKvUaLENtA_c6v5Hmrucvh0WwsN1o9NFQRU="}' - headers: - Cache-Control: - - no-store - Content-Security-Policy: - - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src - ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; - style-src ''self'' http://localhost:3000 ''nonce-bz+9uelNbajqElylgkM2Gg==''; - media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' - https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' - data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 - ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' - ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; - worker-src ''self'' blob: http://localhost:3000' - Content-Type: - - application/json; charset=utf-8 - ETag: - - W/"083c3b807a4d145ec452ffd03c768d76" - Referrer-Policy: - - strict-origin-when-cross-origin - Transfer-Encoding: - - chunked - Vary: - - Accept, Origin - X-Content-Type-Options: - - nosniff - X-Download-Options: - - noopen - X-Frame-Options: - - SAMEORIGIN - X-Permitted-Cross-Domain-Policies: - - none - X-Request-Id: - - abb4aec7-a93a-4a99-ba4c-5a290bbbf782 - X-Runtime: - - '0.027761' - X-XSS-Protection: - - 1; mode=block - status: - code: 200 - message: OK -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Authorization: - - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN - Connection: - - keep-alive - User-Agent: - - tests/v311 - method: GET - uri: http://localhost:3000/api/v1/push/subscription - response: - body: - string: '{"id":5,"endpoint":"https://example.com","alerts":{"mention":true,"status":true,"reblog":true,"follow":true,"follow_request":true,"favourite":true,"poll":true},"server_key":"BFu6DBpfcm8_h8gm3rHUkfaOLg7azvYN_auFI4KcNuh5SLBVMhTkKKvUaLENtA_c6v5Hmrucvh0WwsN1o9NFQRU="}' - headers: - Cache-Control: - - no-store - Content-Security-Policy: - - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src - ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; - style-src ''self'' http://localhost:3000 ''nonce-4kBQ0XWCASsxXi+/Z0W5jA==''; - media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' - https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' - data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 - ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' - ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; - worker-src ''self'' blob: http://localhost:3000' - Content-Type: - - application/json; charset=utf-8 - ETag: - - W/"083c3b807a4d145ec452ffd03c768d76" - Referrer-Policy: - - strict-origin-when-cross-origin - Transfer-Encoding: - - chunked - Vary: - - Accept, Origin - X-Content-Type-Options: - - nosniff - X-Download-Options: - - noopen - X-Frame-Options: - - SAMEORIGIN - X-Permitted-Cross-Domain-Policies: - - none - X-Request-Id: - - eeb007da-a24b-4e31-b962-30ef0ba3bc16 - X-Runtime: - - '0.008203' - X-XSS-Protection: - - 1; mode=block - status: - code: 200 - message: OK -version: 1 diff --git a/tests/test_create_app.py b/tests/test_create_app.py index 8a7ea62..c7282df 100644 --- a/tests/test_create_app.py +++ b/tests/test_create_app.py @@ -70,5 +70,24 @@ def test_app_account_create(): # We can also test resending (marginally) test_app_api.email_resend_confirmation() - - +@pytest.mark.vcr(match_on=['path']) +def test_app_account_create_invalid(): + suffix = str(time.time()).replace(".", "")[-5:] + + test_app = test_app = Mastodon.create_app( + "mastodon.py generated test app", + api_base_url="http://localhost:3000/" + ) + + test_app_api = Mastodon( + test_app[0], + test_app[1], + api_base_url="http://localhost:3000/" + ) + test_token, error = test_app_api.create_account("coolguy" + suffix, "", "email@localhost" + suffix, agreement=False, return_detailed_error=True) + assert test_token is None + assert "details" in error + assert "password" in error.details + assert "password" in error.details + assert not "username" in error.details + \ No newline at end of file