From 7d0a3ac8e5f7bc4a033764ece7196a725ae0acac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 8 May 2024 17:28:56 +0200 Subject: [PATCH 1/6] fix facebook oauth findOne on server to use async and add regression test --- .../facebook-oauth/facebook-oauth_tests.js | 33 ++++++++++++++++++ packages/facebook-oauth/facebook_server.js | 5 ++- packages/facebook-oauth/package.js | 6 ++++ packages/test-helpers/async_multi.js | 34 +++++++++++++++++++ packages/test-helpers/package.js | 2 +- 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 packages/facebook-oauth/facebook-oauth_tests.js diff --git a/packages/facebook-oauth/facebook-oauth_tests.js b/packages/facebook-oauth/facebook-oauth_tests.js new file mode 100644 index 00000000000..77897ad0ad1 --- /dev/null +++ b/packages/facebook-oauth/facebook-oauth_tests.js @@ -0,0 +1,33 @@ +Tinytest.addAsync( + 'facebook-oauth - run service oauth with mocked flow as expected', + async function (test) { + const oauthMock = disableBehaviours(OAuth, { + _fetch: () => Promise.resolve({ json: () => ({})}), + }); + + const serviceMockConfig = { service: 'facebook' }; + const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; + if (Meteor.isServer) { + await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); + const result = await Facebook.handleAuthFromAccessToken('testToken'); + test.isTrue(!!result?.serviceData, 'should return mocked result'); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['openSecret', '_fetch'], + 'should run mock oauth behaviors', + ); + } else if (Meteor.isClient) { + ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); + Facebook.requestCredential({}); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], + 'should run mock oauth behaviors', + ); + } + + oauthMock.stop(); + + return Promise.resolve(); + }, +); diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index 9952e58b2a1..2510a946f7e 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -73,7 +73,7 @@ function getAbsoluteUrlOptions(query) { * @returns {Promise} - Promise with an Object containing the accessToken and expiresIn (lifetime of token in seconds) */ const getTokenResponse = async (query) => { - const config = ServiceConfiguration.configurations.findOne({ + const config = await ServiceConfiguration.configurations.findOneAsync({ service: 'facebook', }); if (!config) throw new ServiceConfiguration.ConfigError(); @@ -117,7 +117,7 @@ const getTokenResponse = async (query) => { }; const getIdentity = async (accessToken, fields) => { - const config = ServiceConfiguration.configurations.findOne({ + const config = await ServiceConfiguration.configurations.findOneAsync({ service: 'facebook', }); if (!config) throw new ServiceConfiguration.ConfigError(); @@ -145,4 +145,3 @@ const getIdentity = async (accessToken, fields) => { Facebook.retrieveCredential = (credentialToken, credentialSecret) => OAuth.retrieveCredential(credentialToken, credentialSecret); - diff --git a/packages/facebook-oauth/package.js b/packages/facebook-oauth/package.js index 47e5fd8a577..1af8c4d0813 100644 --- a/packages/facebook-oauth/package.js +++ b/packages/facebook-oauth/package.js @@ -15,3 +15,9 @@ Package.onUse(api => { api.export('Facebook'); }); + +Package.onTest(function(api) { + api.use('facebook-oauth'); + api.use(['tinytest', 'ecmascript', 'test-helpers', 'oauth', 'oauth2', 'service-configuration']); + api.addFiles('facebook-oauth_tests.js'); +}); diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index 684a034bacd..dc77790b2de 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -220,3 +220,37 @@ runAndThrowIfNeeded = async (fn, test, shouldErrorOut) => { return result; }; + +disableBehaviours = function (obj, mockBehaviors = {}) { + const originalFunctions = {}; + const disabledRuns = []; + + // Store original functions + for (const key in obj) { + if (typeof obj[key] === 'function') { + originalFunctions[key] = obj[key]; + } + } + + // Mutate functions to identity functions + for (const key in obj) { + if (typeof obj[key] === 'function') { + obj[key] = function(...params) { + disabledRuns.push({ name: key, params }); + if (typeof mockBehaviors?.[key] === 'function') { + return mockBehaviors[key](...params); + } + return params?.[0]; + }; + } + } + + // Method to revert the mutation + const stop = function() { + for (const key in originalFunctions) { + obj[key] = originalFunctions[key]; + } + }; + + return { stop, disabledRuns }; +}; diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index ddc0ce33331..ee7d4a04120 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -30,7 +30,7 @@ Package.onUse(function (api) { 'renderToDiv', 'clickIt', 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', 'runAndThrowIfNeeded', - 'makeTestConnection', 'DomUtils']); + 'makeTestConnection', 'DomUtils', 'disableBehaviours']); api.addFiles('try_all_permutations.js'); api.addFiles('async_multi.js'); From 99fb5b77d8b0c484b53490d9ca49b503d5651a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 9 May 2024 14:46:43 +0200 Subject: [PATCH 2/6] implement waitUntil test helper and apply to a flaky test --- packages/mongo/mongo_livedata_tests.js | 39 ++++++++------------ packages/test-helpers/async_multi.js | 34 ------------------ packages/test-helpers/mock.js | 33 +++++++++++++++++ packages/test-helpers/package.js | 4 ++- packages/test-helpers/wait.js | 50 ++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 packages/test-helpers/mock.js create mode 100644 packages/test-helpers/wait.js diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 2f914c5422d..0eaa9135fa2 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -4342,14 +4342,11 @@ testAsyncMulti( test.equal(itemIds, ['a']); // temporary data accessible if (Meteor.isClient) { - return new Promise(resolve => { - Meteor.setTimeout(async () => { - items = await Collection.find().fetchAsync(); - itemIds = items.map(_item => _item._id); - test.equal(itemIds, []); // data IS NOT persisted - resolve(); - }, 1500); - }); + return waitUntil(async () => { + items = await Collection.find().fetchAsync(); + itemIds = items.map(_item => _item._id); + return itemIds?.length === []?.length; // data IS NOT persisted + }, { description: 'data IS NOT persisted'}); } return Promise.resolve(); @@ -4384,14 +4381,11 @@ testAsyncMulti( test.equal(itemIds, ['a']); // temporary data accessible if (Meteor.isClient) { - return new Promise(resolve => { - Meteor.setTimeout(async () => { - items = await Collection.find().fetchAsync(); - itemIds = items.map(_item => _item._id); - test.equal(itemIds, ['a']); // data is persisted - resolve(); - }, 1500); - }); + return waitUntil(async () => { + items = await Collection.find().fetchAsync(); + itemIds = items.map(_item => _item._id); + return itemIds?.length === 1 && itemIds[0] === 'a'; // data is persisted + }, { description: 'data is persisted'}); } return Promise.resolve(); @@ -4443,14 +4437,11 @@ testAsyncMulti( await promise.serverPromise; } catch (e) { // error as no insert method enabled on server - return new Promise(resolve => { - Meteor.setTimeout(async () => { - items = await Collection.find().fetchAsync(); - itemIds = items.map(_item => _item._id); - test.equal(itemIds, []); // data IS NOT persisted - resolve(); - }, 1500); - }); + return waitUntil(async () => { + items = await Collection.find().fetchAsync(); + itemIds = items.map(_item => _item._id); + return itemIds?.length === []?.length ; // data IS NOT persisted + }, { description: 'data IS NOT persisted'}); } } else { return Promise.resolve(); diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index dc77790b2de..684a034bacd 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -220,37 +220,3 @@ runAndThrowIfNeeded = async (fn, test, shouldErrorOut) => { return result; }; - -disableBehaviours = function (obj, mockBehaviors = {}) { - const originalFunctions = {}; - const disabledRuns = []; - - // Store original functions - for (const key in obj) { - if (typeof obj[key] === 'function') { - originalFunctions[key] = obj[key]; - } - } - - // Mutate functions to identity functions - for (const key in obj) { - if (typeof obj[key] === 'function') { - obj[key] = function(...params) { - disabledRuns.push({ name: key, params }); - if (typeof mockBehaviors?.[key] === 'function') { - return mockBehaviors[key](...params); - } - return params?.[0]; - }; - } - } - - // Method to revert the mutation - const stop = function() { - for (const key in originalFunctions) { - obj[key] = originalFunctions[key]; - } - }; - - return { stop, disabledRuns }; -}; diff --git a/packages/test-helpers/mock.js b/packages/test-helpers/mock.js new file mode 100644 index 00000000000..26ed2d58d3f --- /dev/null +++ b/packages/test-helpers/mock.js @@ -0,0 +1,33 @@ +disableBehaviours = function _disableBehaviours(obj, mockBehaviors = {}) { + const originalFunctions = {}; + const disabledRuns = []; + + // Store original functions + for (const key in obj) { + if (typeof obj[key] === 'function') { + originalFunctions[key] = obj[key]; + } + } + + // Mutate functions to identity functions + for (const key in obj) { + if (typeof obj[key] === 'function') { + obj[key] = function(...params) { + disabledRuns.push({ name: key, params }); + if (typeof mockBehaviors?.[key] === 'function') { + return mockBehaviors[key](...params); + } + return params?.[0]; + }; + } + } + + // Method to revert the mutation + const stop = function() { + for (const key in originalFunctions) { + obj[key] = originalFunctions[key]; + } + }; + + return { stop, disabledRuns }; +}; diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index ee7d4a04120..94204e16636 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -30,7 +30,7 @@ Package.onUse(function (api) { 'renderToDiv', 'clickIt', 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', 'runAndThrowIfNeeded', - 'makeTestConnection', 'DomUtils', 'disableBehaviours']); + 'makeTestConnection', 'DomUtils', 'disableBehaviours', 'waitUntil']); api.addFiles('try_all_permutations.js'); api.addFiles('async_multi.js'); @@ -40,6 +40,8 @@ Package.onUse(function (api) { api.addFiles('render_div.js'); api.addFiles('current_style.js'); api.addFiles('callback_logger.js'); + api.addFiles('mock.js'); + api.addFiles('wait.js'); api.addFiles('domutils.js', 'client'); api.addFiles('connection.js', 'server'); }); diff --git a/packages/test-helpers/wait.js b/packages/test-helpers/wait.js new file mode 100644 index 00000000000..31eb7652363 --- /dev/null +++ b/packages/test-helpers/wait.js @@ -0,0 +1,50 @@ +function isPromise(obj) { + return obj && typeof obj.then === 'function'; +} + +waitUntil = function _waitUntil(checkFunction, { timeout = 15_000, interval = 200, leading = false, description = '' } = {}) { + let waitTime = interval; + console.log("-> waitTime", waitTime); + return new Promise((resolve, reject) => { + if (leading && checkFunction()) { + resolve(); + return; + } + const handler = setInterval(() => { + const shouldWait = checkFunction(); + if (isPromise(shouldWait)) { + shouldWait + .then(_shouldWait => { + if (_shouldWait) { + resolve(); + clearInterval(handler); + return; + } + + if (waitTime > timeout) { + console.error(description, 'timed out'); + reject(); + clearInterval(handler); + } + + waitTime += interval; + }) + .catch((_error) => { + console.error(description, _error?.message); + reject(); + clearInterval(handler); + }); + } else if (shouldWait) { + resolve(); + clearInterval(handler); + } else { + if (waitTime > timeout) { + console.error(description, 'timed out'); + reject(); + clearInterval(handler); + } + waitTime += interval; + } + }, interval); + }); +}; From e07b2a8ca4fd070df3ff764f57892e801036e23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 9 May 2024 14:53:25 +0200 Subject: [PATCH 3/6] remove log --- packages/test-helpers/wait.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/test-helpers/wait.js b/packages/test-helpers/wait.js index 31eb7652363..7b29205fc64 100644 --- a/packages/test-helpers/wait.js +++ b/packages/test-helpers/wait.js @@ -4,7 +4,6 @@ function isPromise(obj) { waitUntil = function _waitUntil(checkFunction, { timeout = 15_000, interval = 200, leading = false, description = '' } = {}) { let waitTime = interval; - console.log("-> waitTime", waitTime); return new Promise((resolve, reject) => { if (leading && checkFunction()) { resolve(); From d397f770cf6f3fc54d68224010b8a4503c13c81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 9 May 2024 16:01:38 +0200 Subject: [PATCH 4/6] ensure all accounts oauth packages adapt to async and have basic test coverage --- .../facebook-oauth/facebook-oauth_tests.js | 9 ++--- packages/facebook-oauth/facebook_server.js | 1 + packages/github-oauth/github-oauth_tests.js | 34 +++++++++++++++++++ packages/github-oauth/github_server.js | 8 ++--- packages/github-oauth/package.js | 7 ++++ packages/google-oauth/google-oauth_tests.js | 34 +++++++++++++++++++ packages/google-oauth/google_server.js | 8 ++--- packages/google-oauth/package.js | 6 ++++ packages/meetup-oauth/meetup-oauth_tests.js | 34 +++++++++++++++++++ packages/meetup-oauth/meetup_server.js | 2 +- packages/meetup-oauth/package.js | 6 ++++ .../meteor-developer-oauth_tests.js | 34 +++++++++++++++++++ packages/meteor-developer-oauth/package.js | 6 ++++ packages/oauth/oauth_server.js | 2 ++ packages/weibo-oauth/package.js | 6 ++++ packages/weibo-oauth/weibo-oauth_tests.js | 34 +++++++++++++++++++ packages/weibo-oauth/weibo_server.js | 2 +- 17 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 packages/github-oauth/github-oauth_tests.js create mode 100644 packages/google-oauth/google-oauth_tests.js create mode 100644 packages/meetup-oauth/meetup-oauth_tests.js create mode 100644 packages/meteor-developer-oauth/meteor-developer-oauth_tests.js create mode 100644 packages/weibo-oauth/weibo-oauth_tests.js diff --git a/packages/facebook-oauth/facebook-oauth_tests.js b/packages/facebook-oauth/facebook-oauth_tests.js index 77897ad0ad1..972fe6f60e4 100644 --- a/packages/facebook-oauth/facebook-oauth_tests.js +++ b/packages/facebook-oauth/facebook-oauth_tests.js @@ -2,18 +2,19 @@ Tinytest.addAsync( 'facebook-oauth - run service oauth with mocked flow as expected', async function (test) { const oauthMock = disableBehaviours(OAuth, { - _fetch: () => Promise.resolve({ json: () => ({})}), + _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken' })}), }); - const serviceMockConfig = { service: 'facebook' }; + const service = 'facebook'; + const serviceMockConfig = { service }; const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; if (Meteor.isServer) { await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); - const result = await Facebook.handleAuthFromAccessToken('testToken'); + const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( oauthMock.disabledRuns.map(({ name }) => name), - ['openSecret', '_fetch'], + ['_redirectUri','openSecret','_fetch','openSecret','_fetch'], 'should run mock oauth behaviors', ); } else if (Meteor.isClient) { diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index 2510a946f7e..a260f7eeec3 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -96,6 +96,7 @@ const getTokenResponse = async (query) => { .then((res) => res.json()) .then(data => { const fbAccessToken = data.access_token; + console.log("-> fbAccessToken", fbAccessToken); const fbExpires = data.expires_in; if (!fbAccessToken) { throw new Error("Failed to complete OAuth handshake with facebook " + diff --git a/packages/github-oauth/github-oauth_tests.js b/packages/github-oauth/github-oauth_tests.js new file mode 100644 index 00000000000..38f4cee4c40 --- /dev/null +++ b/packages/github-oauth/github-oauth_tests.js @@ -0,0 +1,34 @@ +Tinytest.addAsync( + 'github-oauth - run service oauth with mocked flow as expected', + async function (test) { + const oauthMock = disableBehaviours(OAuth, { + _fetch: () => Promise.resolve({ json: () => ([{ access_token: 'testToken', email: { primary: 'email' } }])}), + }); + + const service = 'github'; + const serviceMockConfig = { service }; + const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; + if (Meteor.isServer) { + await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); + const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); + test.isTrue(!!result?.serviceData, 'should return mocked result'); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_redirectUri','_fetch','_fetch','_fetch','sealSecret'], + 'should run mock oauth behaviors', + ); + } else if (Meteor.isClient) { + ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); + Github.requestCredential({}); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], + 'should run mock oauth behaviors', + ); + } + + oauthMock.stop(); + + return Promise.resolve(); + }, +); diff --git a/packages/github-oauth/github_server.js b/packages/github-oauth/github_server.js index 7b4f36f5f6d..9b3c4940e1e 100644 --- a/packages/github-oauth/github_server.js +++ b/packages/github-oauth/github_server.js @@ -29,7 +29,7 @@ let userAgent = 'Meteor'; if (Meteor.release) userAgent += `/${Meteor.release}`; const getAccessToken = async (query) => { - const config = ServiceConfiguration.configurations.findOne({ + const config = await ServiceConfiguration.configurations.findOneAsync({ service: 'github' }); if (!config) throw new ServiceConfiguration.ConfigError(); @@ -45,7 +45,7 @@ const getAccessToken = async (query) => { config ) }); - const request = await fetch( + const request = await OAuth._fetch( `https://github.com/login/oauth/access_token?${content.toString()}`, { method: 'POST', @@ -76,7 +76,7 @@ const getAccessToken = async (query) => { const getIdentity = async (accessToken) => { try { - const request = await fetch('https://api.github.com/user', { + const request = await OAuth._fetch('https://api.github.com/user', { method: 'GET', headers: { Accept: 'application/json', @@ -95,7 +95,7 @@ const getIdentity = async (accessToken) => { const getEmails = async (accessToken) => { try { - const request = await fetch('https://api.github.com/user/emails', { + const request = await OAuth._fetch('https://api.github.com/user/emails', { method: 'GET', headers: { 'User-Agent': userAgent, diff --git a/packages/github-oauth/package.js b/packages/github-oauth/package.js index 5667b7f8a91..c9c612fa20b 100644 --- a/packages/github-oauth/package.js +++ b/packages/github-oauth/package.js @@ -9,6 +9,7 @@ Package.onUse(api => { api.use('oauth', ['client', 'server']); api.use('fetch', 'server'); api.use('random', 'client'); + api.use('accounts-base', ['client', 'server']); api.use('service-configuration', ['client', 'server']); api.addFiles('github_client.js', 'client'); @@ -16,3 +17,9 @@ Package.onUse(api => { api.export('Github'); }); + +Package.onTest(function(api) { + api.use('github-oauth'); + api.use(['tinytest', 'ecmascript', 'test-helpers', 'oauth', 'oauth2', 'service-configuration']); + api.addFiles('github-oauth_tests.js'); +}); diff --git a/packages/google-oauth/google-oauth_tests.js b/packages/google-oauth/google-oauth_tests.js new file mode 100644 index 00000000000..dd177faefe6 --- /dev/null +++ b/packages/google-oauth/google-oauth_tests.js @@ -0,0 +1,34 @@ +Tinytest.addAsync( + 'google-oauth - run service oauth with mocked flow as expected', + async function (test) { + const oauthMock = disableBehaviours(OAuth, { + _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken', scope: '1 2 3' })}), + }); + + const service = 'google'; + const serviceMockConfig = { service }; + const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; + if (Meteor.isServer) { + await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); + const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); + test.isTrue(!!result?.serviceData, 'should return mocked result'); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['openSecret','_redirectUri','_fetch','_fetch','_fetch'], + 'should run mock oauth behaviors', + ); + } else if (Meteor.isClient) { + ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); + Google.requestCredential({}); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], + 'should run mock oauth behaviors', + ); + } + + oauthMock.stop(); + + return Promise.resolve(); + }, +); diff --git a/packages/google-oauth/google_server.js b/packages/google-oauth/google_server.js index f1a302c754e..4fe47bd28ee 100644 --- a/packages/google-oauth/google_server.js +++ b/packages/google-oauth/google_server.js @@ -125,7 +125,7 @@ Accounts.registerLoginHandler(async (request) => { // - expiresIn: lifetime of token in seconds // - refreshToken, if this is the first authorization request const getTokens = async (query, callback) => { - const config = ServiceConfiguration.configurations.findOne({ + const config = await ServiceConfiguration.configurations.findOneAsync({ service: 'google', }); if (!config) throw new ServiceConfiguration.ConfigError(); @@ -137,7 +137,7 @@ const getTokens = async (query, callback) => { redirect_uri: OAuth._redirectUri('google', config), grant_type: 'authorization_code', }); - const request = await fetch('https://accounts.google.com/o/oauth2/token', { + const request = await OAuth._fetch('https://accounts.google.com/o/oauth2/token', { method: 'POST', headers: { Accept: 'application/json', @@ -174,7 +174,7 @@ const getIdentity = async (accessToken, callback) => { const content = new URLSearchParams({ access_token: accessToken }); let response; try { - const request = await fetch( + const request = await OAuth._fetch( `https://www.googleapis.com/oauth2/v1/userinfo?${content.toString()}`, { method: 'GET', @@ -194,7 +194,7 @@ const getScopes = async (accessToken, callback) => { const content = new URLSearchParams({ access_token: accessToken }); let response; try { - const request = await fetch( + const request = await OAuth._fetch( `https://www.googleapis.com/oauth2/v1/tokeninfo?${content.toString()}`, { method: 'GET', diff --git a/packages/google-oauth/package.js b/packages/google-oauth/package.js index 242a37cc658..3e51a2b81fa 100644 --- a/packages/google-oauth/package.js +++ b/packages/google-oauth/package.js @@ -23,3 +23,9 @@ Package.onUse(api => { api.export('Google'); }); + +Package.onTest(function(api) { + api.use('google-oauth'); + api.use(['tinytest', 'ecmascript', 'test-helpers', 'oauth', 'oauth2', 'service-configuration']); + api.addFiles('google-oauth_tests.js'); +}); diff --git a/packages/meetup-oauth/meetup-oauth_tests.js b/packages/meetup-oauth/meetup-oauth_tests.js new file mode 100644 index 00000000000..d56d64959c1 --- /dev/null +++ b/packages/meetup-oauth/meetup-oauth_tests.js @@ -0,0 +1,34 @@ +Tinytest.addAsync( + 'meetup-oauth - run service oauth with mocked flow as expected', + async function (test) { + const oauthMock = disableBehaviours(OAuth, { + _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken' })}), + }); + + const service = 'meetup'; + const serviceMockConfig = { service }; + const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; + if (Meteor.isServer) { + await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); + const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); + test.isTrue(!!result?.serviceData, 'should return mocked result'); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['openSecret','_redirectUri','_addValuesToQueryParams','_fetch','_addValuesToQueryParams','_fetch'], + 'should run mock oauth behaviors', + ); + } else if (Meteor.isClient) { + ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); + Meetup.requestCredential({}); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], + 'should run mock oauth behaviors', + ); + } + + oauthMock.stop(); + + return Promise.resolve(); + }, +); diff --git a/packages/meetup-oauth/meetup_server.js b/packages/meetup-oauth/meetup_server.js index bfc465c7b31..a4e92d1a23d 100644 --- a/packages/meetup-oauth/meetup_server.js +++ b/packages/meetup-oauth/meetup_server.js @@ -34,7 +34,7 @@ OAuth.registerService('meetup', 2, null, async query => { }); const getAccessToken = async query => { - const config = ServiceConfiguration.configurations.findOne({service: 'meetup'}); + const config = await ServiceConfiguration.configurations.findOneAsync({service: 'meetup'}); if (!config) throw new ServiceConfiguration.ConfigError(); diff --git a/packages/meetup-oauth/package.js b/packages/meetup-oauth/package.js index 33132163e77..cbd0fb443c4 100644 --- a/packages/meetup-oauth/package.js +++ b/packages/meetup-oauth/package.js @@ -15,3 +15,9 @@ Package.onUse(api => { api.export('Meetup'); }); + +Package.onTest(function(api) { + api.use('meetup-oauth'); + api.use(['tinytest', 'ecmascript', 'test-helpers', 'oauth', 'oauth2', 'service-configuration']); + api.addFiles('meetup-oauth_tests.js'); +}); diff --git a/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js b/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js new file mode 100644 index 00000000000..57016539b5c --- /dev/null +++ b/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js @@ -0,0 +1,34 @@ +Tinytest.addAsync( + 'meteor-developer-oauth - run service oauth with mocked flow as expected', + async function (test) { + const oauthMock = disableBehaviours(OAuth, { + _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken' })}), + }); + + const service = 'meteor-developer'; + const serviceMockConfig = { service }; + const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; + if (Meteor.isServer) { + await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); + const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); + test.isTrue(!!result?.serviceData, 'should return mocked result'); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['openSecret','_redirectUri','_addValuesToQueryParams','_fetch','_fetch','sealSecret'], + 'should run mock oauth behaviors', + ); + } else if (Meteor.isClient) { + ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); + MeteorDeveloperAccounts.requestCredential({}); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_loginStyle','_stateParam','_redirectUri','launchLogin'], + 'should run mock oauth behaviors', + ); + } + + oauthMock.stop(); + + return Promise.resolve(); + }, +); diff --git a/packages/meteor-developer-oauth/package.js b/packages/meteor-developer-oauth/package.js index 7e8f90201e3..a6d127f468b 100644 --- a/packages/meteor-developer-oauth/package.js +++ b/packages/meteor-developer-oauth/package.js @@ -15,3 +15,9 @@ Package.onUse(api => { api.export('MeteorDeveloperAccounts'); }); + +Package.onTest(function(api) { + api.use('meteor-developer-oauth'); + api.use(['tinytest', 'ecmascript', 'test-helpers', 'oauth', 'oauth2', 'service-configuration']); + api.addFiles('meteor-developer-oauth_tests.js'); +}); diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index 443f59bef91..0e1e9d94912 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -211,6 +211,8 @@ WebApp.handlers.use(middleware); OAuthTest.middleware = middleware; +OAuthTest.registeredServices = registeredServices; + // Handle /_oauth/* paths and extract the service name. // // @returns {String|null} e.g. "facebook", or null if this isn't an diff --git a/packages/weibo-oauth/package.js b/packages/weibo-oauth/package.js index a69539f0cb3..9e80191b221 100644 --- a/packages/weibo-oauth/package.js +++ b/packages/weibo-oauth/package.js @@ -14,3 +14,9 @@ Package.onUse(api => { api.export('Weibo'); }); + +Package.onTest(function(api) { + api.use('weibo-oauth'); + api.use(['tinytest', 'ecmascript', 'test-helpers', 'oauth', 'oauth2', 'service-configuration']); + api.addFiles('weibo-oauth_tests.js'); +}); diff --git a/packages/weibo-oauth/weibo-oauth_tests.js b/packages/weibo-oauth/weibo-oauth_tests.js new file mode 100644 index 00000000000..a124d513b21 --- /dev/null +++ b/packages/weibo-oauth/weibo-oauth_tests.js @@ -0,0 +1,34 @@ +Tinytest.addAsync( + 'weibo-oauth - run service oauth with mocked flow as expected', + async function (test) { + const oauthMock = disableBehaviours(OAuth, { + _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken', uid: '12345' })}), + }); + + const service = 'weibo'; + const serviceMockConfig = { service }; + const mockConfig = { clientId: "test", secret: "test", loginStyle: "popup" }; + if (Meteor.isServer) { + await ServiceConfiguration.configurations.upsertAsync(serviceMockConfig, { $set: mockConfig }); + const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); + test.isTrue(!!result?.serviceData, 'should return mocked result'); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['openSecret','_redirectUri','_fetch','_fetch'], + 'should run mock oauth behaviors', + ); + } else if (Meteor.isClient) { + ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); + Weibo.requestCredential({}); + test.equal( + oauthMock.disabledRuns.map(({ name }) => name), + ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], + 'should run mock oauth behaviors', + ); + } + + oauthMock.stop(); + + return Promise.resolve(); + }, +); diff --git a/packages/weibo-oauth/weibo_server.js b/packages/weibo-oauth/weibo_server.js index 24d56438fdb..44ef8558dbf 100644 --- a/packages/weibo-oauth/weibo_server.js +++ b/packages/weibo-oauth/weibo_server.js @@ -32,7 +32,7 @@ OAuth.registerService('weibo', 2, null, async query => { // - access_token // - expires_in: lifetime of this token in seconds (5 years(!) right now) const getTokenResponse = async (query) => { - const config = ServiceConfiguration.configurations.findOne({ + const config = await ServiceConfiguration.configurations.findOneAsync({ service: 'weibo', }); if (!config) throw new ServiceConfiguration.ConfigError(); From f795e0a063205572794dcadec4f7946ba42b0bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 9 May 2024 16:11:11 +0200 Subject: [PATCH 5/6] use better naming --- packages/facebook-oauth/facebook-oauth_tests.js | 2 +- packages/github-oauth/github-oauth_tests.js | 2 +- packages/google-oauth/google-oauth_tests.js | 2 +- packages/meetup-oauth/meetup-oauth_tests.js | 2 +- packages/meteor-developer-oauth/meteor-developer-oauth_tests.js | 2 +- packages/test-helpers/mock.js | 2 +- packages/test-helpers/package.js | 2 +- packages/weibo-oauth/weibo-oauth_tests.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/facebook-oauth/facebook-oauth_tests.js b/packages/facebook-oauth/facebook-oauth_tests.js index 972fe6f60e4..10e62ce72c2 100644 --- a/packages/facebook-oauth/facebook-oauth_tests.js +++ b/packages/facebook-oauth/facebook-oauth_tests.js @@ -1,7 +1,7 @@ Tinytest.addAsync( 'facebook-oauth - run service oauth with mocked flow as expected', async function (test) { - const oauthMock = disableBehaviours(OAuth, { + const oauthMock = mockBehaviours(OAuth, { _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken' })}), }); diff --git a/packages/github-oauth/github-oauth_tests.js b/packages/github-oauth/github-oauth_tests.js index 38f4cee4c40..412ac5d2a00 100644 --- a/packages/github-oauth/github-oauth_tests.js +++ b/packages/github-oauth/github-oauth_tests.js @@ -1,7 +1,7 @@ Tinytest.addAsync( 'github-oauth - run service oauth with mocked flow as expected', async function (test) { - const oauthMock = disableBehaviours(OAuth, { + const oauthMock = mockBehaviours(OAuth, { _fetch: () => Promise.resolve({ json: () => ([{ access_token: 'testToken', email: { primary: 'email' } }])}), }); diff --git a/packages/google-oauth/google-oauth_tests.js b/packages/google-oauth/google-oauth_tests.js index dd177faefe6..f66285c7a01 100644 --- a/packages/google-oauth/google-oauth_tests.js +++ b/packages/google-oauth/google-oauth_tests.js @@ -1,7 +1,7 @@ Tinytest.addAsync( 'google-oauth - run service oauth with mocked flow as expected', async function (test) { - const oauthMock = disableBehaviours(OAuth, { + const oauthMock = mockBehaviours(OAuth, { _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken', scope: '1 2 3' })}), }); diff --git a/packages/meetup-oauth/meetup-oauth_tests.js b/packages/meetup-oauth/meetup-oauth_tests.js index d56d64959c1..a9426fa4338 100644 --- a/packages/meetup-oauth/meetup-oauth_tests.js +++ b/packages/meetup-oauth/meetup-oauth_tests.js @@ -1,7 +1,7 @@ Tinytest.addAsync( 'meetup-oauth - run service oauth with mocked flow as expected', async function (test) { - const oauthMock = disableBehaviours(OAuth, { + const oauthMock = mockBehaviours(OAuth, { _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken' })}), }); diff --git a/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js b/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js index 57016539b5c..dbdb96becf5 100644 --- a/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js +++ b/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js @@ -1,7 +1,7 @@ Tinytest.addAsync( 'meteor-developer-oauth - run service oauth with mocked flow as expected', async function (test) { - const oauthMock = disableBehaviours(OAuth, { + const oauthMock = mockBehaviours(OAuth, { _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken' })}), }); diff --git a/packages/test-helpers/mock.js b/packages/test-helpers/mock.js index 26ed2d58d3f..029f126fe2d 100644 --- a/packages/test-helpers/mock.js +++ b/packages/test-helpers/mock.js @@ -1,4 +1,4 @@ -disableBehaviours = function _disableBehaviours(obj, mockBehaviors = {}) { +mockBehaviours = function _mockBehaviours(obj, mockBehaviors = {}) { const originalFunctions = {}; const disabledRuns = []; diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 94204e16636..c8912fdbe23 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -30,7 +30,7 @@ Package.onUse(function (api) { 'renderToDiv', 'clickIt', 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', 'runAndThrowIfNeeded', - 'makeTestConnection', 'DomUtils', 'disableBehaviours', 'waitUntil']); + 'makeTestConnection', 'DomUtils', 'mockBehaviours', 'waitUntil']); api.addFiles('try_all_permutations.js'); api.addFiles('async_multi.js'); diff --git a/packages/weibo-oauth/weibo-oauth_tests.js b/packages/weibo-oauth/weibo-oauth_tests.js index a124d513b21..778c025ed2e 100644 --- a/packages/weibo-oauth/weibo-oauth_tests.js +++ b/packages/weibo-oauth/weibo-oauth_tests.js @@ -1,7 +1,7 @@ Tinytest.addAsync( 'weibo-oauth - run service oauth with mocked flow as expected', async function (test) { - const oauthMock = disableBehaviours(OAuth, { + const oauthMock = mockBehaviours(OAuth, { _fetch: () => Promise.resolve({ json: () => ({ access_token: 'testToken', uid: '12345' })}), }); From 44b0a2592387c4121d23c8d19635bd5f6fa0c042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 9 May 2024 16:19:43 +0200 Subject: [PATCH 6/6] use better naming --- packages/facebook-oauth/facebook-oauth_tests.js | 4 ++-- packages/github-oauth/github-oauth_tests.js | 4 ++-- packages/google-oauth/google-oauth_tests.js | 4 ++-- packages/meetup-oauth/meetup-oauth_tests.js | 4 ++-- .../meteor-developer-oauth/meteor-developer-oauth_tests.js | 4 ++-- packages/test-helpers/mock.js | 6 +++--- packages/weibo-oauth/weibo-oauth_tests.js | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/facebook-oauth/facebook-oauth_tests.js b/packages/facebook-oauth/facebook-oauth_tests.js index 10e62ce72c2..c4b4f4aa6b4 100644 --- a/packages/facebook-oauth/facebook-oauth_tests.js +++ b/packages/facebook-oauth/facebook-oauth_tests.js @@ -13,7 +13,7 @@ Tinytest.addAsync( const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_redirectUri','openSecret','_fetch','openSecret','_fetch'], 'should run mock oauth behaviors', ); @@ -21,7 +21,7 @@ Tinytest.addAsync( ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); Facebook.requestCredential({}); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], 'should run mock oauth behaviors', ); diff --git a/packages/github-oauth/github-oauth_tests.js b/packages/github-oauth/github-oauth_tests.js index 412ac5d2a00..5b79ef1f7be 100644 --- a/packages/github-oauth/github-oauth_tests.js +++ b/packages/github-oauth/github-oauth_tests.js @@ -13,7 +13,7 @@ Tinytest.addAsync( const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_redirectUri','_fetch','_fetch','_fetch','sealSecret'], 'should run mock oauth behaviors', ); @@ -21,7 +21,7 @@ Tinytest.addAsync( ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); Github.requestCredential({}); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], 'should run mock oauth behaviors', ); diff --git a/packages/google-oauth/google-oauth_tests.js b/packages/google-oauth/google-oauth_tests.js index f66285c7a01..d5bb686bd63 100644 --- a/packages/google-oauth/google-oauth_tests.js +++ b/packages/google-oauth/google-oauth_tests.js @@ -13,7 +13,7 @@ Tinytest.addAsync( const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['openSecret','_redirectUri','_fetch','_fetch','_fetch'], 'should run mock oauth behaviors', ); @@ -21,7 +21,7 @@ Tinytest.addAsync( ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); Google.requestCredential({}); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], 'should run mock oauth behaviors', ); diff --git a/packages/meetup-oauth/meetup-oauth_tests.js b/packages/meetup-oauth/meetup-oauth_tests.js index a9426fa4338..a4677598497 100644 --- a/packages/meetup-oauth/meetup-oauth_tests.js +++ b/packages/meetup-oauth/meetup-oauth_tests.js @@ -13,7 +13,7 @@ Tinytest.addAsync( const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['openSecret','_redirectUri','_addValuesToQueryParams','_fetch','_addValuesToQueryParams','_fetch'], 'should run mock oauth behaviors', ); @@ -21,7 +21,7 @@ Tinytest.addAsync( ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); Meetup.requestCredential({}); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], 'should run mock oauth behaviors', ); diff --git a/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js b/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js index dbdb96becf5..8a19ceb117e 100644 --- a/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js +++ b/packages/meteor-developer-oauth/meteor-developer-oauth_tests.js @@ -13,7 +13,7 @@ Tinytest.addAsync( const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['openSecret','_redirectUri','_addValuesToQueryParams','_fetch','_fetch','sealSecret'], 'should run mock oauth behaviors', ); @@ -21,7 +21,7 @@ Tinytest.addAsync( ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); MeteorDeveloperAccounts.requestCredential({}); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_loginStyle','_stateParam','_redirectUri','launchLogin'], 'should run mock oauth behaviors', ); diff --git a/packages/test-helpers/mock.js b/packages/test-helpers/mock.js index 029f126fe2d..b3bcc5dae3e 100644 --- a/packages/test-helpers/mock.js +++ b/packages/test-helpers/mock.js @@ -1,6 +1,6 @@ mockBehaviours = function _mockBehaviours(obj, mockBehaviors = {}) { const originalFunctions = {}; - const disabledRuns = []; + const mockedRuns = []; // Store original functions for (const key in obj) { @@ -13,7 +13,7 @@ mockBehaviours = function _mockBehaviours(obj, mockBehaviors = {}) { for (const key in obj) { if (typeof obj[key] === 'function') { obj[key] = function(...params) { - disabledRuns.push({ name: key, params }); + mockedRuns.push({ name: key, params }); if (typeof mockBehaviors?.[key] === 'function') { return mockBehaviors[key](...params); } @@ -29,5 +29,5 @@ mockBehaviours = function _mockBehaviours(obj, mockBehaviors = {}) { } }; - return { stop, disabledRuns }; + return { stop, mockedRuns }; }; diff --git a/packages/weibo-oauth/weibo-oauth_tests.js b/packages/weibo-oauth/weibo-oauth_tests.js index 778c025ed2e..260b483cd2a 100644 --- a/packages/weibo-oauth/weibo-oauth_tests.js +++ b/packages/weibo-oauth/weibo-oauth_tests.js @@ -13,7 +13,7 @@ Tinytest.addAsync( const result = await OAuthTest.registeredServices[service].handleOauthRequest({}); test.isTrue(!!result?.serviceData, 'should return mocked result'); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['openSecret','_redirectUri','_fetch','_fetch'], 'should run mock oauth behaviors', ); @@ -21,7 +21,7 @@ Tinytest.addAsync( ServiceConfiguration.configurations.insert({ ...serviceMockConfig, ...mockConfig }); Weibo.requestCredential({}); test.equal( - oauthMock.disabledRuns.map(({ name }) => name), + oauthMock.mockedRuns.map(({ name }) => name), ['_loginStyle', '_redirectUri', '_stateParam', 'launchLogin'], 'should run mock oauth behaviors', );