-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve our approach for testing auth (part 1) #9681
Changes from 11 commits
764582e
2e913a7
ae1a231
b2c2a18
9dd597d
7bc3cc0
f6da3af
31c3f94
18ec387
1688e58
609c017
ffc7800
3e5c98d
876708f
1ddd577
c41f60f
f4cc1af
b471c5c
7aadc10
79dc536
d1435c2
a53f716
14d0789
d22de8a
50f4144
2d310bd
1b79b4c
419bd01
a2b838c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { testAuth } from '../test-helpers.js' | ||
import StackExchangeMonthlyQuestions from './stackexchange-monthlyquestions.service.js' | ||
|
||
describe('StackExchangeMonthlyQuestions', function () { | ||
describe('auth', function () { | ||
it('sends the auth information as configured', async function () { | ||
return testAuth(StackExchangeMonthlyQuestions, { total: 8 }) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { testAuth } from '../test-helpers.js' | ||
import StackExchangeReputation from './stackexchange-reputation.service.js' | ||
|
||
describe('StackExchangeReputation', function () { | ||
describe('auth', function () { | ||
it('sends the auth information as configured', async function () { | ||
return testAuth(StackExchangeReputation, { items: [{ reputation: 8 }] }) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { testAuth } from '../test-helpers.js' | ||
import StackExchangeQuestions from './stackexchange-taginfo.service.js' | ||
|
||
describe('StackExchangeQuestions', function () { | ||
describe('auth', function () { | ||
it('sends the auth information as configured', async function () { | ||
return testAuth(StackExchangeQuestions, { items: [{ count: 8 }] }) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
import { expect } from 'chai' | ||
import nock from 'nock' | ||
import config from 'config' | ||
import { fetch } from '../core/base-service/got.js' | ||
import BaseService from '../core/base-service/base.js' | ||
const runnerConfig = config.util.toObject() | ||
|
||
function cleanUpNockAfterEach() { | ||
|
@@ -30,6 +32,99 @@ function noToken(serviceClass) { | |
} | ||
} | ||
|
||
/** | ||
* Retrieves an example set of parameters for invoking a service class using OpenAPI example of that class. | ||
* | ||
* @param {BaseService} serviceClass The service class containing OpenAPI specifications. | ||
* @returns {object} An object with call params to use with a service invoke of the first OpenAPI example. | ||
* @throws {TypeError} - Throws a TypeError if the input `serviceClass` is not an instance of BaseService, | ||
* or if it lacks the expected structure. | ||
* | ||
* @example | ||
* // Example usage: | ||
* const example = getBadgeExampleCall(StackExchangeReputation) | ||
* console.log(example) | ||
* // Output: { stackexchangesite: 'stackoverflow', query: '123' } | ||
* StackExchangeReputation.invoke(defaultContext, config, example) | ||
*/ | ||
function getBadgeExampleCall(serviceClass) { | ||
if (!(serviceClass.prototype instanceof BaseService)) { | ||
throw new TypeError( | ||
'Invalid serviceClass: Must be an instance of BaseService.', | ||
) | ||
} | ||
|
||
if (!serviceClass.openApi) { | ||
throw new TypeError( | ||
`Missing OpenAPI in service class ${serviceClass.name}.`, | ||
) | ||
} | ||
|
||
const firstOpenapiPath = Object.keys(serviceClass.openApi)[0] | ||
|
||
const firstOpenapiExampleParams = | ||
serviceClass.openApi[firstOpenapiPath].get.parameters | ||
if (!Array.isArray(firstOpenapiExampleParams)) { | ||
throw new TypeError( | ||
`Missing or invalid OpenAPI examples in ${serviceClass.name}.`, | ||
) | ||
} | ||
|
||
// reformat structure for serviceClass.invoke | ||
const exampleInvokeParams = firstOpenapiExampleParams.reduce((acc, obj) => { | ||
acc[obj.name] = obj.example | ||
return acc | ||
}, {}) | ||
|
||
return exampleInvokeParams | ||
} | ||
|
||
/** | ||
* Test authentication of a badge for it's first OpenAPI example using a provided dummyResponse | ||
* | ||
* @param {BaseService} serviceClass The service class tested. | ||
* @param {object} dummyResponse An object containing the dummy response by the server. | ||
* @throws {TypeError} - Throws a TypeError if the input `serviceClass` is not an instance of BaseService, | ||
* or if `serviceClass` is missing authorizedOrigins. | ||
* | ||
* @example | ||
* // Example usage: | ||
* testAuth(StackExchangeReputation, { items: [{ reputation: 8 }] }) | ||
*/ | ||
async function testAuth(serviceClass, dummyResponse) { | ||
if (!(serviceClass.prototype instanceof BaseService)) { | ||
throw new TypeError( | ||
'Invalid serviceClass: Must be an instance of BaseService.', | ||
) | ||
} | ||
|
||
cleanUpNockAfterEach() | ||
|
||
const config = { private: { stackapps_api_key: 'fake-key' } } | ||
const exampleInvokeParams = getBadgeExampleCall(serviceClass) | ||
const authOrigin = serviceClass.auth.authorizedOrigins[0] | ||
|
||
if (!authOrigin) { | ||
throw new TypeError(`Missing authorizedOrigins for ${serviceClass.name}.`) | ||
} | ||
|
||
const scope = nock(authOrigin) | ||
.get(/.*/) | ||
.query(queryObject => queryObject.key === 'fake-key') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, this is a bit specific to this service. This will work for services that pass auth in the query string, but won't for services where we pass auth in a header, for example. We're probably going to need a few different variants of this function for different auth methods (basic auth, header, query string, etc). I don't think that will be too hard - there's a few different ways we can do this. I think I'd be fine just merging this how it is and saying at the moment it only works for querystring auth. Then we work out how to generalise this as we expand more services to use this helper. I usually find it easier to think about this stuff once I have a concrete problem I am trying to apply it to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See ffc7800, its all more generic now and should handle most service classes |
||
.reply(200, dummyResponse) | ||
expect( | ||
await serviceClass.invoke(defaultContext, config, exampleInvokeParams), | ||
).to.not.have.property('isError') | ||
jNullj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
scope.done() | ||
} | ||
|
||
const defaultContext = { requestFetcher: fetch } | ||
|
||
export { cleanUpNockAfterEach, noToken, defaultContext } | ||
export { | ||
cleanUpNockAfterEach, | ||
noToken, | ||
getBadgeExampleCall, | ||
testAuth, | ||
defaultContext, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is specific to the stackexchange badges. If this is going to be a completely generic helper we can use for all services, we'll need to pass this into
testAuth
as a param. I'd suggest we make the config object the first or second param totestAuth()
. This one is small, so lets address this one before merging.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So i went a bit overboard and just made everything generic and support all auth methods.
So now i have a function to generate a fake config object based on the service class.
See ffc7800