Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

api-testing

Package Overview
Dependencies
Maintainers
25
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

api-testing - npm Package Compare versions

Comparing version 1.4.2 to 1.5.0

.eslintignore

2

.eslintrc.json

@@ -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"
}
'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);
};

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc