api-testing
Advanced tools
Comparing version 1.4.2 to 1.5.0
@@ -10,3 +10,3 @@ { | ||
"computed-property-spacing": [ "error", "never" ], | ||
"indent": [ "error", 4, { | ||
"indent": [ "error", "tab", { | ||
"SwitchCase": 1, | ||
@@ -13,0 +13,0 @@ "MemberExpression": "off" |
@@ -0,1 +1,12 @@ | ||
## 1.5.0 | ||
* Run mocha tests in parallel | ||
* Attempt to re-run createAccount when exceptions occur (https://phabricator.wikimedia.org/T199393) | ||
## 1.3.0 / 2020-08-17 | ||
* Ability to add a display name in changetag creation utility function | ||
* Run all job queue jobs after changetag creation to ensure changes have propagated | ||
## 1.2.0 / 2020-06-22 | ||
* Add utility function in action API for creating change tags. | ||
## 1.1.0 / 2020-06-09 | ||
@@ -5,8 +16,1 @@ * Removed tests for MediaWiki core action API and REST API. | ||
* Updated eslint rules | ||
## 1.2.0 / 2020-06-22 | ||
* Add utility function in action API for creating change tags. | ||
## 1.3.0 / 2020-08-17 | ||
* Ability to add a display name in changetag creation utility function | ||
* Run all job queue jobs after changetag creation to ensure changes have propagated |
{ | ||
"base_uri": "http://default.web.mw.localhost:8080/mediawiki/", | ||
"main_page": "Main_Page", | ||
"root_user": { | ||
"name": "Admin", | ||
"password": "dockerpass" | ||
}, | ||
"secret_key": "a5dca55190e1c3927e098c317dd74e85c7eced36f959275114773b188fbabdbc", | ||
"extra_parameters": { | ||
"xdebug_session": "PHPSTORM" | ||
} | ||
"base_uri": "http://default.web.mw.localhost:8080/mediawiki/", | ||
"main_page": "Main_Page", | ||
"root_user": { | ||
"name": "Admin", | ||
"password": "dockerpass" | ||
}, | ||
"secret_key": "a5dca55190e1c3927e098c317dd74e85c7eced36f959275114773b188fbabdbc", | ||
"extra_parameters": { | ||
"xdebug_session": "PHPSTORM" | ||
} | ||
} |
{ | ||
"base_uri": "http://127.0.0.1:9412/", | ||
"main_page": "Main_Page", | ||
"root_user": | ||
{ | ||
"name": "WikiAdmin", | ||
"password": "testwikijenkinspass" | ||
}, | ||
"secret_key": "supercalifragilisticexpialidocious" | ||
"base_uri": "http://127.0.0.1:9412/", | ||
"main_page": "Main_Page", | ||
"root_user": | ||
{ | ||
"name": "WikiAdmin", | ||
"password": "testwikijenkinspass" | ||
}, | ||
"secret_key": "supercalifragilisticexpialidocious" | ||
} |
12
index.js
'use strict'; | ||
module.exports = { | ||
action: require('./lib/action_clients'), | ||
utils: require('./lib/utils'), | ||
REST: require('./lib/REST'), | ||
assert: require('./lib/assert').assert, | ||
wiki: require('./lib/wiki'), | ||
clientFactory: require('./lib/clientFactory') | ||
action: require('./lib/action_clients'), | ||
utils: require('./lib/utils'), | ||
REST: require('./lib/REST'), | ||
assert: require('./lib/assert').assert, | ||
wiki: require('./lib/wiki'), | ||
clientFactory: require('./lib/clientFactory') | ||
}; |
@@ -14,108 +14,108 @@ 'use strict'; | ||
/** | ||
* Add tag to database if not already added. | ||
* | ||
* @param {string} tag name | ||
* @param {string} display display text for tag. Affects new tags, will not update existing tag | ||
* @return {Promise<string>} tag name | ||
*/ | ||
async makeTag(tag, display) { | ||
if (cachedTags[tag]) { | ||
return tag; | ||
} | ||
const root = await this.root(); | ||
const tagList = await root.list('tags', { tglimit: 50, tgprop: 'displayname' }); | ||
const existingTags = tagList.reduce((obj, row) => { | ||
obj[row.name] = true; | ||
return obj; | ||
}, {}); | ||
if (!existingTags[tag]) { | ||
const { managetags } = await root.action('managetags', { | ||
operation: 'create', | ||
tag, | ||
token: await root.token() | ||
}, true); | ||
assert.isOk(managetags.tag); | ||
if (display) { | ||
const { param_text: summary } = await root.edit(`MediaWiki:tag-${tag}`, { text: display }); | ||
assert.deepEqual(display, summary); | ||
} | ||
// We don't really need to run all jobs, but we do need to wait | ||
// for replication. | ||
await wiki.runAllJobs(); | ||
} | ||
cachedTags = { ...existingTags }; | ||
return tag; | ||
}, | ||
/** | ||
* Add tag to database if not already added. | ||
* | ||
* @param {string} tag name | ||
* @param {string} display display text for tag. Affects new tags, will not update existing tag | ||
* @return {Promise<string>} tag name | ||
*/ | ||
async makeTag(tag, display) { | ||
if (cachedTags[tag]) { | ||
return tag; | ||
} | ||
const root = await this.root(); | ||
const tagList = await root.list('tags', { tglimit: 50, tgprop: 'displayname' }); | ||
const existingTags = tagList.reduce((obj, row) => { | ||
obj[row.name] = true; | ||
return obj; | ||
}, {}); | ||
if (!existingTags[tag]) { | ||
const { managetags } = await root.action('managetags', { | ||
operation: 'create', | ||
tag, | ||
token: await root.token() | ||
}, true); | ||
assert.isOk(managetags.tag); | ||
if (display) { | ||
const { param_text: summary } = await root.edit(`MediaWiki:tag-${tag}`, { text: display }); | ||
assert.deepEqual(display, summary); | ||
} | ||
// We don't really need to run all jobs, but we do need to wait | ||
// for replication. | ||
await wiki.runAllJobs(); | ||
} | ||
cachedTags = { ...existingTags }; | ||
return tag; | ||
}, | ||
async root() { | ||
if (singletons.root) { | ||
return singletons.root; | ||
} | ||
async root() { | ||
if (singletons.root) { | ||
return singletons.root; | ||
} | ||
const root = new Client(); | ||
await root.login(config.root_user.name, | ||
config.root_user.password); | ||
const root = new Client(); | ||
await root.login(config.root_user.name, | ||
config.root_user.password); | ||
await root.loadTokens(['createaccount', 'userrights', 'csrf']); | ||
await root.loadTokens(['createaccount', 'userrights', 'csrf']); | ||
const rightsToken = await root.token('userrights'); | ||
assert.notEqual(rightsToken, '+\\'); | ||
const rightsToken = await root.token('userrights'); | ||
assert.notEqual(rightsToken, '+\\'); | ||
singletons.root = root; | ||
return root; | ||
}, | ||
singletons.root = root; | ||
return root; | ||
}, | ||
async user(name, groups = [], tokens = ['csrf']) { | ||
if (singletons[name]) { | ||
return singletons[name]; | ||
} | ||
async user(name, groups = [], tokens = ['csrf']) { | ||
if (singletons[name]) { | ||
return singletons[name]; | ||
} | ||
// TODO: Use a fixed user name for Alice. Works only on a blank wiki. | ||
let uname = utils.title(name); | ||
const passwd = utils.uniq(); | ||
const root = await this.root(); | ||
const client = new Client(); | ||
// TODO: Use a fixed user name for Alice. Works only on a blank wiki. | ||
let uname = utils.title(name); | ||
const passwd = utils.uniq(); | ||
const root = await this.root(); | ||
const client = new Client(); | ||
const account = await client.createAccount({ username: uname, password: passwd }); | ||
uname = account.username; | ||
const account = await client.createAccount({ username: uname, password: passwd }); | ||
uname = account.username; | ||
if (groups.length) { | ||
// HACK: This reduces the chance of race conditions due to | ||
// replication lag. For the proper solution, see T230211. | ||
await wiki.runAllJobs(); | ||
if (groups.length) { | ||
// HACK: This reduces the chance of race conditions due to | ||
// replication lag. For the proper solution, see T230211. | ||
await wiki.runAllJobs(); | ||
const groupResult = await root.addGroups(uname, groups); | ||
assert.sameMembers(groupResult.added, groups); | ||
} | ||
const groupResult = await root.addGroups(uname, groups); | ||
assert.sameMembers(groupResult.added, groups); | ||
} | ||
await client.account(uname, passwd); | ||
await client.account(uname, passwd); | ||
if (tokens.length) { | ||
await client.loadTokens(tokens); | ||
} | ||
if (tokens.length) { | ||
await client.loadTokens(tokens); | ||
} | ||
singletons[name] = client; | ||
return client; | ||
}, | ||
singletons[name] = client; | ||
return client; | ||
}, | ||
async alice() { | ||
return this.user('Alice'); | ||
}, | ||
async alice() { | ||
return this.user('Alice'); | ||
}, | ||
async bob() { | ||
return this.user('Bob'); | ||
}, | ||
async bob() { | ||
return this.user('Bob'); | ||
}, | ||
async mindy() { | ||
return this.user('Mindy', ['sysop'], ['userrights', 'csrf']); | ||
}, | ||
async mindy() { | ||
return this.user('Mindy', ['sysop'], ['userrights', 'csrf']); | ||
}, | ||
async robby() { | ||
return this.user('Robby', ['bot']); | ||
}, | ||
async robby() { | ||
return this.user('Robby', ['bot']); | ||
}, | ||
getAnon() { | ||
return new Client(); | ||
} | ||
getAnon() { | ||
return new Client(); | ||
} | ||
}; |
@@ -10,440 +10,453 @@ 'use strict'; | ||
class Client { | ||
/** | ||
* Constructs a new agent for making HTTP requests to the MediaWiki | ||
* action API. The agent acts like a browser session and has its own | ||
* cookie jar. | ||
* | ||
* Pass in an optional supertest agent to use for making requests | ||
* where the client wants to behave as a logged in user. | ||
* If not supplied, client behaves like an "anonymous" user | ||
* until account() is used to attach the client to a user account. | ||
* | ||
* Note that all anonymous users share the same IP address, even though they | ||
* don't share a browser session (cookie jar). This means that are treated | ||
* as the same user in some respects, but not in others. | ||
* | ||
* @param {Object} agent supertest agent | ||
*/ | ||
constructor(agent) { | ||
if (agent) { | ||
this.req = agent.req; | ||
this.username = agent.username; | ||
this.userid = agent.userid; | ||
} else { | ||
this.req = supertest.agent(config.base_uri); | ||
this.username = '<anon>'; | ||
this.userid = 0; | ||
} | ||
} | ||
/** | ||
* Constructs a new agent for making HTTP requests to the MediaWiki | ||
* action API. The agent acts like a browser session and has its own | ||
* cookie jar. | ||
* | ||
* Pass in an optional supertest agent to use for making requests | ||
* where the client wants to behave as a logged in user. | ||
* If not supplied, client behaves like an "anonymous" user | ||
* until account() is used to attach the client to a user account. | ||
* | ||
* Note that all anonymous users share the same IP address, even though they | ||
* don't share a browser session (cookie jar). This means that are treated | ||
* as the same user in some respects, but not in others. | ||
* | ||
* @param {Object} agent supertest agent | ||
*/ | ||
constructor(agent) { | ||
if (agent) { | ||
this.req = agent.req; | ||
this.username = agent.username; | ||
this.userid = agent.userid; | ||
} else { | ||
this.req = supertest.agent(config.base_uri); | ||
this.username = '<anon>'; | ||
this.userid = 0; | ||
} | ||
} | ||
/** | ||
* Attaches this API client to a user account. | ||
* | ||
* A login for this user is performed, | ||
* and the corresponding server session is associated with this client. | ||
* This should only be used when instantiating fixtures. | ||
* | ||
* If no password is given, a new user account is created with a random | ||
* password and a random suffix appended to the user name. The new user is | ||
* then logged in. This should be used to construct a temporary unique | ||
* user account that can be modified and destroyed by tests. | ||
* | ||
* @param {string} name | ||
* @param {string|null} passwd | ||
* @return {Promise<Client>} | ||
*/ | ||
async account(name, passwd = null) { | ||
let uname = name; | ||
let upass = passwd; | ||
/** | ||
* Attaches this API client to a user account. | ||
* | ||
* A login for this user is performed, | ||
* and the corresponding server session is associated with this client. | ||
* This should only be used when instantiating fixtures. | ||
* | ||
* If no password is given, a new user account is created with a random | ||
* password and a random suffix appended to the user name. The new user is | ||
* then logged in. This should be used to construct a temporary unique | ||
* user account that can be modified and destroyed by tests. | ||
* | ||
* @param {string} name | ||
* @param {string|null} passwd | ||
* @return {Promise<Client>} | ||
*/ | ||
async account(name, passwd = null) { | ||
let uname = name; | ||
let upass = passwd; | ||
if (!upass) { | ||
uname = utils.title(name); | ||
upass = utils.title(); | ||
if (!upass) { | ||
uname = utils.title(name); | ||
upass = utils.title(); | ||
const account = await this.createAccount({ username: uname, password: upass }); | ||
uname = account.username; | ||
} | ||
const account = await this.createAccount({ username: uname, password: upass }); | ||
uname = account.username; | ||
} | ||
await this.login(uname, upass); | ||
return this; | ||
} | ||
await this.login(uname, upass); | ||
return this; | ||
} | ||
/** | ||
* Constructs an HTTP request to the action API and returns the | ||
* corresponding supertest Test object, which behaves like a | ||
* superagent Request. It can be used like a Promise that resolves | ||
* to a Response. | ||
* | ||
* Call end(), then(), or use await to send the request. | ||
* | ||
* @param {Object} params | ||
* @param {boolean|string} post | ||
* @param {string} endpoint | ||
* @return {Promise<*>} | ||
*/ | ||
async request(params, post = false, endpoint = 'api.php') { | ||
// TODO: it would be nice if we could resolve/await any promises in params | ||
// TODO: convert any arrays in params to strings | ||
const defaultParams = { | ||
format: 'json', | ||
...config.extra_parameters | ||
}; | ||
/** | ||
* Constructs an HTTP request to the action API and returns the | ||
* corresponding supertest Test object, which behaves like a | ||
* superagent Request. It can be used like a Promise that resolves | ||
* to a Response. | ||
* | ||
* Call end(), then(), or use await to send the request. | ||
* | ||
* @param {Object} params | ||
* @param {boolean|string} post | ||
* @param {string} endpoint | ||
* @return {Promise<*>} | ||
*/ | ||
async request(params, post = false, endpoint = 'api.php') { | ||
// TODO: it would be nice if we could resolve/await any promises in params | ||
// TODO: convert any arrays in params to strings | ||
const defaultParams = { | ||
format: 'json', | ||
...config.extra_parameters | ||
}; | ||
let req; | ||
if (post) { | ||
req = this.req.post(endpoint) | ||
let req; | ||
if (post) { | ||
req = this.req.post(endpoint) | ||
.type('form') | ||
.send({ ...defaultParams, ...params }); | ||
} else { | ||
req = this.req.get(endpoint) | ||
} else { | ||
req = this.req.get(endpoint) | ||
.query({ ...defaultParams, ...params }); | ||
} | ||
} | ||
return req; | ||
} | ||
return req; | ||
} | ||
/** | ||
* Executes an HTTP request to the action API and returns the parsed | ||
* response body. Will fail if the response contains an error code. | ||
* | ||
* @param {string} actionName | ||
* @param {Object} params | ||
* @param {boolean|string} post | ||
* @return {Promise<Object>} | ||
*/ | ||
async action(actionName, params, post = false) { | ||
const response = await this.request( | ||
{ action: actionName, ...params }, | ||
post | ||
); | ||
/** | ||
* Executes an HTTP request to the action API and returns the parsed | ||
* response body. Will fail if the response contains an error code. | ||
* | ||
* @param {string} actionName | ||
* @param {Object} params | ||
* @param {boolean|string} post | ||
* @return {Promise<Object>} | ||
*/ | ||
async action(actionName, params, post = false) { | ||
const response = await this.request( | ||
{ action: actionName, ...params }, | ||
post | ||
); | ||
assert.equal(response.status, 200); | ||
assert.exists(response.body); | ||
assert.equal(response.status, 200); | ||
assert.exists(response.body); | ||
if (response.body.error) { | ||
assert.fail(`User "${this.username}": Action "${actionName}" returned error code "${response.body.error.code}": ${response.body.error.info}!`); | ||
} | ||
if (response.body.error) { | ||
assert.fail(`User "${this.username}": Action "${actionName}" returned error code "${response.body.error.code}": ${response.body.error.info}!`); | ||
} | ||
return response.body; | ||
} | ||
return response.body; | ||
} | ||
/** | ||
* Executes an upload request | ||
* | ||
* @param {Object} params | ||
* @param {string|ArrayBuffer} file - file path or Buffer object. | ||
* @param {string} endpoint | ||
* @return {Promise<*>} | ||
*/ | ||
async upload(params, file, endpoint = 'api.php') { | ||
const res = await this.req.post(endpoint) | ||
/** | ||
* Executes an upload request | ||
* | ||
* @param {Object} params | ||
* @param {string|ArrayBuffer} file - file path or Buffer object. | ||
* @param {string} endpoint | ||
* @return {Promise<*>} | ||
*/ | ||
async upload(params, file, endpoint = 'api.php') { | ||
const res = await this.req.post(endpoint) | ||
.field({ action: 'upload', format: 'json', ...params }) | ||
.attach('file', file); | ||
return res; | ||
} | ||
return res; | ||
} | ||
/** | ||
* Executes a prop query | ||
* | ||
* @param {string|Array} prop | ||
* @param {string|Array} titles | ||
* @param {Object} params | ||
* @return {Promise<Array>} | ||
*/ | ||
async prop(prop, titles, params = {}) { | ||
const defaults = { | ||
prop: typeof prop === 'string' ? prop : prop.join('|'), | ||
titles: typeof titles === 'string' ? titles : titles.join('|') | ||
}; | ||
const result = await this.action('query', { ...defaults, ...params }); | ||
/** | ||
* Executes a prop query | ||
* | ||
* @param {string|Array} prop | ||
* @param {string|Array} titles | ||
* @param {Object} params | ||
* @return {Promise<Array>} | ||
*/ | ||
async prop(prop, titles, params = {}) { | ||
const defaults = { | ||
prop: typeof prop === 'string' ? prop : prop.join('|'), | ||
titles: typeof titles === 'string' ? titles : titles.join('|') | ||
}; | ||
const result = await this.action('query', { ...defaults, ...params }); | ||
const names = {}; | ||
const pages = {}; | ||
const names = {}; | ||
const pages = {}; | ||
if (result.query.normalized) { | ||
for (const e of result.query.normalized) { | ||
names[e.to] = e.from; | ||
} | ||
} | ||
if (result.query.normalized) { | ||
for (const e of result.query.normalized) { | ||
names[e.to] = e.from; | ||
} | ||
} | ||
for (const k in result.query.pages) { | ||
let title = result.query.pages[k].title; | ||
for (const k in result.query.pages) { | ||
let title = result.query.pages[k].title; | ||
// De-normalize the titles, so the keys in the result correspond | ||
// to the titles parameter. | ||
if (title in names) { | ||
title = names[title]; | ||
} | ||
// De-normalize the titles, so the keys in the result correspond | ||
// to the titles parameter. | ||
if (title in names) { | ||
title = names[title]; | ||
} | ||
pages[title] = result.query.pages[k]; | ||
} | ||
pages[title] = result.query.pages[k]; | ||
} | ||
return pages; | ||
} | ||
return pages; | ||
} | ||
/** | ||
* Executes a list query | ||
* | ||
* @param {string} list | ||
* @param {Object} params | ||
* @return {Promise<Array>} | ||
*/ | ||
async list(list, params) { | ||
const defaults = { list }; | ||
const result = await this.action('query', { ...defaults, ...params }); | ||
return result.query[list]; | ||
} | ||
/** | ||
* Executes a list query | ||
* | ||
* @param {string} list | ||
* @param {Object} params | ||
* @return {Promise<Array>} | ||
*/ | ||
async list(list, params) { | ||
const defaults = { list }; | ||
const result = await this.action('query', { ...defaults, ...params }); | ||
return result.query[list]; | ||
} | ||
/** | ||
* Executes a meta query | ||
* | ||
* @param {string} meta | ||
* @param {Object} params | ||
* @param {string} field | ||
* @return {Promise<Object>} | ||
*/ | ||
async meta(meta, params, field = null) { | ||
const defaults = { meta }; | ||
const result = await this.action('query', { ...defaults, ...params }); | ||
/** | ||
* Executes a meta query | ||
* | ||
* @param {string} meta | ||
* @param {Object} params | ||
* @param {string} field | ||
* @return {Promise<Object>} | ||
*/ | ||
async meta(meta, params, field = null) { | ||
const defaults = { meta }; | ||
const result = await this.action('query', { ...defaults, ...params }); | ||
const key = field || meta; | ||
return result.query[key]; | ||
} | ||
const key = field || meta; | ||
return result.query[key]; | ||
} | ||
/** | ||
* Executes an HTTP request to the action API and returns the error | ||
* stanza of the response body. Will fail if there is no error stanza. | ||
* This is intended as an easy way to test for expected errors. | ||
* | ||
* @param {string} actionName | ||
* @param {Object} params | ||
* @param {boolean|string} post | ||
* @return {Promise<Object>} | ||
*/ | ||
async actionError(actionName, params, post = false) { | ||
const response = await this.request( | ||
{ action: actionName, ...params }, | ||
post | ||
); | ||
/** | ||
* Executes an HTTP request to the action API and returns the error | ||
* stanza of the response body. Will fail if there is no error stanza. | ||
* This is intended as an easy way to test for expected errors. | ||
* | ||
* @param {string} actionName | ||
* @param {Object} params | ||
* @param {boolean|string} post | ||
* @return {Promise<Object>} | ||
*/ | ||
async actionError(actionName, params, post = false) { | ||
const response = await this.request( | ||
{ action: actionName, ...params }, | ||
post | ||
); | ||
assert.equal(response.status, 200); | ||
assert.exists(response.body); | ||
assert.exists(response.body.error); | ||
return response.body.error; | ||
} | ||
assert.equal(response.status, 200); | ||
assert.exists(response.body); | ||
assert.exists(response.body.error); | ||
return response.body.error; | ||
} | ||
/** | ||
* Loads the given tokens. Any cached tokens are reset. | ||
* | ||
* @param {string[]} ttypes | ||
* @return {Promise<Object>} | ||
*/ | ||
async loadTokens(ttypes) { | ||
this.tokens = await this.meta('tokens', { type: ttypes.join('|') }); | ||
/** | ||
* Loads the given tokens. Any cached tokens are reset. | ||
* | ||
* @param {string[]} ttypes | ||
* @return {Promise<Object>} | ||
*/ | ||
async loadTokens(ttypes) { | ||
this.tokens = await this.meta('tokens', { type: ttypes.join('|') }); | ||
return this.tokens; | ||
} | ||
return this.tokens; | ||
} | ||
/** | ||
* Returns the given token. If the token is not cached, it is requested | ||
* and then cached. | ||
* | ||
* @param {string} ttype | ||
* @return {Promise<string>} | ||
*/ | ||
async token(ttype = 'csrf') { | ||
const tname = `${ttype}token`; | ||
if (this.tokens && this.tokens[tname]) { | ||
return this.tokens[tname]; | ||
} | ||
/** | ||
* Returns the given token. If the token is not cached, it is requested | ||
* and then cached. | ||
* | ||
* @param {string} ttype | ||
* @return {Promise<string>} | ||
*/ | ||
async token(ttype = 'csrf') { | ||
const tname = `${ttype}token`; | ||
if (this.tokens && this.tokens[tname]) { | ||
return this.tokens[tname]; | ||
} | ||
// TODO: skip tokens we already have! | ||
const newTokens = await this.meta('tokens', { type: ttype }); | ||
// TODO: skip tokens we already have! | ||
const newTokens = await this.meta('tokens', { type: ttype }); | ||
this.tokens = { ...this.tokens, ...newTokens }; | ||
this.tokens = { ...this.tokens, ...newTokens }; | ||
/* eslint-disable max-len */ | ||
// const session = this.req.jar.getCookie('default_session', cookiejar.CookieAccessInfo.All); | ||
// console.log(`token(${ttype}): user id ${this.userid}: ${JSON.stringify(this.tokens)} (session: ${session})`); | ||
/* eslint-enable max-len */ | ||
/* eslint-disable max-len */ | ||
// const session = this.req.jar.getCookie('default_session', cookiejar.CookieAccessInfo.All); | ||
// console.log(`token(${ttype}): user id ${this.userid}: ${JSON.stringify(this.tokens)} (session: ${session})`); | ||
/* eslint-enable max-len */ | ||
assert.exists(this.tokens[tname]); | ||
return this.tokens[tname]; | ||
} | ||
assert.exists(this.tokens[tname]); | ||
return this.tokens[tname]; | ||
} | ||
/** | ||
* Logs this agent in as the given user. | ||
* | ||
* @param {string} username | ||
* @param {string} password | ||
* @return {Promise<Object>} | ||
*/ | ||
async login(username, password) { | ||
const result = await this.action( | ||
'login', | ||
{ | ||
lgname: username, | ||
lgpassword: password, | ||
lgtoken: await this.token('login') | ||
}, | ||
'POST' | ||
); | ||
assert.equal(result.login.result, 'Success', | ||
`Login for "${username}": ${result.login.reason}`); | ||
/** | ||
* Logs this agent in as the given user. | ||
* | ||
* @param {string} username | ||
* @param {string} password | ||
* @return {Promise<Object>} | ||
*/ | ||
async login(username, password) { | ||
const result = await this.action( | ||
'login', | ||
{ | ||
lgname: username, | ||
lgpassword: password, | ||
lgtoken: await this.token('login') | ||
}, | ||
'POST' | ||
); | ||
assert.equal(result.login.result, 'Success', | ||
`Login for "${username}": ${result.login.reason}`); | ||
this.username = result.login.lgusername; | ||
this.userid = result.login.lguserid; | ||
this.tokens = {}; // discard anon tokens | ||
this.username = result.login.lgusername; | ||
this.userid = result.login.lguserid; | ||
this.tokens = {}; // discard anon tokens | ||
return result.login; | ||
} | ||
return result.login; | ||
} | ||
/** | ||
* Performs an edit on a page. | ||
* | ||
* @param {string} pageTitle | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async edit(pageTitle, params) { | ||
const effectiveParams = { | ||
...{ | ||
title: pageTitle, | ||
summary: 'testing' | ||
}, | ||
...params | ||
}; | ||
/** | ||
* Performs an edit on a page. | ||
* | ||
* @param {string} pageTitle | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async edit(pageTitle, params) { | ||
const effectiveParams = { | ||
...{ | ||
title: pageTitle, | ||
summary: 'testing' | ||
}, | ||
...params | ||
}; | ||
// use a unique default text | ||
effectiveParams.text = effectiveParams.text || 'Lorem ipsum ' + utils.uniq(); | ||
// use a unique default text | ||
effectiveParams.text = effectiveParams.text || 'Lorem ipsum ' + utils.uniq(); | ||
effectiveParams.token = params.token || await this.token('csrf'); | ||
effectiveParams.token = params.token || await this.token('csrf'); | ||
const result = await this.action('edit', effectiveParams, 'POST'); | ||
assert.equal(result.edit.result, 'Success'); | ||
const result = await this.action('edit', effectiveParams, 'POST'); | ||
assert.equal(result.edit.result, 'Success'); | ||
// record parameters, for convenience | ||
result.edit.param_user = this.username; | ||
Object.keys(effectiveParams).forEach((paramName) => { | ||
result.edit[`param_${paramName}`] = effectiveParams[paramName]; | ||
}); | ||
// record parameters, for convenience | ||
result.edit.param_user = this.username; | ||
Object.keys(effectiveParams).forEach((paramName) => { | ||
result.edit[`param_${paramName}`] = effectiveParams[paramName]; | ||
}); | ||
await wiki.runAllJobs(); | ||
return result.edit; | ||
} | ||
await wiki.runAllJobs(); | ||
return result.edit; | ||
} | ||
/** | ||
* Returns a revision record of the given page. | ||
* If revid is not given or 0, the latest revision is returned. | ||
* | ||
* @param {string} pageTitle | ||
* @param {number} revid | ||
* @return {Promise<Object>} | ||
*/ | ||
async getRevision(pageTitle, revid = 0) { | ||
const params = { | ||
rvslots: 'main', | ||
rvprop: 'ids|user|comment|content|timestamp|flags|contentmodel' | ||
}; | ||
/** | ||
* Returns a revision record of the given page. | ||
* If revid is not given or 0, the latest revision is returned. | ||
* | ||
* @param {string} pageTitle | ||
* @param {number} revid | ||
* @return {Promise<Object>} | ||
*/ | ||
async getRevision(pageTitle, revid = 0) { | ||
const params = { | ||
rvslots: 'main', | ||
rvprop: 'ids|user|comment|content|timestamp|flags|contentmodel' | ||
}; | ||
if (revid) { | ||
params.revids = revid; | ||
} else { | ||
params.titles = pageTitle; | ||
params.rvlimit = 1; | ||
} | ||
if (revid) { | ||
params.revids = revid; | ||
} else { | ||
params.titles = pageTitle; | ||
params.rvlimit = 1; | ||
} | ||
const result = await this.prop( | ||
'revisions', | ||
pageTitle, | ||
params | ||
); | ||
const result = await this.prop( | ||
'revisions', | ||
pageTitle, | ||
params | ||
); | ||
// XXX: pageTitle may need normalization! | ||
const page = result[pageTitle]; | ||
return page.revisions[0]; | ||
} | ||
// XXX: pageTitle may need normalization! | ||
const page = result[pageTitle]; | ||
return page.revisions[0]; | ||
} | ||
/** | ||
* Returns the newest log entry matching the given parameters. | ||
* | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async getLogEntry(params) { | ||
const list = await this.list('logevents', { | ||
...{ | ||
leprop: 'ids|title|type|user|timestamp|comment', | ||
lelimit: 1 | ||
}, | ||
...params | ||
}); | ||
/** | ||
* Returns the newest log entry matching the given parameters. | ||
* | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async getLogEntry(params) { | ||
const list = await this.list('logevents', { | ||
...{ | ||
leprop: 'ids|title|type|user|timestamp|comment', | ||
lelimit: 1 | ||
}, | ||
...params | ||
}); | ||
return list[0]; | ||
} | ||
return list[0]; | ||
} | ||
/** | ||
* Returns the newest recent changes entry matching the given parameters. | ||
* | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async getChangeEntry(params) { | ||
const list = await this.list('recentchanges', { | ||
...{ | ||
rcprop: 'ids|flags|user|comment|timestamp|title', | ||
rclimit: 1 | ||
}, | ||
...params | ||
}); | ||
/** | ||
* Returns the newest recent changes entry matching the given parameters. | ||
* | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async getChangeEntry(params) { | ||
const list = await this.list('recentchanges', { | ||
...{ | ||
rcprop: 'ids|flags|user|comment|timestamp|title', | ||
rclimit: 1 | ||
}, | ||
...params | ||
}); | ||
return list[0]; | ||
} | ||
return list[0]; | ||
} | ||
async getHtml(title) { | ||
const result = await this.action('parse', { page: title }); | ||
async getHtml(title) { | ||
const result = await this.action('parse', { page: title }); | ||
const html = result.parse.text['*']; | ||
return html.replace(/<!--[^]*?-->/g, ''); | ||
} | ||
const html = result.parse.text['*']; | ||
return html.replace(/<!--[^]*?-->/g, ''); | ||
} | ||
/** | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async createAccount(params) { | ||
const defaults = { | ||
createtoken: params.token || await this.token('createaccount'), | ||
retype: params.retype || params.password, | ||
createreturnurl: config.base_uri | ||
}; | ||
/** | ||
* @param {Object} params | ||
* @return {Promise<Object>} | ||
*/ | ||
async createAccount(params) { | ||
const defaults = { | ||
createtoken: params.token || await this.token('createaccount'), | ||
retype: params.retype || params.password, | ||
createreturnurl: config.base_uri | ||
}; | ||
const attempts = 4; | ||
let result; | ||
const result = await this.action('createaccount', { ...defaults, ...params }, 'POST'); | ||
assert.equal(result.createaccount.status, 'PASS'); | ||
for (let i = 1; i <= attempts; i++) { | ||
try { | ||
result = await this.action('createaccount', { ...defaults, ...params }, 'POST'); | ||
if (result && result.createaccount) { | ||
break; | ||
} | ||
} catch (e) { | ||
// console.log( 'T199393: Deadlock while creating account on attempt ' + i ); | ||
await utils.sleep(1000); | ||
} | ||
} | ||
// TODO: wait for replication! | ||
return result.createaccount; | ||
} | ||
assert.equal(result.createaccount.status, 'PASS'); | ||
/** | ||
* @param {string} userName | ||
* @param {string[]} groups | ||
* @return {Promise<Object>} | ||
*/ | ||
async addGroups(userName, groups) { | ||
const gprops = { | ||
user: userName, | ||
add: groups.join('|'), | ||
token: await this.token('userrights') | ||
}; | ||
// TODO: wait for replication! | ||
return result.createaccount; | ||
} | ||
const result = await this.action('userrights', gprops, 'POST'); | ||
assert.isOk(result.userrights.added); | ||
/** | ||
* @param {string} userName | ||
* @param {string[]} groups | ||
* @return {Promise<Object>} | ||
*/ | ||
async addGroups(userName, groups) { | ||
const gprops = { | ||
user: userName, | ||
add: groups.join('|'), | ||
token: await this.token('userrights') | ||
}; | ||
// TODO: wait for replication! | ||
return result.userrights; | ||
} | ||
const result = await this.action('userrights', gprops, 'POST'); | ||
assert.isOk(result.userrights.added); | ||
// TODO: wait for replication! | ||
return result.userrights; | ||
} | ||
} | ||
module.exports = Client; |
@@ -7,53 +7,53 @@ 'use strict'; | ||
module.exports = use(function (_chai, _utils) { | ||
const assert = _chai.assert; | ||
const assert = _chai.assert; | ||
/** | ||
* Compares two titles, applying some normalization | ||
* | ||
* @param {string} act | ||
* @param {string} exp | ||
* @param {string} msg | ||
*/ | ||
assert.sameTitle = (act, exp, msg) => { | ||
new _chai.Assertion(utils.dbkey(act), msg, assert.deepEqual, true).to.eql(utils.dbkey(exp)); | ||
}; | ||
/** | ||
* Compares two titles, applying some normalization | ||
* | ||
* @param {string} act | ||
* @param {string} exp | ||
* @param {string} msg | ||
*/ | ||
assert.sameTitle = (act, exp, msg) => { | ||
new _chai.Assertion(utils.dbkey(act), msg, assert.deepEqual, true).to.eql(utils.dbkey(exp)); | ||
}; | ||
/** | ||
* Gets header assertion | ||
* | ||
* @param {Object} res Supertest Response object | ||
* @param {string} header Name of header to check | ||
* @param {string|RegExp} exp Expected header | ||
* @param {?string} msg | ||
*/ | ||
const getHeaderAssertion = (res, header, exp, msg) => { | ||
if (/^\/.+\/$/.test(exp)) { | ||
new _chai.Assertion(res.header[header.toLowerCase()], msg, true).to.match(exp); | ||
} else { | ||
new _chai.Assertion(res.header[header.toLowerCase()], msg, true).to.equal(exp); | ||
} | ||
}; | ||
/** | ||
* Gets header assertion | ||
* | ||
* @param {Object} res Supertest Response object | ||
* @param {string} header Name of header to check | ||
* @param {string|RegExp} exp Expected header | ||
* @param {?string} msg | ||
*/ | ||
const getHeaderAssertion = (res, header, exp, msg) => { | ||
if (/^\/.+\/$/.test(exp)) { | ||
new _chai.Assertion(res.header[header.toLowerCase()], msg, true).to.match(exp); | ||
} else { | ||
new _chai.Assertion(res.header[header.toLowerCase()], msg, true).to.equal(exp); | ||
} | ||
}; | ||
/** | ||
* Asserts whether a response's header was as expected | ||
* | ||
* @param {Object} res Supertest Response object | ||
* @param {string} header Name of header to check | ||
* @param {string|RegExp} exp Expected header | ||
* @param {?string} msg | ||
*/ | ||
assert.header = (res, header, exp, msg) => { | ||
getHeaderAssertion(res, header, exp, msg); | ||
}; | ||
/** | ||
* Asserts whether a response's header was as expected | ||
* | ||
* @param {Object} res Supertest Response object | ||
* @param {string} header Name of header to check | ||
* @param {string|RegExp} exp Expected header | ||
* @param {?string} msg | ||
*/ | ||
assert.header = (res, header, exp, msg) => { | ||
getHeaderAssertion(res, header, exp, msg); | ||
}; | ||
/** | ||
* Asserts whether a response's content type was as expected | ||
* | ||
* @param {Object} res Supertest Response object | ||
* @param {string|RegExp} exp Expected content type | ||
* @param {?string} msg | ||
*/ | ||
assert.contentType = (res, exp, msg) => { | ||
getHeaderAssertion(res, 'content-type', exp, msg); | ||
}; | ||
/** | ||
* Asserts whether a response's content type was as expected | ||
* | ||
* @param {Object} res Supertest Response object | ||
* @param {string|RegExp} exp Expected content type | ||
* @param {?string} msg | ||
*/ | ||
assert.contentType = (res, exp, msg) => { | ||
getHeaderAssertion(res, 'content-type', exp, msg); | ||
}; | ||
}); |
@@ -14,41 +14,41 @@ 'use strict'; | ||
/** | ||
* Constructs a test REST Api client (aka a supertest agent) using the supplied | ||
* action api agent. | ||
* | ||
* @param {string} endpoint REST api endpoint to hit | ||
* @param {Object} actionClient supertest agent for making calls to Action Api | ||
* @return {Object} supertest agent for making calls to REST Api | ||
*/ | ||
getRESTClient: (endpoint, actionClient) => { | ||
return new REST(endpoint, actionClient); | ||
}, | ||
/** | ||
* Constructs a test REST Api client (aka a supertest agent) using the supplied | ||
* action api agent. | ||
* | ||
* @param {string} endpoint REST api endpoint to hit | ||
* @param {Object} actionClient supertest agent for making calls to Action Api | ||
* @return {Object} supertest agent for making calls to REST Api | ||
*/ | ||
getRESTClient: (endpoint, actionClient) => { | ||
return new REST(endpoint, actionClient); | ||
}, | ||
/** | ||
* Constructs a test REST Api client (aka a supertest agent) using the supplied | ||
* rest api agent | ||
* | ||
* @param {Object} restClient supertest agent for making calls to Action Api | ||
* @return {Object} supertest agent for making calls to REST Api | ||
*/ | ||
getActionClient: (restClient) => { | ||
return new Client(restClient); | ||
}, | ||
/** | ||
* Constructs a test REST Api client (aka a supertest agent) using the supplied | ||
* rest api agent | ||
* | ||
* @param {Object} restClient supertest agent for making calls to Action Api | ||
* @return {Object} supertest agent for making calls to REST Api | ||
*/ | ||
getActionClient: (restClient) => { | ||
return new Client(restClient); | ||
}, | ||
/** | ||
* Returns a supertest agent without a pre-defined domain to be used for making | ||
* http requests with an absolute URL. | ||
* | ||
* @param {Object} client rest or action client | ||
* @return {Object} supertest agent | ||
*/ | ||
getHttpClient: (client) => { | ||
const agent = supertest.agent(''); | ||
if (client) { | ||
agent.jar = client.req.jar; | ||
} | ||
return agent; | ||
} | ||
/** | ||
* Returns a supertest agent without a pre-defined domain to be used for making | ||
* http requests with an absolute URL. | ||
* | ||
* @param {Object} client rest or action client | ||
* @return {Object} supertest agent | ||
*/ | ||
getHttpClient: (client) => { | ||
const agent = supertest.agent(''); | ||
if (client) { | ||
agent.jar = client.req.jar; | ||
} | ||
return agent; | ||
} | ||
}; | ||
module.exports = clientFactory; |
'use strict'; | ||
module.exports = (baseDir = process.cwd()) => { | ||
const fs = require('fs'); | ||
const fs = require('fs'); | ||
const configsDir = `${baseDir}/configs`; | ||
const configFileEnv = process.env.API_TESTING_CONFIG_FILE; | ||
const baseURLEnv = process.env.REST_BASE_URL; | ||
const configsDir = `${baseDir}/configs`; | ||
const configFileEnv = process.env.API_TESTING_CONFIG_FILE; | ||
const baseURLEnv = process.env.REST_BASE_URL; | ||
let requireFile = configFileEnv; | ||
let requireFile = configFileEnv; | ||
if (baseURLEnv) { | ||
return { | ||
base_uri: baseURLEnv | ||
}; | ||
} else if (requireFile) { | ||
if (!fs.existsSync(requireFile)) { | ||
// was it just the filename without the default config dir? | ||
requireFile = `${configsDir}/${configFileEnv}`; | ||
if (!fs.existsSync(requireFile)) { | ||
throw Error(`API_TESTING_CONFIG_FILE was set but neither '${configFileEnv}' nor '${requireFile}' exist.`); | ||
} | ||
} | ||
} else { | ||
// If .api-testing.config.json doesn't exist in root folder, throw helpful error | ||
const localConfigFile = '.api-testing.config.json'; | ||
requireFile = `${baseDir}/${localConfigFile}`; | ||
if (!fs.existsSync(requireFile)) { | ||
throw Error(`Missing local config! Please create a ${localConfigFile} config`); | ||
} | ||
} | ||
if (baseURLEnv) { | ||
return { | ||
base_uri: baseURLEnv | ||
}; | ||
} else if (requireFile) { | ||
if (!fs.existsSync(requireFile)) { | ||
// was it just the filename without the default config dir? | ||
requireFile = `${configsDir}/${configFileEnv}`; | ||
if (!fs.existsSync(requireFile)) { | ||
throw Error(`API_TESTING_CONFIG_FILE was set but neither '${configFileEnv}' nor '${requireFile}' exist.`); | ||
} | ||
} | ||
} else { | ||
// If .api-testing.config.json doesn't exist in root folder, throw helpful error | ||
const localConfigFile = '.api-testing.config.json'; | ||
requireFile = `${baseDir}/${localConfigFile}`; | ||
if (!fs.existsSync(requireFile)) { | ||
throw Error(`Missing local config! Please create a ${localConfigFile} config`); | ||
} | ||
} | ||
return require(requireFile); | ||
return require(requireFile); | ||
}; |
226
lib/REST.js
@@ -7,132 +7,132 @@ 'use strict'; | ||
class RESTClient { | ||
/** | ||
* Constructs a new agent for making HTTP requests to the MediaWiki | ||
* REST API. The agent acts like a browser session and has its own | ||
* cookie jar. | ||
* Pass in an optional supertest agent with user session information (cookie jar) | ||
* for the client to behave as a logged in user. | ||
* | ||
* @param {string} endpoint REST endpoint path | ||
* @param {Object} agent supertest agent | ||
*/ | ||
constructor(endpoint = 'rest.php/v1', agent = null) { | ||
this.pathPrefix = endpoint; | ||
if (agent) { | ||
this.req = agent.req; | ||
this.username = agent.username; | ||
this.userid = agent.userid; | ||
} else { | ||
this.req = supertest.agent(config.base_uri); | ||
this.username = '<anon>'; | ||
this.userid = 0; | ||
} | ||
} | ||
/** | ||
* Constructs a new agent for making HTTP requests to the MediaWiki | ||
* REST API. The agent acts like a browser session and has its own | ||
* cookie jar. | ||
* Pass in an optional supertest agent with user session information (cookie jar) | ||
* for the client to behave as a logged in user. | ||
* | ||
* @param {string} endpoint REST endpoint path | ||
* @param {Object} agent supertest agent | ||
*/ | ||
constructor(endpoint = 'rest.php/v1', agent = null) { | ||
this.pathPrefix = endpoint; | ||
if (agent) { | ||
this.req = agent.req; | ||
this.username = agent.username; | ||
this.userid = agent.userid; | ||
} else { | ||
this.req = supertest.agent(config.base_uri); | ||
this.username = '<anon>'; | ||
this.userid = 0; | ||
} | ||
} | ||
/** | ||
* Constructs an HTTP request to the REST API and returns the | ||
* corresponding supertest Test object, which behaves like a | ||
* superagent Request. It can be used like a Promise that resolves | ||
* to a Response. | ||
* | ||
* Call end(), then(), or use await to send the request. | ||
* | ||
* @param {string} endpoint | ||
* @param {string} method | ||
* @param {Object|string} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async request(endpoint, method, params = {}, headers = {}) { | ||
let req; | ||
endpoint = this.pathPrefix + endpoint; | ||
switch (method.toUpperCase()) { | ||
case 'GET': | ||
req = this.req.get(endpoint) | ||
/** | ||
* Constructs an HTTP request to the REST API and returns the | ||
* corresponding supertest Test object, which behaves like a | ||
* superagent Request. It can be used like a Promise that resolves | ||
* to a Response. | ||
* | ||
* Call end(), then(), or use await to send the request. | ||
* | ||
* @param {string} endpoint | ||
* @param {string} method | ||
* @param {Object|string} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async request(endpoint, method, params = {}, headers = {}) { | ||
let req; | ||
endpoint = this.pathPrefix + endpoint; | ||
switch (method.toUpperCase()) { | ||
case 'GET': | ||
req = this.req.get(endpoint) | ||
.query(params) | ||
.set(headers); | ||
break; | ||
case 'POST': | ||
req = this.req.post(endpoint) | ||
break; | ||
case 'POST': | ||
req = this.req.post(endpoint) | ||
.send(params) | ||
.set(headers); | ||
break; | ||
case 'PUT': | ||
req = this.req.put(endpoint) | ||
break; | ||
case 'PUT': | ||
req = this.req.put(endpoint) | ||
.send(params) | ||
.set(headers); | ||
break; | ||
case 'DELETE': | ||
req = this.req.del(endpoint) | ||
break; | ||
case 'DELETE': | ||
req = this.req.del(endpoint) | ||
.query(params) | ||
.set(headers); | ||
break; | ||
default: | ||
throw new Error(`The following method is unsupported: ${method}`); | ||
} | ||
break; | ||
default: | ||
throw new Error(`The following method is unsupported: ${method}`); | ||
} | ||
return req; | ||
} | ||
return req; | ||
} | ||
_objectKeysToLowerCase(headers) { | ||
return Object.keys(headers).reduce((updatedHeaders, key) => { | ||
updatedHeaders[key.toLowerCase()] = headers[key]; | ||
return updatedHeaders; | ||
}, {}); | ||
} | ||
_objectKeysToLowerCase(headers) { | ||
return Object.keys(headers).reduce((updatedHeaders, key) => { | ||
updatedHeaders[key.toLowerCase()] = headers[key]; | ||
return updatedHeaders; | ||
}, {}); | ||
} | ||
/** | ||
* Constructs a GET request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object|null}params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async get(endpoint, params = {}, headers = {}) { | ||
return this.request(endpoint, 'GET', params, headers); | ||
} | ||
/** | ||
* Constructs a GET request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object|null}params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async get(endpoint, params = {}, headers = {}) { | ||
return this.request(endpoint, 'GET', params, headers); | ||
} | ||
/** | ||
* Constructs a POST request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object|string} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async post(endpoint, params = {}, headers = {}) { | ||
const updatedHeaders = this._objectKeysToLowerCase(headers); | ||
return this.request(endpoint, 'POST', params, Object.assign({ 'content-type': 'application/json' }, updatedHeaders)); | ||
} | ||
/** | ||
* Constructs a POST request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object|string} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async post(endpoint, params = {}, headers = {}) { | ||
const updatedHeaders = this._objectKeysToLowerCase(headers); | ||
return this.request(endpoint, 'POST', params, Object.assign({ 'content-type': 'application/json' }, updatedHeaders)); | ||
} | ||
/** | ||
* Constructs a PUT request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object|string} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async put(endpoint, params = {}, headers = {}) { | ||
const updatedHeaders = this._objectKeysToLowerCase(headers); | ||
return this.request(endpoint, 'PUT', params, Object.assign({ 'content-type': 'application/json' }, updatedHeaders)); | ||
} | ||
/** | ||
* Constructs a PUT request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object|string} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async put(endpoint, params = {}, headers = {}) { | ||
const updatedHeaders = this._objectKeysToLowerCase(headers); | ||
return this.request(endpoint, 'PUT', params, Object.assign({ 'content-type': 'application/json' }, updatedHeaders)); | ||
} | ||
/** | ||
* Constructs a DELETE request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async del(endpoint, params = {}, headers = {}) { | ||
return this.request(endpoint, 'DELETE', params, headers); | ||
} | ||
/** | ||
* Constructs a DELETE request and returns the | ||
* corresponding supertest Test object | ||
* | ||
* @param {string} endpoint | ||
* @param {Object} params | ||
* @param {Object} headers | ||
* @return {Promise<*>} | ||
*/ | ||
async del(endpoint, params = {}, headers = {}) { | ||
return this.request(endpoint, 'DELETE', params, headers); | ||
} | ||
} | ||
module.exports = RESTClient; |
'use strict'; | ||
module.exports = { | ||
/** | ||
* Returns a unique string of random alphanumeric characters. | ||
* | ||
* @param {number} n the desired number of characters | ||
* @return {string} | ||
*/ | ||
uniq(n = 10) { | ||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
let result = ''; | ||
/** | ||
* Returns a unique string of random alphanumeric characters. | ||
* | ||
* @param {number} n the desired number of characters | ||
* @return {string} | ||
*/ | ||
uniq(n = 10) { | ||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
let result = ''; | ||
for (let i = 0; i < n; i++) { | ||
result += characters.charAt(Math.floor(Math.random() * characters.length)); | ||
} | ||
return result; | ||
}, | ||
for (let i = 0; i < n; i++) { | ||
result += characters.charAt(Math.floor(Math.random() * characters.length)); | ||
} | ||
return result; | ||
}, | ||
/** | ||
* Returns a unique title for use in tests. | ||
* | ||
* @param {string} prefix | ||
* @return {string} | ||
*/ | ||
title(prefix = '') { | ||
return prefix + this.uniq(); | ||
}, | ||
/** | ||
* Returns a unique title for use in tests. | ||
* | ||
* @param {string} prefix | ||
* @return {string} | ||
*/ | ||
title(prefix = '') { | ||
return prefix + this.uniq(); | ||
}, | ||
/** | ||
* Returns a promise that will resolve in no less than the given number of milliseconds. | ||
* | ||
* @param {number} ms wait time in milliseconds | ||
* @return {Promise<void>} | ||
*/ | ||
sleep(ms = 1000) { | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
}, | ||
/** | ||
* Returns a promise that will resolve in no less than the given number of milliseconds. | ||
* | ||
* @param {number} ms wait time in milliseconds | ||
* @return {Promise<void>} | ||
*/ | ||
sleep(ms = 1000) { | ||
// eslint-disable-next-line no-promise-executor-return | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
}, | ||
/** | ||
* Converts a title string to DB key form by replacing any spaces with underscores. | ||
* | ||
* @param {string} title | ||
* @return {string} | ||
*/ | ||
dbkey(title) { | ||
return title.replace(/ /g, '_'); | ||
} | ||
/** | ||
* Converts a title string to DB key form by replacing any spaces with underscores. | ||
* | ||
* @param {string} title | ||
* @return {string} | ||
*/ | ||
dbkey(title) { | ||
return title.replace(/ /g, '_'); | ||
} | ||
}; |
@@ -19,48 +19,48 @@ 'use strict'; | ||
const runJobs = async (n = 1) => { | ||
if (!config.secret_key) { | ||
throw Error('Missing secret_key configuration value. ' + | ||
if (!config.secret_key) { | ||
throw Error('Missing secret_key configuration value. ' + | ||
'Set secret_key to the value of $wgSecretKey from LocalSettings.php'); | ||
} | ||
} | ||
const sig = (params) => { | ||
const data = {}; | ||
const keys = Object.keys(params).sort(); | ||
const sig = (params) => { | ||
const data = {}; | ||
const keys = Object.keys(params).sort(); | ||
for (const k of keys) { | ||
data[k] = params[k]; | ||
} | ||
for (const k of keys) { | ||
data[k] = params[k]; | ||
} | ||
const s = querystring.stringify(data); | ||
const hmac = crypto.createHmac('sha1', config.secret_key).update(s); | ||
return hmac.digest('hex'); | ||
}; | ||
const s = querystring.stringify(data); | ||
const hmac = crypto.createHmac('sha1', config.secret_key).update(s); | ||
return hmac.digest('hex'); | ||
}; | ||
const params = { | ||
title: 'Special:RunJobs', | ||
maxjobs: n, | ||
maxtime: Math.max(n * 10, 60), | ||
async: '', // false | ||
stats: '1', // true | ||
tasks: '', // what does this mean? | ||
sigexpiry: Math.ceil(Date.now() / 1000 + 60 * 60) // one hour | ||
}; | ||
const params = { | ||
title: 'Special:RunJobs', | ||
maxjobs: n, | ||
maxtime: Math.max(n * 10, 60), | ||
async: '', // false | ||
stats: '1', // true | ||
tasks: '', // what does this mean? | ||
sigexpiry: Math.ceil(Date.now() / 1000 + 60 * 60) // one hour | ||
}; | ||
params.signature = sig(params); | ||
params.signature = sig(params); | ||
const response = await supertest.agent(config.base_uri).post('index.php').type('form').send(params); | ||
const response = await supertest.agent(config.base_uri).post('index.php').type('form').send(params); | ||
assert.isDefined(response.body.reached, `Text: ${response.text}`); | ||
assert.isDefined(response.body.reached, `Text: ${response.text}`); | ||
if (response.body.reached === 'none-ready') { | ||
// The backend reports that no more jobs are ready. | ||
return 0; | ||
} else { | ||
// If response.body.jobs is empty, we may be hitting an infinite | ||
// loop here. That should not happen. | ||
assert.isNotEmpty(response.body.jobs); | ||
if (response.body.reached === 'none-ready') { | ||
// The backend reports that no more jobs are ready. | ||
return 0; | ||
} else { | ||
// If response.body.jobs is empty, we may be hitting an infinite | ||
// loop here. That should not happen. | ||
assert.isNotEmpty(response.body.jobs); | ||
// There is no reliable way to get the current size of the job queue. | ||
// Just return some number to indicate that there is more work to be done. | ||
return 100; | ||
} | ||
// There is no reliable way to get the current size of the job queue. | ||
// Just return some number to indicate that there is more work to be done. | ||
return 100; | ||
} | ||
}; | ||
@@ -75,23 +75,23 @@ | ||
const runAllJobs = async () => { | ||
const log = () => {}; // TODO: allow optional logging | ||
const log = () => {}; // TODO: allow optional logging | ||
while (true) { | ||
log('Running jobs...'); | ||
const jobsRemaining = await runJobs(10); | ||
while (true) { | ||
log('Running jobs...'); | ||
const jobsRemaining = await runJobs(10); | ||
if (jobsRemaining) { | ||
log(`Still ${jobsRemaining} in the queue.`); | ||
} else { | ||
break; | ||
} | ||
} | ||
if (jobsRemaining) { | ||
log(`Still ${jobsRemaining} in the queue.`); | ||
} else { | ||
break; | ||
} | ||
} | ||
}; | ||
const getSecretKey = () => { | ||
return config.secret_key || null; | ||
return config.secret_key || null; | ||
}; | ||
module.exports = { | ||
runAllJobs, | ||
getSecretKey | ||
runAllJobs, | ||
getSecretKey | ||
}; |
{ | ||
"name": "api-testing", | ||
"version": "1.4.2", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "npm run lint && mocha", | ||
"lint": "eslint --cache --max-warnings 0 ." | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"engines": { | ||
"node": ">= 10.0.0" | ||
}, | ||
"dependencies": { | ||
"chai": "^4.2.0", | ||
"supertest": "^5.0.0-0" | ||
}, | ||
"devDependencies": { | ||
"eslint-config-wikimedia": "0.16.1", | ||
"mocha": "7.2.0" | ||
} | ||
"name": "api-testing", | ||
"version": "1.5.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "npm run lint && mocha --parallel", | ||
"lint": "eslint --cache .", | ||
"doc": "jsdoc -c .jsdoc.json" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"engines": { | ||
"node": ">= 10.0.0" | ||
}, | ||
"dependencies": { | ||
"chai": "^4.2.0", | ||
"supertest": "^5.0.0-0" | ||
}, | ||
"devDependencies": { | ||
"eslint-config-wikimedia": "0.18.0", | ||
"jsdoc": "3.6.7", | ||
"jsdoc-class-hierarchy": "1.1.2", | ||
"jsdoc-wmf-theme": "0.0.3", | ||
"mocha": "7.2.0" | ||
} | ||
} |
@@ -7,25 +7,25 @@ 'use strict'; | ||
describe('Tag creation', () => { | ||
let alice; | ||
describe('Tag creation', () => { | ||
let alice; | ||
before(async () => { | ||
alice = await action.alice(); | ||
}); | ||
before(async () => { | ||
alice = await action.alice(); | ||
}); | ||
// If running this locally, the tag will already exist after the test has run once. | ||
it('Should create a new tag and return existing', async () => { | ||
const tagName = 'api-test'; | ||
const tagDisplay = 'TestTagDisplay'; | ||
const tag = await action.makeTag(tagName, `''${tagDisplay}''`); | ||
assert.deepEqual(tag, tagName); | ||
// If running this locally, the tag will already exist after the test has run once. | ||
it('Should create a new tag and return existing', async () => { | ||
const tagName = 'api-test'; | ||
const tagDisplay = 'TestTagDisplay'; | ||
const tag = await action.makeTag(tagName, `''${tagDisplay}''`); | ||
assert.deepEqual(tag, tagName); | ||
const tagList = await alice.list('tags', { tglimit: 50, tgprop: 'displayname' }); | ||
assert.deepInclude(tagList, { name: tagName, displayname: `<i>${tagDisplay}</i>` }); | ||
const tagList = await alice.list('tags', { tglimit: 50, tgprop: 'displayname' }); | ||
assert.deepInclude(tagList, { name: tagName, displayname: `<i>${tagDisplay}</i>` }); | ||
// If tag has already been created, should still return because it has been cached | ||
const tag2 = await action.makeTag(tagName); | ||
assert.deepEqual(tag2, tagName); | ||
}); | ||
// If tag has already been created, should still return because it has been cached | ||
const tag2 = await action.makeTag(tagName); | ||
assert.deepEqual(tag2, tagName); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -7,53 +7,53 @@ 'use strict'; | ||
it('Should share supertest agent between REST and Action API Clients', async () => { | ||
const actionAgent = await action.alice(); | ||
const aliceUsername = actionAgent.username; | ||
const aliceUserId = actionAgent.userid; | ||
it('Should share supertest agent between REST and Action API Clients', async () => { | ||
const actionAgent = await action.alice(); | ||
const aliceUsername = actionAgent.username; | ||
const aliceUserId = actionAgent.userid; | ||
const restAgent = clientFactory.getRESTClient('', actionAgent); | ||
assert.strictEqual(restAgent.req, actionAgent.req); | ||
assert.propertyVal(restAgent, 'username', aliceUsername); | ||
assert.propertyVal(restAgent, 'userid', aliceUserId); | ||
const restAgent = clientFactory.getRESTClient('', actionAgent); | ||
assert.strictEqual(restAgent.req, actionAgent.req); | ||
assert.propertyVal(restAgent, 'username', aliceUsername); | ||
assert.propertyVal(restAgent, 'userid', aliceUserId); | ||
const actionAgentFromRest = clientFactory.getActionClient(restAgent); | ||
assert.strictEqual(restAgent.req, actionAgentFromRest.req); | ||
assert.propertyVal(restAgent, 'username', aliceUsername); | ||
assert.propertyVal(restAgent, 'userid', aliceUserId); | ||
}); | ||
const actionAgentFromRest = clientFactory.getActionClient(restAgent); | ||
assert.strictEqual(restAgent.req, actionAgentFromRest.req); | ||
assert.propertyVal(restAgent, 'username', aliceUsername); | ||
assert.propertyVal(restAgent, 'userid', aliceUserId); | ||
}); | ||
it('Should return a test client representing an anonymous user when agent is not supplied to constructor', | ||
async () => { | ||
// TODO: This should actually test the cookie jar itself. | ||
const restAgent = clientFactory.getRESTClient(); | ||
assert.propertyVal(restAgent, 'username', '<anon>'); | ||
assert.propertyVal(restAgent, 'userid', 0); | ||
it('Should return a test client representing an anonymous user when agent is not supplied to constructor', | ||
async () => { | ||
// TODO: This should actually test the cookie jar itself. | ||
const restAgent = clientFactory.getRESTClient(); | ||
assert.propertyVal(restAgent, 'username', '<anon>'); | ||
assert.propertyVal(restAgent, 'userid', 0); | ||
const actionAgent = clientFactory.getActionClient(); | ||
assert.propertyVal(actionAgent, 'username', '<anon>'); | ||
assert.propertyVal(actionAgent, 'userid', 0); | ||
} | ||
); | ||
const actionAgent = clientFactory.getActionClient(); | ||
assert.propertyVal(actionAgent, 'username', '<anon>'); | ||
assert.propertyVal(actionAgent, 'userid', 0); | ||
} | ||
); | ||
it('Should return a REST test client for a specific endpoint', | ||
async () => { | ||
// TODO: This should actually test the cookie jar itself. | ||
// TODO: Don't use private member, use mocks ? | ||
const restAgent = clientFactory.getRESTClient('/some/path'); | ||
assert.propertyVal(restAgent, 'pathPrefix', '/some/path'); | ||
} | ||
); | ||
it('Should return a REST test client for a specific endpoint', | ||
async () => { | ||
// TODO: This should actually test the cookie jar itself. | ||
// TODO: Don't use private member, use mocks ? | ||
const restAgent = clientFactory.getRESTClient('/some/path'); | ||
assert.propertyVal(restAgent, 'pathPrefix', '/some/path'); | ||
} | ||
); | ||
it('Should return a supertest agent with an empty cookie jar', async () => { | ||
const httpAgent = clientFactory.getHttpClient(); | ||
// empty string indicates no specified app domain | ||
assert.equal(httpAgent.app, ''); | ||
it('Should return a supertest agent with an empty cookie jar', async () => { | ||
const httpAgent = clientFactory.getHttpClient(); | ||
// empty string indicates no specified app domain | ||
assert.equal(httpAgent.app, ''); | ||
}); | ||
}); | ||
it('Should return a supertest agent with a cookie jar set', async () => { | ||
const actionAgent = await action.alice(); | ||
const httpAgentWithUser = clientFactory.getHttpClient(actionAgent); | ||
assert.strictEqual(httpAgentWithUser.jar, actionAgent.req.jar); | ||
}); | ||
it('Should return a supertest agent with a cookie jar set', async () => { | ||
const actionAgent = await action.alice(); | ||
const httpAgentWithUser = clientFactory.getHttpClient(actionAgent); | ||
assert.strictEqual(httpAgentWithUser.jar, actionAgent.req.jar); | ||
}); | ||
}); |
@@ -12,5 +12,5 @@ 'use strict'; | ||
const testConfigFiles = [ | ||
[`${testConfigsDir}/quibble.json`, `{ "file": "${testConfigsDir}/quibble.json" }`], | ||
[`${testConfigsDir}/example.json`, `{ "file": "${testConfigsDir}/example.json" }`], | ||
[`${testRootDir}/.api-testing.config.json`, `{ "file": "${testRootDir}/.api-testing.config.json" }`] | ||
[`${testConfigsDir}/quibble.json`, `{ "file": "${testConfigsDir}/quibble.json" }`], | ||
[`${testConfigsDir}/example.json`, `{ "file": "${testConfigsDir}/example.json" }`], | ||
[`${testRootDir}/.api-testing.config.json`, `{ "file": "${testRootDir}/.api-testing.config.json" }`] | ||
]; | ||
@@ -20,8 +20,8 @@ | ||
const createTestConfigs = async () => { | ||
await fsp.mkdir(testRootDir); | ||
await fsp.mkdir(testConfigsDir); | ||
const fileWritePromises = testConfigFiles.map( | ||
(fileInfo) => fsp.writeFile(fileInfo[0], fileInfo[1]) | ||
); | ||
await Promise.all(fileWritePromises); | ||
await fsp.mkdir(testRootDir); | ||
await fsp.mkdir(testConfigsDir); | ||
const fileWritePromises = testConfigFiles.map( | ||
(fileInfo) => fsp.writeFile(fileInfo[0], fileInfo[1]) | ||
); | ||
await Promise.all(fileWritePromises); | ||
}; | ||
@@ -31,15 +31,15 @@ | ||
const deleteTestConfigs = async () => { | ||
// NOTE: rmdir does not support recursion in node 11 and earlier. | ||
const filesInConfigDir = await fsp.readdir(testConfigsDir, { withFileTypes: true }); | ||
await Promise.all(filesInConfigDir.map((dirent) => fsp.unlink(`${testConfigsDir}/${dirent.name}`))); | ||
// NOTE: rmdir does not support recursion in node 11 and earlier. | ||
const filesInConfigDir = await fsp.readdir(testConfigsDir, { withFileTypes: true }); | ||
await Promise.all(filesInConfigDir.map((dirent) => fsp.unlink(`${testConfigsDir}/${dirent.name}`))); | ||
const filesInRootDir = await fsp.readdir(testRootDir, { withFileTypes: true }); | ||
await Promise.all(filesInRootDir.map( | ||
(dirent) => dirent.isDirectory() ? | ||
fsp.rmdir(`${testRootDir}/${dirent.name}`) : | ||
fsp.unlink(`${testRootDir}/${dirent.name}`) | ||
)); | ||
const filesInRootDir = await fsp.readdir(testRootDir, { withFileTypes: true }); | ||
await Promise.all(filesInRootDir.map( | ||
(dirent) => dirent.isDirectory() ? | ||
fsp.rmdir(`${testRootDir}/${dirent.name}`) : | ||
fsp.unlink(`${testRootDir}/${dirent.name}`) | ||
)); | ||
// await fsp.rmdir(testConfigsDir); | ||
await fsp.rmdir(testRootDir); | ||
// await fsp.rmdir(testConfigsDir); | ||
await fsp.rmdir(testRootDir); | ||
}; | ||
@@ -50,55 +50,55 @@ | ||
describe('Configuration', () => { | ||
let envVar; | ||
let envVar; | ||
before(async () => { | ||
// Save the env var for other tests | ||
envVar = process.env.API_TESTING_CONFIG_FILE; | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
await createTestConfigs(); | ||
}); | ||
before(async () => { | ||
// Save the env var for other tests | ||
envVar = process.env.API_TESTING_CONFIG_FILE; | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
await createTestConfigs(); | ||
}); | ||
after(async () => { | ||
await deleteTestConfigs(); | ||
after(async () => { | ||
await deleteTestConfigs(); | ||
if (envVar) { | ||
process.env.API_TESTING_CONFIG_FILE = envVar; | ||
} | ||
}); | ||
if (envVar) { | ||
process.env.API_TESTING_CONFIG_FILE = envVar; | ||
} | ||
}); | ||
describe(`Using ${testRootDir} as the configuration root folder`, () => { | ||
it('Use .api-testing.config.json file if API_TESTING_CONFIG_FILE does not exist', () => { | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
assert.deepEqual(getConfig(testRootDir), { file: `${testRootDir}/.api-testing.config.json` }); | ||
}); | ||
describe(`Using ${testRootDir} as the configuration root folder`, () => { | ||
it('Use .api-testing.config.json file if API_TESTING_CONFIG_FILE does not exist', () => { | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
assert.deepEqual(getConfig(testRootDir), { file: `${testRootDir}/.api-testing.config.json` }); | ||
}); | ||
it('Select full path config set in API_TESTING_CONFIG_FILE env variable over local config', () => { | ||
process.env.API_TESTING_CONFIG_FILE = `${testConfigsDir}/quibble.json`; | ||
assert.deepEqual(getConfig(testRootDir), { file: `${testConfigsDir}/quibble.json` }); | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
}); | ||
it('Select full path config set in API_TESTING_CONFIG_FILE env variable over local config', () => { | ||
process.env.API_TESTING_CONFIG_FILE = `${testConfigsDir}/quibble.json`; | ||
assert.deepEqual(getConfig(testRootDir), { file: `${testConfigsDir}/quibble.json` }); | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
}); | ||
it('Throw exception if config file set in API_TESTING_CONFIG_FILE does not exist', () => { | ||
process.env.API_TESTING_CONFIG_FILE = 'idonotexist.json'; | ||
assert.throws(() => getConfig(testRootDir), Error, /API_TESTING_CONFIG_FILE was set but neither/); | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
}); | ||
it('Throw exception if config file set in API_TESTING_CONFIG_FILE does not exist', () => { | ||
process.env.API_TESTING_CONFIG_FILE = 'idonotexist.json'; | ||
assert.throws(() => getConfig(testRootDir), Error, /API_TESTING_CONFIG_FILE was set but neither/); | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
}); | ||
describe('Renaming required root folder config ".api-testing.config.json"', () => { | ||
it('Throws exception if ".api-testing.config.json" doesnt exist and API_TESTING_CONFIG_FILE is not set', () => { | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
fs.rename(`${testRootDir}/.api-testing.config.json`, `${testRootDir}/wrong.json`, (err) => { | ||
assert.throws(() => getConfig(testRootDir), Error, /Missing local config!/); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('Renaming required root folder config ".api-testing.config.json"', () => { | ||
it('Throws exception if ".api-testing.config.json" doesnt exist and API_TESTING_CONFIG_FILE is not set', () => { | ||
delete process.env.API_TESTING_CONFIG_FILE; | ||
fs.rename(`${testRootDir}/.api-testing.config.json`, `${testRootDir}/wrong.json`, (err) => { | ||
assert.throws(() => getConfig(testRootDir), Error, /Missing local config!/); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('Using REST_BASE_URL for configuration', () => { | ||
it('should return a json when REST_BASE_URL is set', () => { | ||
process.env.REST_BASE_URL = 'http://localhost:8081/'; | ||
describe('Using REST_BASE_URL for configuration', () => { | ||
it('should return a json when REST_BASE_URL is set', () => { | ||
process.env.REST_BASE_URL = 'http://localhost:8081/'; | ||
assert.deepEqual(getConfig(), { base_uri: process.env.REST_BASE_URL }); | ||
delete process.env.REST_BASE_URL; | ||
}); | ||
}); | ||
assert.deepEqual(getConfig(), { base_uri: process.env.REST_BASE_URL }); | ||
delete process.env.REST_BASE_URL; | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
31
1138
62274
5