diff options
Diffstat (limited to 'services/fxaccounts/tests/xpcshell/test_profile_client.js')
-rw-r--r-- | services/fxaccounts/tests/xpcshell/test_profile_client.js | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/services/fxaccounts/tests/xpcshell/test_profile_client.js b/services/fxaccounts/tests/xpcshell/test_profile_client.js new file mode 100644 index 000000000..2243da3aa --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_profile_client.js @@ -0,0 +1,411 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://gre/modules/FxAccountsCommon.js"); +Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm"); + +const STATUS_SUCCESS = 200; + +/** + * Mock request responder + * @param {String} response + * Mocked raw response from the server + * @returns {Function} + */ +var mockResponse = function (response) { + let Request = function (requestUri) { + // Store the request uri so tests can inspect it + Request._requestUri = requestUri; + return { + setHeader: function () {}, + get: function () { + this.response = response; + this.onComplete(); + } + }; + }; + + return Request; +}; + +// A simple mock FxA that hands out tokens without checking them and doesn't +// expect tokens to be revoked. We have specific token tests further down that +// has more checks here. +var mockFxa = { + getOAuthToken(options) { + do_check_eq(options.scope, "profile"); + return "token"; + } +} + +const PROFILE_OPTIONS = { + serverURL: "http://127.0.0.1:1111/v1", + fxa: mockFxa, +}; + +/** + * Mock request error responder + * @param {Error} error + * Error object + * @returns {Function} + */ +var mockResponseError = function (error) { + return function () { + return { + setHeader: function () {}, + get: function () { + this.onComplete(error); + } + }; + }; +}; + +add_test(function successfulResponse () { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + status: STATUS_SUCCESS, + body: "{\"email\":\"someone@restmail.net\",\"uid\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}", + }; + + client._Request = new mockResponse(response); + client.fetchProfile() + .then( + function (result) { + do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/profile"); + do_check_eq(result.email, "someone@restmail.net"); + do_check_eq(result.uid, "0d5c1a89b8c54580b8e3e8adadae864a"); + run_next_test(); + } + ); +}); + +add_test(function parseErrorResponse () { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + status: STATUS_SUCCESS, + body: "unexpected", + }; + + client._Request = new mockResponse(response); + client.fetchProfile() + .then( + null, + function (e) { + do_check_eq(e.name, "FxAccountsProfileClientError"); + do_check_eq(e.code, STATUS_SUCCESS); + do_check_eq(e.errno, ERRNO_PARSE); + do_check_eq(e.error, ERROR_PARSE); + do_check_eq(e.message, "unexpected"); + run_next_test(); + } + ); +}); + +add_test(function serverErrorResponse () { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + status: 500, + body: "{ \"code\": 500, \"errno\": 100, \"error\": \"Bad Request\", \"message\": \"Something went wrong\", \"reason\": \"Because the internet\" }", + }; + + client._Request = new mockResponse(response); + client.fetchProfile() + .then( + null, + function (e) { + do_check_eq(e.name, "FxAccountsProfileClientError"); + do_check_eq(e.code, 500); + do_check_eq(e.errno, 100); + do_check_eq(e.error, "Bad Request"); + do_check_eq(e.message, "Something went wrong"); + run_next_test(); + } + ); +}); + +// Test that we get a token, then if we get a 401 we revoke it, get a new one +// and retry. +add_test(function server401ResponseThenSuccess () { + // The last token we handed out. + let lastToken = -1; + // The number of times our removeCachedOAuthToken function was called. + let numTokensRemoved = 0; + + let mockFxa = { + getOAuthToken(options) { + do_check_eq(options.scope, "profile"); + return "" + ++lastToken; // tokens are strings. + }, + removeCachedOAuthToken(options) { + // This test never has more than 1 token alive at once, so the token + // being revoked must always be the last token we handed out. + do_check_eq(parseInt(options.token), lastToken); + ++numTokensRemoved; + } + } + let profileOptions = { + serverURL: "http://127.0.0.1:1111/v1", + fxa: mockFxa, + }; + let client = new FxAccountsProfileClient(profileOptions); + + // 2 responses - first one implying the token has expired, second works. + let responses = [ + { + status: 401, + body: "{ \"code\": 401, \"errno\": 100, \"error\": \"Token expired\", \"message\": \"That token is too old\", \"reason\": \"Because security\" }", + }, + { + success: true, + status: STATUS_SUCCESS, + body: "{\"avatar\":\"http://example.com/image.jpg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}", + }, + ]; + + let numRequests = 0; + let numAuthHeaders = 0; + // Like mockResponse but we want access to headers etc. + client._Request = function(requestUri) { + return { + setHeader: function (name, value) { + if (name == "Authorization") { + numAuthHeaders++; + do_check_eq(value, "Bearer " + lastToken); + } + }, + get: function () { + this.response = responses[numRequests]; + ++numRequests; + this.onComplete(); + } + }; + } + + client.fetchProfile() + .then(result => { + do_check_eq(result.avatar, "http://example.com/image.jpg"); + do_check_eq(result.id, "0d5c1a89b8c54580b8e3e8adadae864a"); + // should have been exactly 2 requests and exactly 2 auth headers. + do_check_eq(numRequests, 2); + do_check_eq(numAuthHeaders, 2); + // and we should have seen one token revoked. + do_check_eq(numTokensRemoved, 1); + + run_next_test(); + } + ); +}); + +// Test that we get a token, then if we get a 401 we revoke it, get a new one +// and retry - but we *still* get a 401 on the retry, so the caller sees that. +add_test(function server401ResponsePersists () { + // The last token we handed out. + let lastToken = -1; + // The number of times our removeCachedOAuthToken function was called. + let numTokensRemoved = 0; + + let mockFxa = { + getOAuthToken(options) { + do_check_eq(options.scope, "profile"); + return "" + ++lastToken; // tokens are strings. + }, + removeCachedOAuthToken(options) { + // This test never has more than 1 token alive at once, so the token + // being revoked must always be the last token we handed out. + do_check_eq(parseInt(options.token), lastToken); + ++numTokensRemoved; + } + } + let profileOptions = { + serverURL: "http://127.0.0.1:1111/v1", + fxa: mockFxa, + }; + let client = new FxAccountsProfileClient(profileOptions); + + let response = { + status: 401, + body: "{ \"code\": 401, \"errno\": 100, \"error\": \"It's not your token, it's you!\", \"message\": \"I don't like you\", \"reason\": \"Because security\" }", + }; + + let numRequests = 0; + let numAuthHeaders = 0; + client._Request = function(requestUri) { + return { + setHeader: function (name, value) { + if (name == "Authorization") { + numAuthHeaders++; + do_check_eq(value, "Bearer " + lastToken); + } + }, + get: function () { + this.response = response; + ++numRequests; + this.onComplete(); + } + }; + } + + client.fetchProfile().then( + null, + function (e) { + do_check_eq(e.name, "FxAccountsProfileClientError"); + do_check_eq(e.code, 401); + do_check_eq(e.errno, 100); + do_check_eq(e.error, "It's not your token, it's you!"); + // should have been exactly 2 requests and exactly 2 auth headers. + do_check_eq(numRequests, 2); + do_check_eq(numAuthHeaders, 2); + // and we should have seen both tokens revoked. + do_check_eq(numTokensRemoved, 2); + run_next_test(); + } + ); +}); + +add_test(function networkErrorResponse () { + let client = new FxAccountsProfileClient({ + serverURL: "http://", + fxa: mockFxa, + }); + client.fetchProfile() + .then( + null, + function (e) { + do_check_eq(e.name, "FxAccountsProfileClientError"); + do_check_eq(e.code, null); + do_check_eq(e.errno, ERRNO_NETWORK); + do_check_eq(e.error, ERROR_NETWORK); + run_next_test(); + } + ); +}); + +add_test(function unsupportedMethod () { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + + return client._createRequest("/profile", "PUT") + .then( + null, + function (e) { + do_check_eq(e.name, "FxAccountsProfileClientError"); + do_check_eq(e.code, ERROR_CODE_METHOD_NOT_ALLOWED); + do_check_eq(e.errno, ERRNO_NETWORK); + do_check_eq(e.error, ERROR_NETWORK); + do_check_eq(e.message, ERROR_MSG_METHOD_NOT_ALLOWED); + run_next_test(); + } + ); +}); + +add_test(function onCompleteRequestError () { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + client._Request = new mockResponseError(new Error("onComplete error")); + client.fetchProfile() + .then( + null, + function (e) { + do_check_eq(e.name, "FxAccountsProfileClientError"); + do_check_eq(e.code, null); + do_check_eq(e.errno, ERRNO_NETWORK); + do_check_eq(e.error, ERROR_NETWORK); + do_check_eq(e.message, "Error: onComplete error"); + run_next_test(); + } + ); +}); + +add_test(function fetchProfileImage_successfulResponse () { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + status: STATUS_SUCCESS, + body: "{\"avatar\":\"http://example.com/image.jpg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}", + }; + + client._Request = new mockResponse(response); + client.fetchProfileImage() + .then( + function (result) { + do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/avatar"); + do_check_eq(result.avatar, "http://example.com/image.jpg"); + do_check_eq(result.id, "0d5c1a89b8c54580b8e3e8adadae864a"); + run_next_test(); + } + ); +}); + +add_test(function constructorTests() { + validationHelper(undefined, + "Error: Missing 'serverURL' configuration option"); + + validationHelper({}, + "Error: Missing 'serverURL' configuration option"); + + validationHelper({ serverURL: "badUrl" }, + "Error: Invalid 'serverURL'"); + + run_next_test(); +}); + +add_test(function errorTests() { + let error1 = new FxAccountsProfileClientError(); + do_check_eq(error1.name, "FxAccountsProfileClientError"); + do_check_eq(error1.code, null); + do_check_eq(error1.errno, ERRNO_UNKNOWN_ERROR); + do_check_eq(error1.error, ERROR_UNKNOWN); + do_check_eq(error1.message, null); + + let error2 = new FxAccountsProfileClientError({ + code: STATUS_SUCCESS, + errno: 1, + error: "Error", + message: "Something", + }); + let fields2 = error2._toStringFields(); + let statusCode = 1; + + do_check_eq(error2.name, "FxAccountsProfileClientError"); + do_check_eq(error2.code, STATUS_SUCCESS); + do_check_eq(error2.errno, statusCode); + do_check_eq(error2.error, "Error"); + do_check_eq(error2.message, "Something"); + + do_check_eq(fields2.name, "FxAccountsProfileClientError"); + do_check_eq(fields2.code, STATUS_SUCCESS); + do_check_eq(fields2.errno, statusCode); + do_check_eq(fields2.error, "Error"); + do_check_eq(fields2.message, "Something"); + + do_check_true(error2.toString().indexOf("Something") >= 0); + run_next_test(); +}); + +function run_test() { + run_next_test(); +} + +/** + * Quick way to test the "FxAccountsProfileClient" constructor. + * + * @param {Object} options + * FxAccountsProfileClient constructor options + * @param {String} expected + * Expected error message + * @returns {*} + */ +function validationHelper(options, expected) { + // add fxa to options - that missing isn't what we are testing here. + if (options) { + options.fxa = mockFxa; + } + try { + new FxAccountsProfileClient(options); + } catch (e) { + return do_check_eq(e.toString(), expected); + } + throw new Error("Validation helper error"); +} |