mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +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` |   description: One of the objects in the array returned from `Posts.getPostSummaryByPids` | ||||||
|   type: array |   type: array | ||||||
|   items: |   items: | ||||||
|     type: object |     $ref: ./PostObject.yaml#/PostObject | ||||||
|     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 |  | ||||||
| @@ -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 |   - name: categories | ||||||
|     description: Administrative calls to manage categories |     description: Administrative calls to manage categories | ||||||
| paths: | paths: | ||||||
|  |   /users/: | ||||||
|  |     $ref: 'write/users.yaml' | ||||||
|   /users/{uid}: |   /users/{uid}: | ||||||
|     $ref: 'write/users/uid.yaml' |     $ref: 'write/users/uid.yaml' | ||||||
|   /users/{uid}/settings: |   /users/{uid}/settings: | ||||||
| @@ -40,12 +42,14 @@ paths: | |||||||
|     $ref: 'write/users/uid/follow.yaml' |     $ref: 'write/users/uid/follow.yaml' | ||||||
|   /users/{uid}/ban: |   /users/{uid}/ban: | ||||||
|     $ref: 'write/users/uid/ban.yaml' |     $ref: 'write/users/uid/ban.yaml' | ||||||
|   /users/{uid}/tokens: |   /users/{uid}/tokens/{token}: | ||||||
|     $ref: 'write/users/uid/tokens.yaml' |     $ref: 'write/users/uid/tokens/token.yaml' | ||||||
|   /categories/: |   /categories/: | ||||||
|     $ref: 'write/categories.yaml' |     $ref: 'write/categories.yaml' | ||||||
|   /groups/: |   /groups/: | ||||||
|     $ref: 'write/groups.yaml' |     $ref: 'write/groups.yaml' | ||||||
|  |   /groups/{slug}: | ||||||
|  |     $ref: 'write/groups/slug.yaml' | ||||||
|   /groups/{slug}/membership/{uid}: |   /groups/{slug}/membership/{uid}: | ||||||
|     $ref: 'write/groups/slug/membership/uid.yaml' |     $ref: 'write/groups/slug/membership/uid.yaml' | ||||||
|   /topics: |   /topics: | ||||||
|   | |||||||
| @@ -12,38 +12,37 @@ post: | |||||||
|           properties: |           properties: | ||||||
|             name: |             name: | ||||||
|               type: string |               type: string | ||||||
|  |               example: My New Category | ||||||
|             description: |             description: | ||||||
|               type: string |               type: string | ||||||
|  |               example: Lorem ipsum, dolor sit amet | ||||||
|             parentCid: |             parentCid: | ||||||
|               type: number |               type: number | ||||||
|  |               example: 0 | ||||||
|             cloneFromCid: |             cloneFromCid: | ||||||
|               type: number |               type: number | ||||||
|  |               example: 0 | ||||||
|             icon: |             icon: | ||||||
|               type: string |               type: string | ||||||
|  |               example: bullhorn | ||||||
|               description: A ForkAwesome icon without the `fa-` prefix |               description: A ForkAwesome icon without the `fa-` prefix | ||||||
|             bgColor: |             bgColor: | ||||||
|               type: string |               type: string | ||||||
|  |               example: '#ffffff' | ||||||
|             color: |             color: | ||||||
|               type: string |               type: string | ||||||
|  |               example: '#000000' | ||||||
|             link: |             link: | ||||||
|               type: string |               type: string | ||||||
|  |               example: 'https://example.org' | ||||||
|             class: |             class: | ||||||
|               type: string |               type: string | ||||||
|  |               example: 'col-md-3 col-xs-6' | ||||||
|             backgroundImage: |             backgroundImage: | ||||||
|               type: string |               type: string | ||||||
|  |               example: '/assets/relative/path/to/image' | ||||||
|           required: |           required: | ||||||
|             - name |             - 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: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: category successfully created |       description: category successfully created | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ post: | |||||||
|           properties: |           properties: | ||||||
|             name: |             name: | ||||||
|               type: string |               type: string | ||||||
|  |               example: 'My Test Group' | ||||||
|             timestamp: |             timestamp: | ||||||
|               type: number |               type: number | ||||||
|             disableJoinRequests: |             disableJoinRequests: | ||||||
| @@ -23,6 +24,7 @@ post: | |||||||
|             hidden: |             hidden: | ||||||
|               type: number |               type: number | ||||||
|               enum: [0, 1] |               enum: [0, 1] | ||||||
|  |               example: 1 | ||||||
|             ownerUid: |             ownerUid: | ||||||
|               type: number |               type: number | ||||||
|             private: |             private: | ||||||
| @@ -37,9 +39,6 @@ post: | |||||||
|               type: number |               type: number | ||||||
|           required: |           required: | ||||||
|             - name |             - name | ||||||
|         example: |  | ||||||
|           name: 'My Test Group' |  | ||||||
|           hidden: 1 |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: group successfully created |       description: group successfully created | ||||||
| @@ -52,21 +51,3 @@ post: | |||||||
|                 $ref: ../components/schemas/Status.yaml#/Status |                 $ref: ../components/schemas/Status.yaml#/Status | ||||||
|               response: |               response: | ||||||
|                 $ref: ../components/schemas/GroupObject.yaml#/GroupDataObject |                 $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 |         type: string | ||||||
|       required: true |       required: true | ||||||
|       description: slug of the group you would like to join |       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: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: group successfully joined, or membership requested |       description: group successfully joined, or membership requested | ||||||
| @@ -24,3 +31,36 @@ put: | |||||||
|               response: |               response: | ||||||
|                 type: object |                 type: object | ||||||
|                 properties: {} |                 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: |             content: | ||||||
|               type: string |               type: string | ||||||
|               description: New post content |               description: New post content | ||||||
|  |               example: New post content | ||||||
|             title: |             title: | ||||||
|               type: string |               type: string | ||||||
|               description: Topic title, only accepted for main posts |               description: Topic title, only accepted for main posts | ||||||
|  |               example: New title | ||||||
|           required: |           required: | ||||||
|             - content |             - content | ||||||
|         example: |  | ||||||
|           content: 'New post content' |  | ||||||
|           title: 'New title' |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: Post successfully edited |       description: Post successfully edited | ||||||
| @@ -40,7 +39,7 @@ put: | |||||||
|               status: |               status: | ||||||
|                 $ref: ../../components/schemas/Status.yaml#/Status |                 $ref: ../../components/schemas/Status.yaml#/Status | ||||||
|               response: |               response: | ||||||
|                 $ref: ../../components/schemas/PostsObject.yaml#/PostsObject |                 $ref: ../../components/schemas/PostObject.yaml#/PostObject | ||||||
| delete: | delete: | ||||||
|   tags: |   tags: | ||||||
|     - posts |     - posts | ||||||
|   | |||||||
| @@ -12,22 +12,22 @@ post: | |||||||
|           properties: |           properties: | ||||||
|             cid: |             cid: | ||||||
|               type: number |               type: number | ||||||
|  |               example: 1 | ||||||
|             title: |             title: | ||||||
|               type: string |               type: string | ||||||
|  |               example: Test topic | ||||||
|             content: |             content: | ||||||
|               type: string |               type: string | ||||||
|  |               example: This is the test topic's content | ||||||
|             tags: |             tags: | ||||||
|               type: array |               type: array | ||||||
|               items: |               items: | ||||||
|                 type: string |                 type: string | ||||||
|  |               example: [test, topic] | ||||||
|           required: |           required: | ||||||
|             - cid |             - cid | ||||||
|             - title |             - title | ||||||
|             - content |             - content | ||||||
|         example: |  | ||||||
|           cid: 1 |  | ||||||
|           title: Test topic |  | ||||||
|           content: This is the test topic's content |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: topic successfully created |       description: topic successfully created | ||||||
| @@ -39,4 +39,8 @@ post: | |||||||
|               status: |               status: | ||||||
|                 $ref: ../components/schemas/Status.yaml#/Status |                 $ref: ../components/schemas/Status.yaml#/Status | ||||||
|               response: |               response: | ||||||
|                 $ref: ../components/schemas/TopicObject.yaml#/TopicObject |                 allOf: | ||||||
|  |                   - $ref: ../components/schemas/TopicObject.yaml#/TopicObject | ||||||
|  |                   - type: object | ||||||
|  |                     properties: | ||||||
|  |                       mainPost: {} | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| post: | post: | ||||||
|   tags: |   tags: | ||||||
|     - topics |     - topics | ||||||
|   summary: peply to a topic |   summary: reply to a topic | ||||||
|   description: This operation creates a new reply to an existing topic. |   description: This operation creates a new reply to an existing topic. | ||||||
|   parameters: |   parameters: | ||||||
|     - in: path |     - in: path | ||||||
| @@ -10,7 +10,7 @@ post: | |||||||
|         type: string |         type: string | ||||||
|       required: true |       required: true | ||||||
|       description: a valid topic id |       description: a valid topic id | ||||||
|       example: 1 |       example: 2 | ||||||
|   requestBody: |   requestBody: | ||||||
|     required: true |     required: true | ||||||
|     content: |     content: | ||||||
| @@ -20,14 +20,13 @@ post: | |||||||
|           properties: |           properties: | ||||||
|             content: |             content: | ||||||
|               type: string |               type: string | ||||||
|  |               example: This is a test reply | ||||||
|             timestamp: |             timestamp: | ||||||
|               type: number |               type: number | ||||||
|             toPid: |             toPid: | ||||||
|               type: number |               type: number | ||||||
|           required: |           required: | ||||||
|             - content |             - content | ||||||
|         example: |  | ||||||
|           content: This is a test reply |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: post successfully created |       description: post successfully created | ||||||
| @@ -39,7 +38,7 @@ post: | |||||||
|               status: |               status: | ||||||
|                 $ref: ../../components/schemas/Status.yaml#/Status |                 $ref: ../../components/schemas/Status.yaml#/Status | ||||||
|               response: |               response: | ||||||
|                 $ref: ../../components/schemas/PostsObject.yaml#/PostsObject |                 $ref: ../../components/schemas/PostObject.yaml#/PostObject | ||||||
| delete: | delete: | ||||||
|   tags: |   tags: | ||||||
|     - topics |     - topics | ||||||
| @@ -52,7 +51,7 @@ delete: | |||||||
|         type: string |         type: string | ||||||
|       required: true |       required: true | ||||||
|       description: a valid topic id |       description: a valid topic id | ||||||
|       example: 1 |       example: 2 | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: Topic successfully purged |       description: Topic successfully purged | ||||||
|   | |||||||
| @@ -23,10 +23,7 @@ put: | |||||||
|               description: 'An array of tags' |               description: 'An array of tags' | ||||||
|               items: |               items: | ||||||
|                 type: string |                 type: string | ||||||
|         example: |               example: [test, foobar] | ||||||
|           tags: |  | ||||||
|             - test |  | ||||||
|             - foobar |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: Topic tags successfully added |       description: Topic tags successfully added | ||||||
|   | |||||||
| @@ -13,16 +13,15 @@ post: | |||||||
|             username: |             username: | ||||||
|               type: string |               type: string | ||||||
|               description: 'If the username is taken, a number will be appended' |               description: 'If the username is taken, a number will be appended' | ||||||
|  |               example: Dragon Fruit | ||||||
|             password: |             password: | ||||||
|               type: string |               type: string | ||||||
|  |               example: s3cre7password | ||||||
|             email: |             email: | ||||||
|               type: string |               type: string | ||||||
|  |               example: dragonfruit@example.org | ||||||
|           required: |           required: | ||||||
|             - username |             - username | ||||||
|         example: |  | ||||||
|           username: Dragon Fruit |  | ||||||
|           password: s3cre7password |  | ||||||
|           email: dragonfruit@example.org |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: user successfully created |       description: user successfully created | ||||||
| @@ -62,11 +61,7 @@ delete: | |||||||
|               description: A collection of uids |               description: A collection of uids | ||||||
|               items: |               items: | ||||||
|                 type: number |                 type: number | ||||||
|         example: |               example: [5, 6] | ||||||
|           uids: |  | ||||||
|             - 1 |  | ||||||
|             - 2 |  | ||||||
|             - 3 |  | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: user account(s) deleted |       description: user account(s) deleted | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ delete: | |||||||
|         type: integer |         type: integer | ||||||
|       required: true |       required: true | ||||||
|       description: uid of the user to delete |       description: uid of the user to delete | ||||||
|       example: 1 |       example: 3 | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: user account deleted |       description: user account deleted | ||||||
| @@ -39,7 +39,35 @@ put: | |||||||
|     content: |     content: | ||||||
|       application/json: |       application/json: | ||||||
|         schema: |         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: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: user profile updated |       description: user profile updated | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ put: | |||||||
|         type: integer |         type: integer | ||||||
|       required: true |       required: true | ||||||
|       description: uid of the user to ban |       description: uid of the user to ban | ||||||
|       example: 1 |       example: 2 | ||||||
|   requestBody: |   requestBody: | ||||||
|     content: |     content: | ||||||
|       application/json: |       application/json: | ||||||
| @@ -46,7 +46,7 @@ delete: | |||||||
|         type: integer |         type: integer | ||||||
|       required: true |       required: true | ||||||
|       description: uid of the user to unban |       description: uid of the user to unban | ||||||
|       example: 1 |       example: 2 | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: successfully unbanned user |       description: successfully unbanned user | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| post: | put: | ||||||
|   tags: |   tags: | ||||||
|     - users |     - users | ||||||
|   summary: follow a user |   summary: follow a user | ||||||
| @@ -9,7 +9,7 @@ post: | |||||||
|         type: integer |         type: integer | ||||||
|       required: true |       required: true | ||||||
|       description: uid of the user to follow |       description: uid of the user to follow | ||||||
|       example: 1 |       example: 2 | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: successfully followed user |       description: successfully followed user | ||||||
| @@ -33,7 +33,7 @@ delete: | |||||||
|         type: integer |         type: integer | ||||||
|       required: true |       required: true | ||||||
|       description: uid of the user to unfollow |       description: uid of the user to unfollow | ||||||
|       example: 1 |       example: 2 | ||||||
|   responses: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: successfully unfollowed user |       description: successfully unfollowed user | ||||||
|   | |||||||
| @@ -20,10 +20,10 @@ put: | |||||||
|             currentPassword: |             currentPassword: | ||||||
|               type: string |               type: string | ||||||
|               description: test |               description: test | ||||||
|               example: oldp455word |               example: '123456' | ||||||
|             newPassword: |             newPassword: | ||||||
|               type: string |               type: string | ||||||
|               example: s3cre7password |               example: '123456' | ||||||
|           required: |           required: | ||||||
|             - newPassword |             - newPassword | ||||||
|   responses: |   responses: | ||||||
|   | |||||||
| @@ -3,6 +3,14 @@ post: | |||||||
|     - users |     - users | ||||||
|   summary: generate a user token |   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. |   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: |   responses: | ||||||
|     '200': |     '200': | ||||||
|       description: successfully generated a user token |       description: successfully generated a user token | ||||||
| @@ -15,27 +23,3 @@ post: | |||||||
|                 $ref: ../../../components/schemas/Status.yaml#/Status |                 $ref: ../../../components/schemas/Status.yaml#/Status | ||||||
|               response: |               response: | ||||||
|                 type: object |                 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 db = require('./mocks/databasemock'); | ||||||
| const helpers = require('./helpers'); | const helpers = require('./helpers'); | ||||||
|  | const meta = require('../src/meta'); | ||||||
| const user = require('../src/user'); | const user = require('../src/user'); | ||||||
| const groups = require('../src/groups'); | const groups = require('../src/groups'); | ||||||
| const categories = require('../src/categories'); | const categories = require('../src/categories'); | ||||||
| @@ -17,7 +18,7 @@ const topics = require('../src/topics'); | |||||||
| const plugins = require('../src/plugins'); | const plugins = require('../src/plugins'); | ||||||
| const flags = require('../src/flags'); | const flags = require('../src/flags'); | ||||||
| const messaging = require('../src/messaging'); | const messaging = require('../src/messaging'); | ||||||
|  | const utils = require('../src/utils'); | ||||||
|  |  | ||||||
| describe('Read API', async () => { | describe('Read API', async () => { | ||||||
| 	let readApi = false; | 	let readApi = false; | ||||||
| @@ -25,9 +26,30 @@ describe('Read API', async () => { | |||||||
| 	const readApiPath = path.resolve(__dirname, '../public/openapi/read.yaml'); | 	const readApiPath = path.resolve(__dirname, '../public/openapi/read.yaml'); | ||||||
| 	const writeApiPath = path.resolve(__dirname, '../public/openapi/write.yaml'); | 	const writeApiPath = path.resolve(__dirname, '../public/openapi/write.yaml'); | ||||||
| 	let jar; | 	let jar; | ||||||
|  | 	let csrfToken; | ||||||
| 	let setup = false; | 	let setup = false; | ||||||
| 	const unauthenticatedRoutes = ['/api/login', '/api/register'];	// Everything else will be called with the admin user | 	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) { | 	async function dummySearchHook(data) { | ||||||
| 		return [1]; | 		return [1]; | ||||||
| 	} | 	} | ||||||
| @@ -44,8 +66,26 @@ describe('Read API', async () => { | |||||||
| 		// Create sample users | 		// Create sample users | ||||||
| 		const adminUid = await user.create({ username: 'admin', password: '123456', email: 'test@example.org' }); | 		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' }); | 		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); | 		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 | 		// Create a category | ||||||
| 		const testCategory = await categories.create({ name: 'test' }); | 		const testCategory = await categories.create({ name: 'test' }); | ||||||
|  |  | ||||||
| @@ -78,6 +118,15 @@ describe('Read API', async () => { | |||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		jar = await helpers.loginUser('admin', '123456'); | 		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; | 		setup = true; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -93,13 +142,13 @@ describe('Read API', async () => { | |||||||
| 	readApi = await SwaggerParser.dereference(readApiPath); | 	readApi = await SwaggerParser.dereference(readApiPath); | ||||||
| 	writeApi = await SwaggerParser.dereference(writeApiPath); | 	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 | 	// generateTests(readApi, Object.keys(readApi.paths)); | ||||||
| 	// const paths = Object.keys(writeApi.paths); | 	generateTests(writeApi, Object.keys(writeApi.paths), writeApi.servers[0].url); | ||||||
| 	const paths = Object.keys(readApi.paths); |  | ||||||
|  |  | ||||||
|  | 	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) => { | 		paths.forEach((path) => { | ||||||
| 		// const context = writeApi.paths[path]; | 			const context = api.paths[path]; | ||||||
| 		const context = readApi.paths[path]; |  | ||||||
| 			let schema; | 			let schema; | ||||||
| 			let response; | 			let response; | ||||||
| 			let url; | 			let url; | ||||||
| @@ -108,16 +157,19 @@ describe('Read API', async () => { | |||||||
| 			const qs = {}; | 			const qs = {}; | ||||||
|  |  | ||||||
| 			Object.keys(context).forEach((_method) => { | 			Object.keys(context).forEach((_method) => { | ||||||
| 			if (_method !== 'get') { | 				// if (_method !== 'get') { | ||||||
| 				return; | 				// 	return; | ||||||
| 			} | 				// } | ||||||
|  |  | ||||||
| 				it('should have examples when parameters are present', () => { | 				it('should have examples when parameters are present', () => { | ||||||
| 					method = _method; | 					method = _method; | ||||||
| 				const parameters = context[method].parameters; | 					let parameters = context[method].parameters; | ||||||
| 					let testPath = path; | 					let testPath = path; | ||||||
|  |  | ||||||
| 					if (parameters) { | 					if (parameters) { | ||||||
|  | 						// Use mock data if provided | ||||||
|  | 						parameters = mocks[method][path] || parameters; | ||||||
|  |  | ||||||
| 						parameters.forEach((param) => { | 						parameters.forEach((param) => { | ||||||
| 							assert(param.example !== null && param.example !== undefined, `${method.toUpperCase()} ${path} has parameters without examples`); | 							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 () => { | 				it('should resolve with a 200 when called', async () => { | ||||||
| 					await setupData(); | 					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 { | 					try { | ||||||
|  | 						// console.log(`calling ${method} ${url} with`, body); | ||||||
| 						response = await request(url, { | 						response = await request(url, { | ||||||
| 							method: method, | 							method: method, | ||||||
| 							jar: !unauthenticatedRoutes.includes(path) ? jar : undefined, | 							jar: !unauthenticatedRoutes.includes(path) ? jar : undefined, | ||||||
| 							json: true, | 							json: true, | ||||||
| 							headers: headers, | 							headers: headers, | ||||||
| 							qs: qs, | 							qs: qs, | ||||||
|  | 							body: body, | ||||||
| 						}); | 						}); | ||||||
| 					} catch (e) { | 					} catch (e) { | ||||||
| 						assert(!e, `${method.toUpperCase()} ${path} resolved with ${e.message}`); | 						assert(!e, `${method.toUpperCase()} ${path} resolved with ${e.message}`); | ||||||
| 					} | 					} | ||||||
| 				}); | 				}); | ||||||
| 			console.log(response); |  | ||||||
|  |  | ||||||
| 				// Recursively iterate through schema properties, comparing type | 				// Recursively iterate through schema properties, comparing type | ||||||
| 				it('response should match schema definition', () => { | 				it('response should match schema definition', () => { | ||||||
| @@ -165,15 +237,38 @@ describe('Read API', async () => { | |||||||
| 					const hasJSON = has200.content && has200.content['application/json']; | 					const hasJSON = has200.content && has200.content['application/json']; | ||||||
| 					if (hasJSON) { | 					if (hasJSON) { | ||||||
| 						schema = context[method].responses['200'].content['application/json'].schema; | 						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? | 					// 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 = []; | 		let required = []; | ||||||
| 		const additionalProperties = schema.hasOwnProperty('additionalProperties'); | 		const additionalProperties = schema.hasOwnProperty('additionalProperties'); | ||||||
|  |  | ||||||
| @@ -194,7 +289,7 @@ describe('Read API', async () => { | |||||||
| 		// Compare the schema to the response | 		// Compare the schema to the response | ||||||
| 		required.forEach((prop) => { | 		required.forEach((prop) => { | ||||||
| 			if (schema.hasOwnProperty(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) | 				// 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) { | 				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) | 				// 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) { | 				switch (schema[prop].type) { | ||||||
| 					case 'string': | 					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; | 						break; | ||||||
| 					case 'boolean': | 					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; | 						break; | ||||||
| 					case 'object': | 					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 + ')'); | 						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], context ? [context, prop].join('.') : prop); | 						compare(schema[prop], response[prop], method, path, context ? [context, prop].join('.') : prop); | ||||||
| 						break; | 						break; | ||||||
| 					case 'array': | 					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) { | 						if (schema[prop].items) { | ||||||
| 						// Ensure the array items have a schema defined | 						// 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 | 							// Compare types | ||||||
| 							if (schema[prop].items.type === 'object' || Array.isArray(schema[prop].items.allOf)) { | 							if (schema[prop].items.type === 'object' || Array.isArray(schema[prop].items.allOf)) { | ||||||
| 								response[prop].forEach((res) => { | 								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 | 							} else if (response[prop].length) { // for now | ||||||
| 								response[prop].forEach((item) => { | 								response[prop].forEach((item) => { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user