mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-27 09:06:15 +01:00
fix(writeapi): tests
This commit is contained in:
@@ -2,117 +2,4 @@ PostsObject:
|
||||
description: One of the objects in the array returned from `Posts.getPostSummaryByPids`
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
pid:
|
||||
type: number
|
||||
tid:
|
||||
type: number
|
||||
description: A topic identifier
|
||||
content:
|
||||
type: string
|
||||
uid:
|
||||
type: number
|
||||
description: A user identifier
|
||||
timestamp:
|
||||
type: number
|
||||
deleted:
|
||||
type: boolean
|
||||
upvotes:
|
||||
type: number
|
||||
downvotes:
|
||||
type: number
|
||||
votes:
|
||||
type: number
|
||||
timestampISO:
|
||||
type: string
|
||||
description: An ISO 8601 formatted date string (complementing `timestamp`)
|
||||
user:
|
||||
type: object
|
||||
properties:
|
||||
uid:
|
||||
type: number
|
||||
description: A user identifier
|
||||
username:
|
||||
type: string
|
||||
description: A friendly name for a given user account
|
||||
userslug:
|
||||
type: string
|
||||
description: An URL-safe variant of the username (i.e. lower-cased, spaces
|
||||
removed, etc.)
|
||||
picture:
|
||||
type: string
|
||||
nullable: true
|
||||
status:
|
||||
type: string
|
||||
icon:text:
|
||||
type: string
|
||||
description: A single-letter representation of a username. This is used in the
|
||||
auto-generated icon given to users without
|
||||
an avatar
|
||||
icon:bgColor:
|
||||
type: string
|
||||
description: A six-character hexadecimal colour code assigned to the user. This
|
||||
value is used in conjunction with
|
||||
`icon:text` for the user's auto-generated
|
||||
icon
|
||||
example: "#f44336"
|
||||
topic:
|
||||
type: object
|
||||
properties:
|
||||
uid:
|
||||
type: number
|
||||
description: A user identifier
|
||||
tid:
|
||||
type: number
|
||||
description: A topic identifier
|
||||
title:
|
||||
type: string
|
||||
cid:
|
||||
type: number
|
||||
description: A category identifier
|
||||
slug:
|
||||
type: string
|
||||
deleted:
|
||||
type: number
|
||||
postcount:
|
||||
type: number
|
||||
mainPid:
|
||||
type: number
|
||||
description: The post id of the first post in this topic (also called the
|
||||
"original post")
|
||||
teaserPid:
|
||||
type: number
|
||||
description: The post id of the teaser (the most recent post, depending on settings)
|
||||
nullable: true
|
||||
titleRaw:
|
||||
type: string
|
||||
category:
|
||||
type: object
|
||||
properties:
|
||||
cid:
|
||||
type: number
|
||||
description: A category identifier
|
||||
name:
|
||||
type: string
|
||||
icon:
|
||||
type: string
|
||||
slug:
|
||||
type: string
|
||||
parentCid:
|
||||
type: number
|
||||
description: The category identifier for the category that is the immediate
|
||||
ancestor of the current category
|
||||
bgColor:
|
||||
type: string
|
||||
color:
|
||||
type: string
|
||||
backgroundImage:
|
||||
nullable: true
|
||||
imageClass:
|
||||
nullable: true
|
||||
type: string
|
||||
isMainPost:
|
||||
type: boolean
|
||||
replies:
|
||||
type: number
|
||||
$ref: ./PostObject.yaml#/PostObject
|
||||
@@ -1,35 +0,0 @@
|
||||
UserRequest:
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
example: Dragon Fruit
|
||||
email:
|
||||
type: string
|
||||
example: dragonfruit@example.org
|
||||
fullname:
|
||||
type: string
|
||||
example: Mr. Dragon Fruit Jr.
|
||||
website:
|
||||
type: string
|
||||
example: 'https://example.org'
|
||||
location:
|
||||
type: string
|
||||
example: 'Toronto, Canada'
|
||||
groupTitle:
|
||||
type: string
|
||||
example: '["administrators","Staff"]'
|
||||
birthday:
|
||||
type: string
|
||||
description: A birthdate given in an ISO format parseable by the Date object
|
||||
example: 03/27/2020
|
||||
signature:
|
||||
type: string
|
||||
example: |
|
||||
This is an example signature
|
||||
It can span multiple lines.
|
||||
aboutme:
|
||||
type: string
|
||||
example: |
|
||||
This is a paragraph all about how my life got twist-turned upside-down
|
||||
and I'd like to take a minute and sit right here,
|
||||
to tell you all about how I because the administrator of NodeBB
|
||||
@@ -28,6 +28,8 @@ tags:
|
||||
- name: categories
|
||||
description: Administrative calls to manage categories
|
||||
paths:
|
||||
/users/:
|
||||
$ref: 'write/users.yaml'
|
||||
/users/{uid}:
|
||||
$ref: 'write/users/uid.yaml'
|
||||
/users/{uid}/settings:
|
||||
@@ -40,12 +42,14 @@ paths:
|
||||
$ref: 'write/users/uid/follow.yaml'
|
||||
/users/{uid}/ban:
|
||||
$ref: 'write/users/uid/ban.yaml'
|
||||
/users/{uid}/tokens:
|
||||
$ref: 'write/users/uid/tokens.yaml'
|
||||
/users/{uid}/tokens/{token}:
|
||||
$ref: 'write/users/uid/tokens/token.yaml'
|
||||
/categories/:
|
||||
$ref: 'write/categories.yaml'
|
||||
/groups/:
|
||||
$ref: 'write/groups.yaml'
|
||||
/groups/{slug}:
|
||||
$ref: 'write/groups/slug.yaml'
|
||||
/groups/{slug}/membership/{uid}:
|
||||
$ref: 'write/groups/slug/membership/uid.yaml'
|
||||
/topics:
|
||||
|
||||
@@ -12,38 +12,37 @@ post:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: My New Category
|
||||
description:
|
||||
type: string
|
||||
example: Lorem ipsum, dolor sit amet
|
||||
parentCid:
|
||||
type: number
|
||||
example: 0
|
||||
cloneFromCid:
|
||||
type: number
|
||||
example: 0
|
||||
icon:
|
||||
type: string
|
||||
example: bullhorn
|
||||
description: A ForkAwesome icon without the `fa-` prefix
|
||||
bgColor:
|
||||
type: string
|
||||
example: '#ffffff'
|
||||
color:
|
||||
type: string
|
||||
example: '#000000'
|
||||
link:
|
||||
type: string
|
||||
example: 'https://example.org'
|
||||
class:
|
||||
type: string
|
||||
example: 'col-md-3 col-xs-6'
|
||||
backgroundImage:
|
||||
type: string
|
||||
example: '/assets/relative/path/to/image'
|
||||
required:
|
||||
- name
|
||||
example:
|
||||
name: My New Category
|
||||
description: Lorem ipsum, dolor sit amet
|
||||
parentCid: 0
|
||||
cloneFromCid: 0
|
||||
icon: bullhorn
|
||||
bgColor: '#ffffff'
|
||||
color: '#000000'
|
||||
link: 'https://example.org'
|
||||
class: 'col-md-3 col-xs-6'
|
||||
backgroundImage: '/assets/relative/path/to/image'
|
||||
responses:
|
||||
'200':
|
||||
description: category successfully created
|
||||
|
||||
@@ -12,6 +12,7 @@ post:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: 'My Test Group'
|
||||
timestamp:
|
||||
type: number
|
||||
disableJoinRequests:
|
||||
@@ -23,6 +24,7 @@ post:
|
||||
hidden:
|
||||
type: number
|
||||
enum: [0, 1]
|
||||
example: 1
|
||||
ownerUid:
|
||||
type: number
|
||||
private:
|
||||
@@ -37,9 +39,6 @@ post:
|
||||
type: number
|
||||
required:
|
||||
- name
|
||||
example:
|
||||
name: 'My Test Group'
|
||||
hidden: 1
|
||||
responses:
|
||||
'200':
|
||||
description: group successfully created
|
||||
@@ -52,21 +51,3 @@ post:
|
||||
$ref: ../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
$ref: ../components/schemas/GroupObject.yaml#/GroupDataObject
|
||||
delete:
|
||||
tags:
|
||||
- groups
|
||||
summary: Delete an existing group
|
||||
description: This operation deletes an existing group, all members within this group will cease to be members after the group is deleted.
|
||||
responses:
|
||||
'200':
|
||||
description: group successfully deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: ../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
type: object
|
||||
properties: {}
|
||||
@@ -10,7 +10,14 @@ put:
|
||||
type: string
|
||||
required: true
|
||||
description: slug of the group you would like to join
|
||||
example: my-group
|
||||
example: test-group
|
||||
- in: path
|
||||
name: uid
|
||||
schema:
|
||||
type: number
|
||||
required: true
|
||||
description: uid of the user to join the group
|
||||
example: 1
|
||||
responses:
|
||||
'200':
|
||||
description: group successfully joined, or membership requested
|
||||
@@ -24,3 +31,36 @@ put:
|
||||
response:
|
||||
type: object
|
||||
properties: {}
|
||||
delete:
|
||||
tags:
|
||||
- groups
|
||||
summary: leave a group
|
||||
description: This operation leaves a group.
|
||||
parameters:
|
||||
- in: path
|
||||
name: slug
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: slug of the group you would like to leave
|
||||
example: test-group
|
||||
- in: path
|
||||
name: uid
|
||||
schema:
|
||||
type: number
|
||||
required: true
|
||||
description: uid of the user to leave the group
|
||||
example: 1
|
||||
responses:
|
||||
'200':
|
||||
description: group successfully left
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: ../../../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
type: object
|
||||
properties: {}
|
||||
@@ -21,14 +21,13 @@ put:
|
||||
content:
|
||||
type: string
|
||||
description: New post content
|
||||
example: New post content
|
||||
title:
|
||||
type: string
|
||||
description: Topic title, only accepted for main posts
|
||||
example: New title
|
||||
required:
|
||||
- content
|
||||
example:
|
||||
content: 'New post content'
|
||||
title: 'New title'
|
||||
responses:
|
||||
'200':
|
||||
description: Post successfully edited
|
||||
@@ -40,7 +39,7 @@ put:
|
||||
status:
|
||||
$ref: ../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
$ref: ../../components/schemas/PostsObject.yaml#/PostsObject
|
||||
$ref: ../../components/schemas/PostObject.yaml#/PostObject
|
||||
delete:
|
||||
tags:
|
||||
- posts
|
||||
|
||||
@@ -12,22 +12,22 @@ post:
|
||||
properties:
|
||||
cid:
|
||||
type: number
|
||||
example: 1
|
||||
title:
|
||||
type: string
|
||||
example: Test topic
|
||||
content:
|
||||
type: string
|
||||
example: This is the test topic's content
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: [test, topic]
|
||||
required:
|
||||
- cid
|
||||
- title
|
||||
- content
|
||||
example:
|
||||
cid: 1
|
||||
title: Test topic
|
||||
content: This is the test topic's content
|
||||
responses:
|
||||
'200':
|
||||
description: topic successfully created
|
||||
@@ -39,4 +39,8 @@ post:
|
||||
status:
|
||||
$ref: ../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
$ref: ../components/schemas/TopicObject.yaml#/TopicObject
|
||||
allOf:
|
||||
- $ref: ../components/schemas/TopicObject.yaml#/TopicObject
|
||||
- type: object
|
||||
properties:
|
||||
mainPost: {}
|
||||
@@ -1,7 +1,7 @@
|
||||
post:
|
||||
tags:
|
||||
- topics
|
||||
summary: peply to a topic
|
||||
summary: reply to a topic
|
||||
description: This operation creates a new reply to an existing topic.
|
||||
parameters:
|
||||
- in: path
|
||||
@@ -10,7 +10,7 @@ post:
|
||||
type: string
|
||||
required: true
|
||||
description: a valid topic id
|
||||
example: 1
|
||||
example: 2
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -20,14 +20,13 @@ post:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
example: This is a test reply
|
||||
timestamp:
|
||||
type: number
|
||||
toPid:
|
||||
type: number
|
||||
required:
|
||||
- content
|
||||
example:
|
||||
content: This is a test reply
|
||||
responses:
|
||||
'200':
|
||||
description: post successfully created
|
||||
@@ -39,7 +38,7 @@ post:
|
||||
status:
|
||||
$ref: ../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
$ref: ../../components/schemas/PostsObject.yaml#/PostsObject
|
||||
$ref: ../../components/schemas/PostObject.yaml#/PostObject
|
||||
delete:
|
||||
tags:
|
||||
- topics
|
||||
@@ -52,7 +51,7 @@ delete:
|
||||
type: string
|
||||
required: true
|
||||
description: a valid topic id
|
||||
example: 1
|
||||
example: 2
|
||||
responses:
|
||||
'200':
|
||||
description: Topic successfully purged
|
||||
|
||||
@@ -23,10 +23,7 @@ put:
|
||||
description: 'An array of tags'
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
tags:
|
||||
- test
|
||||
- foobar
|
||||
example: [test, foobar]
|
||||
responses:
|
||||
'200':
|
||||
description: Topic tags successfully added
|
||||
|
||||
@@ -13,16 +13,15 @@ post:
|
||||
username:
|
||||
type: string
|
||||
description: 'If the username is taken, a number will be appended'
|
||||
example: Dragon Fruit
|
||||
password:
|
||||
type: string
|
||||
example: s3cre7password
|
||||
email:
|
||||
type: string
|
||||
example: dragonfruit@example.org
|
||||
required:
|
||||
- username
|
||||
example:
|
||||
username: Dragon Fruit
|
||||
password: s3cre7password
|
||||
email: dragonfruit@example.org
|
||||
responses:
|
||||
'200':
|
||||
description: user successfully created
|
||||
@@ -62,11 +61,7 @@ delete:
|
||||
description: A collection of uids
|
||||
items:
|
||||
type: number
|
||||
example:
|
||||
uids:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
example: [5, 6]
|
||||
responses:
|
||||
'200':
|
||||
description: user account(s) deleted
|
||||
|
||||
@@ -9,7 +9,7 @@ delete:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to delete
|
||||
example: 1
|
||||
example: 3
|
||||
responses:
|
||||
'200':
|
||||
description: user account deleted
|
||||
@@ -39,7 +39,35 @@ put:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../../components/schemas/UserRequest.yaml#/UserRequest
|
||||
type: object
|
||||
properties:
|
||||
fullname:
|
||||
type: string
|
||||
example: Mr. Dragon Fruit Jr.
|
||||
website:
|
||||
type: string
|
||||
example: 'https://example.org'
|
||||
location:
|
||||
type: string
|
||||
example: 'Toronto, Canada'
|
||||
groupTitle:
|
||||
type: string
|
||||
example: '["administrators","Staff"]'
|
||||
birthday:
|
||||
type: string
|
||||
description: A birthdate given in an ISO format parseable by the Date object
|
||||
example: 03/27/2020
|
||||
signature:
|
||||
type: string
|
||||
example: |
|
||||
This is an example signature
|
||||
It can span multiple lines.
|
||||
aboutme:
|
||||
type: string
|
||||
example: |
|
||||
This is a paragraph all about how my life got twist-turned upside-down
|
||||
and I'd like to take a minute and sit right here,
|
||||
to tell you all about how I because the administrator of NodeBB
|
||||
responses:
|
||||
'200':
|
||||
description: user profile updated
|
||||
|
||||
@@ -9,7 +9,7 @@ put:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to ban
|
||||
example: 1
|
||||
example: 2
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
@@ -46,7 +46,7 @@ delete:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to unban
|
||||
example: 1
|
||||
example: 2
|
||||
responses:
|
||||
'200':
|
||||
description: successfully unbanned user
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
post:
|
||||
put:
|
||||
tags:
|
||||
- users
|
||||
summary: follow a user
|
||||
@@ -9,7 +9,7 @@ post:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to follow
|
||||
example: 1
|
||||
example: 2
|
||||
responses:
|
||||
'200':
|
||||
description: successfully followed user
|
||||
@@ -33,7 +33,7 @@ delete:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to unfollow
|
||||
example: 1
|
||||
example: 2
|
||||
responses:
|
||||
'200':
|
||||
description: successfully unfollowed user
|
||||
|
||||
@@ -20,10 +20,10 @@ put:
|
||||
currentPassword:
|
||||
type: string
|
||||
description: test
|
||||
example: oldp455word
|
||||
example: '123456'
|
||||
newPassword:
|
||||
type: string
|
||||
example: s3cre7password
|
||||
example: '123456'
|
||||
required:
|
||||
- newPassword
|
||||
responses:
|
||||
|
||||
@@ -3,6 +3,14 @@ post:
|
||||
- users
|
||||
summary: generate a user token
|
||||
description: This route can only be used to generate tokens for the same user. In other words, you cannot use this route to generate a token for a different user than the one you are authenticated as.
|
||||
parameters:
|
||||
- in: path
|
||||
name: uid
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to generate a token for
|
||||
example: 1
|
||||
responses:
|
||||
'200':
|
||||
description: successfully generated a user token
|
||||
@@ -15,27 +23,3 @@ post:
|
||||
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
type: object
|
||||
delete:
|
||||
tags:
|
||||
- users
|
||||
summary: delete user token
|
||||
parameters:
|
||||
- in: path
|
||||
name: token
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: a valid API token
|
||||
example: 6d03a630-86fd-4515-9a35-e957502f4f89
|
||||
responses:
|
||||
'200':
|
||||
description: successfully deleted user token
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||
response:
|
||||
type: object
|
||||
145
test/api.js
145
test/api.js
@@ -10,6 +10,7 @@ const wait = util.promisify(setTimeout);
|
||||
|
||||
const db = require('./mocks/databasemock');
|
||||
const helpers = require('./helpers');
|
||||
const meta = require('../src/meta');
|
||||
const user = require('../src/user');
|
||||
const groups = require('../src/groups');
|
||||
const categories = require('../src/categories');
|
||||
@@ -17,7 +18,7 @@ const topics = require('../src/topics');
|
||||
const plugins = require('../src/plugins');
|
||||
const flags = require('../src/flags');
|
||||
const messaging = require('../src/messaging');
|
||||
|
||||
const utils = require('../src/utils');
|
||||
|
||||
describe('Read API', async () => {
|
||||
let readApi = false;
|
||||
@@ -25,9 +26,30 @@ describe('Read API', async () => {
|
||||
const readApiPath = path.resolve(__dirname, '../public/openapi/read.yaml');
|
||||
const writeApiPath = path.resolve(__dirname, '../public/openapi/write.yaml');
|
||||
let jar;
|
||||
let csrfToken;
|
||||
let setup = false;
|
||||
const unauthenticatedRoutes = ['/api/login', '/api/register']; // Everything else will be called with the admin user
|
||||
|
||||
const mocks = {
|
||||
get: {},
|
||||
post: {},
|
||||
put: {},
|
||||
delete: {
|
||||
'/users/{uid}/tokens/{token}': [
|
||||
{
|
||||
in: 'path',
|
||||
name: 'uid',
|
||||
example: 1,
|
||||
},
|
||||
{
|
||||
in: 'path',
|
||||
name: 'token',
|
||||
example: utils.generateUUID(),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
async function dummySearchHook(data) {
|
||||
return [1];
|
||||
}
|
||||
@@ -44,8 +66,26 @@ describe('Read API', async () => {
|
||||
// Create sample users
|
||||
const adminUid = await user.create({ username: 'admin', password: '123456', email: 'test@example.org' });
|
||||
const unprivUid = await user.create({ username: 'unpriv', password: '123456', email: 'unpriv@example.org' });
|
||||
for (let x = 0; x < 3; x++) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await user.create({ username: 'deleteme', password: '123456' }); // for testing of user deletion routes (uids 4-6)
|
||||
}
|
||||
await groups.join('administrators', adminUid);
|
||||
|
||||
// Create sample group
|
||||
await groups.create({
|
||||
name: 'Test Group',
|
||||
});
|
||||
|
||||
await meta.settings.set('core.api', {
|
||||
tokens: [{
|
||||
token: mocks.delete['/users/{uid}/tokens/{token}'][1].example,
|
||||
uid: 1,
|
||||
description: 'for testing of token deletion rotue',
|
||||
timestamp: Date.now(),
|
||||
}],
|
||||
});
|
||||
|
||||
// Create a category
|
||||
const testCategory = await categories.create({ name: 'test' });
|
||||
|
||||
@@ -78,6 +118,15 @@ describe('Read API', async () => {
|
||||
});
|
||||
|
||||
jar = await helpers.loginUser('admin', '123456');
|
||||
|
||||
// Retrieve CSRF token using cookie, to test Write API
|
||||
const config = await request({
|
||||
url: nconf.get('url') + '/api/config',
|
||||
json: true,
|
||||
jar: jar,
|
||||
});
|
||||
csrfToken = config.csrf_token;
|
||||
|
||||
setup = true;
|
||||
}
|
||||
|
||||
@@ -93,13 +142,13 @@ describe('Read API', async () => {
|
||||
readApi = await SwaggerParser.dereference(readApiPath);
|
||||
writeApi = await SwaggerParser.dereference(writeApiPath);
|
||||
|
||||
// Iterate through all documented paths, make a call to it, and compare the result body with what is defined in the spec
|
||||
// const paths = Object.keys(writeApi.paths);
|
||||
const paths = Object.keys(readApi.paths);
|
||||
// generateTests(readApi, Object.keys(readApi.paths));
|
||||
generateTests(writeApi, Object.keys(writeApi.paths), writeApi.servers[0].url);
|
||||
|
||||
function generateTests(api, paths, prefix) {
|
||||
// Iterate through all documented paths, make a call to it, and compare the result body with what is defined in the spec
|
||||
paths.forEach((path) => {
|
||||
// const context = writeApi.paths[path];
|
||||
const context = readApi.paths[path];
|
||||
const context = api.paths[path];
|
||||
let schema;
|
||||
let response;
|
||||
let url;
|
||||
@@ -108,16 +157,19 @@ describe('Read API', async () => {
|
||||
const qs = {};
|
||||
|
||||
Object.keys(context).forEach((_method) => {
|
||||
if (_method !== 'get') {
|
||||
return;
|
||||
}
|
||||
// if (_method !== 'get') {
|
||||
// return;
|
||||
// }
|
||||
|
||||
it('should have examples when parameters are present', () => {
|
||||
method = _method;
|
||||
const parameters = context[method].parameters;
|
||||
let parameters = context[method].parameters;
|
||||
let testPath = path;
|
||||
|
||||
if (parameters) {
|
||||
// Use mock data if provided
|
||||
parameters = mocks[method][path] || parameters;
|
||||
|
||||
parameters.forEach((param) => {
|
||||
assert(param.example !== null && param.example !== undefined, `${method.toUpperCase()} ${path} has parameters without examples`);
|
||||
|
||||
@@ -135,25 +187,45 @@ describe('Read API', async () => {
|
||||
});
|
||||
}
|
||||
|
||||
url = nconf.get('url') + testPath;
|
||||
url = nconf.get('url') + prefix + testPath;
|
||||
});
|
||||
|
||||
it('may contain a request body with application/json type if POST/PUT/DELETE', () => {
|
||||
if (['post', 'put', 'delete'].includes(method) && context[method].hasOwnProperty('requestBody')) {
|
||||
assert(context[method].requestBody);
|
||||
assert(context[method].requestBody.content);
|
||||
assert(context[method].requestBody.content['application/json']);
|
||||
assert(context[method].requestBody.content['application/json'].schema);
|
||||
assert(context[method].requestBody.content['application/json'].schema.properties);
|
||||
}
|
||||
});
|
||||
|
||||
it('should resolve with a 200 when called', async () => {
|
||||
await setupData();
|
||||
|
||||
if (csrfToken) {
|
||||
headers['x-csrf-token'] = csrfToken;
|
||||
}
|
||||
|
||||
let body = {};
|
||||
if (context[method].hasOwnProperty('requestBody')) {
|
||||
body = buildBody(context[method].requestBody.content['application/json'].schema.properties);
|
||||
}
|
||||
|
||||
try {
|
||||
// console.log(`calling ${method} ${url} with`, body);
|
||||
response = await request(url, {
|
||||
method: method,
|
||||
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
|
||||
json: true,
|
||||
headers: headers,
|
||||
qs: qs,
|
||||
body: body,
|
||||
});
|
||||
} catch (e) {
|
||||
assert(!e, `${method.toUpperCase()} ${path} resolved with ${e.message}`);
|
||||
}
|
||||
});
|
||||
console.log(response);
|
||||
|
||||
// Recursively iterate through schema properties, comparing type
|
||||
it('response should match schema definition', () => {
|
||||
@@ -165,15 +237,38 @@ describe('Read API', async () => {
|
||||
const hasJSON = has200.content && has200.content['application/json'];
|
||||
if (hasJSON) {
|
||||
schema = context[method].responses['200'].content['application/json'].schema;
|
||||
compare(schema, response, 'root');
|
||||
compare(schema, response, method.toUpperCase(), path, 'root');
|
||||
}
|
||||
|
||||
// TODO someday: text/csv, binary file type checking?
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function compare(schema, response, context) {
|
||||
it('should successfully re-login if needed', async () => {
|
||||
const reloginPaths = ['/users/{uid}/password'];
|
||||
if (method === 'put' && reloginPaths.includes(path)) {
|
||||
jar = await helpers.loginUser('admin', '123456');
|
||||
|
||||
// Retrieve CSRF token using cookie, to test Write API
|
||||
const config = await request({
|
||||
url: nconf.get('url') + '/api/config',
|
||||
json: true,
|
||||
jar: jar,
|
||||
});
|
||||
csrfToken = config.csrf_token;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buildBody(schema) {
|
||||
return Object.keys(schema).reduce((memo, cur) => {
|
||||
memo[cur] = schema[cur].example;
|
||||
return memo;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function compare(schema, response, method, path, context) {
|
||||
let required = [];
|
||||
const additionalProperties = schema.hasOwnProperty('additionalProperties');
|
||||
|
||||
@@ -194,7 +289,7 @@ describe('Read API', async () => {
|
||||
// Compare the schema to the response
|
||||
required.forEach((prop) => {
|
||||
if (schema.hasOwnProperty(prop)) {
|
||||
assert(response.hasOwnProperty(prop), '"' + prop + '" is a required property (path: ' + path + ', context: ' + context + ')');
|
||||
assert(response.hasOwnProperty(prop), '"' + prop + '" is a required property (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
|
||||
// Don't proceed with type-check if the value could possibly be unset (nullable: true, in spec)
|
||||
if (response[prop] === null && schema[prop].nullable === true) {
|
||||
@@ -202,30 +297,30 @@ describe('Read API', async () => {
|
||||
}
|
||||
|
||||
// Therefore, if the value is actually null, that's a problem (nullable is probably missing)
|
||||
assert(response[prop] !== null, '"' + prop + '" was null, but schema does not specify it to be a nullable property (path: ' + path + ', context: ' + context + ')');
|
||||
assert(response[prop] !== null, '"' + prop + '" was null, but schema does not specify it to be a nullable property (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
|
||||
switch (schema[prop].type) {
|
||||
case 'string':
|
||||
assert.strictEqual(typeof response[prop], 'string', '"' + prop + '" was expected to be a string, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')');
|
||||
assert.strictEqual(typeof response[prop], 'string', '"' + prop + '" was expected to be a string, but was ' + typeof response[prop] + ' instead (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
break;
|
||||
case 'boolean':
|
||||
assert.strictEqual(typeof response[prop], 'boolean', '"' + prop + '" was expected to be a boolean, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')');
|
||||
assert.strictEqual(typeof response[prop], 'boolean', '"' + prop + '" was expected to be a boolean, but was ' + typeof response[prop] + ' instead (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
break;
|
||||
case 'object':
|
||||
assert.strictEqual(typeof response[prop], 'object', '"' + prop + '" was expected to be an object, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')');
|
||||
compare(schema[prop], response[prop], context ? [context, prop].join('.') : prop);
|
||||
assert.strictEqual(typeof response[prop], 'object', '"' + prop + '" was expected to be an object, but was ' + typeof response[prop] + ' instead (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
compare(schema[prop], response[prop], method, path, context ? [context, prop].join('.') : prop);
|
||||
break;
|
||||
case 'array':
|
||||
assert.strictEqual(Array.isArray(response[prop]), true, '"' + prop + '" was expected to be an array, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')');
|
||||
assert.strictEqual(Array.isArray(response[prop]), true, '"' + prop + '" was expected to be an array, but was ' + typeof response[prop] + ' instead (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
|
||||
if (schema[prop].items) {
|
||||
// Ensure the array items have a schema defined
|
||||
assert(schema[prop].items.type || schema[prop].items.allOf, '"' + prop + '" is defined to be an array, but its items have no schema defined (path: ' + path + ', context: ' + context + ')');
|
||||
assert(schema[prop].items.type || schema[prop].items.allOf, '"' + prop + '" is defined to be an array, but its items have no schema defined (path: ' + method + ' ' + path + ', context: ' + context + ')');
|
||||
|
||||
// Compare types
|
||||
if (schema[prop].items.type === 'object' || Array.isArray(schema[prop].items.allOf)) {
|
||||
response[prop].forEach((res) => {
|
||||
compare(schema[prop].items, res, context ? [context, prop].join('.') : prop);
|
||||
compare(schema[prop].items, res, method, path, context ? [context, prop].join('.') : prop);
|
||||
});
|
||||
} else if (response[prop].length) { // for now
|
||||
response[prop].forEach((item) => {
|
||||
|
||||
Reference in New Issue
Block a user