mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-02-28 09:31:17 +01:00
feat: closes #9048, tests for topic thumbs routes, write API schema
This commit is contained in:
@@ -76,6 +76,8 @@ paths:
|
|||||||
$ref: 'write/topics/tid/ignore.yaml'
|
$ref: 'write/topics/tid/ignore.yaml'
|
||||||
/topics/{tid}/tags:
|
/topics/{tid}/tags:
|
||||||
$ref: 'write/topics/tid/tags.yaml'
|
$ref: 'write/topics/tid/tags.yaml'
|
||||||
|
/topics/{tid}/thumbs:
|
||||||
|
$ref: 'write/topics/tid/thumbs.yaml'
|
||||||
/posts/{pid}:
|
/posts/{pid}:
|
||||||
$ref: 'write/posts/pid.yaml'
|
$ref: 'write/posts/pid.yaml'
|
||||||
/posts/{pid}/state:
|
/posts/{pid}/state:
|
||||||
|
|||||||
146
public/openapi/write/topics/tid/thumbs.yaml
Normal file
146
public/openapi/write/topics/tid/thumbs.yaml
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: get topic thumbnails
|
||||||
|
description: This operation retrieves a topic's uploaded thumbnails
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: tid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: a valid topic id
|
||||||
|
example: 1
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Thumbnails successfully retrieved
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: add topic thumbnail
|
||||||
|
description: This operation adds a thumbnail to an existing topic or a draft (via a composer `uuid`)
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: tid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: a valid topic id
|
||||||
|
example: 1
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Thumbnail successfully added
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: migrate topic thumbnail
|
||||||
|
description: This operation migrates a thumbnails from a topic or draft, to another tid or draft.
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: tid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: a valid topic id or draft uuid
|
||||||
|
example: 1
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
tid:
|
||||||
|
type: string
|
||||||
|
description: a valid topic id or draft uuid
|
||||||
|
example: '1'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic thumbnails migrated
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: remove topic thumbnail
|
||||||
|
description: This operation removes a topic thumbnail.
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: tid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: a valid topic id
|
||||||
|
example: 1
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
description: Relative path to the topic thumbnail
|
||||||
|
example: files/test.png
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic thumbnail removed
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: array
|
||||||
|
description: A list of the topic thumbnails that still remain
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
description: Path to a topic thumbnail
|
||||||
@@ -16,10 +16,10 @@ const uploadsController = module.exports;
|
|||||||
uploadsController.upload = async function (req, res, filesIterator) {
|
uploadsController.upload = async function (req, res, filesIterator) {
|
||||||
let files = req.files.files;
|
let files = req.files.files;
|
||||||
|
|
||||||
|
// These checks added because of odd behaviour by request: https://github.com/request/request/issues/2445
|
||||||
if (!Array.isArray(files)) {
|
if (!Array.isArray(files)) {
|
||||||
return res.status(500).json('invalid files');
|
return res.status(500).json('invalid files');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(files[0])) {
|
if (Array.isArray(files[0])) {
|
||||||
files = files[0];
|
files = files[0];
|
||||||
}
|
}
|
||||||
|
|||||||
54
test/api.js
54
test/api.js
@@ -99,6 +99,7 @@ describe('API', async () => {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
meta.config.allowTopicsThumbnail = 1;
|
||||||
|
|
||||||
// Create a category
|
// Create a category
|
||||||
const testCategory = await categories.create({ name: 'test' });
|
const testCategory = await categories.create({ name: 'test' });
|
||||||
@@ -123,8 +124,9 @@ describe('API', async () => {
|
|||||||
// Create a new chat room
|
// Create a new chat room
|
||||||
await messaging.newRoom(1, [2]);
|
await messaging.newRoom(1, [2]);
|
||||||
|
|
||||||
// Create an empty file to test DELETE /files
|
// Create an empty file to test DELETE /files and thumb deletion
|
||||||
fs.closeSync(fs.openSync(path.resolve(nconf.get('upload_path'), 'files/test.txt'), 'w'));
|
fs.closeSync(fs.openSync(path.resolve(nconf.get('upload_path'), 'files/test.txt'), 'w'));
|
||||||
|
fs.closeSync(fs.openSync(path.resolve(nconf.get('upload_path'), 'files/test.png'), 'w'));
|
||||||
|
|
||||||
const socketUser = require('../src/socket.io/user');
|
const socketUser = require('../src/socket.io/user');
|
||||||
const socketAdmin = require('../src/socket.io/admin');
|
const socketAdmin = require('../src/socket.io/admin');
|
||||||
@@ -172,6 +174,7 @@ describe('API', async () => {
|
|||||||
|
|
||||||
function generateTests(api, paths, prefix) {
|
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
|
// Iterate through all documented paths, make a call to it, and compare the result body with what is defined in the spec
|
||||||
|
const pathLib = path; // for calling path module from inside this forEach
|
||||||
paths.forEach((path) => {
|
paths.forEach((path) => {
|
||||||
const context = api.paths[path];
|
const context = api.paths[path];
|
||||||
let schema;
|
let schema;
|
||||||
@@ -224,13 +227,20 @@ describe('API', async () => {
|
|||||||
url = nconf.get('url') + (prefix || '') + testPath;
|
url = nconf.get('url') + (prefix || '') + testPath;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a valid request body (if present) with application/json type if POST/PUT/DELETE', () => {
|
it('should contain a valid request body (if present) with application/json or multipart/form-data type if POST/PUT/DELETE', () => {
|
||||||
if (['post', 'put', 'delete'].includes(method) && context[method].hasOwnProperty('requestBody')) {
|
if (['post', 'put', 'delete'].includes(method) && context[method].hasOwnProperty('requestBody')) {
|
||||||
assert(context[method].requestBody);
|
assert(context[method].requestBody);
|
||||||
assert(context[method].requestBody.content);
|
assert(context[method].requestBody.content);
|
||||||
assert(context[method].requestBody.content['application/json']);
|
|
||||||
assert(context[method].requestBody.content['application/json'].schema);
|
if (context[method].requestBody.content.hasOwnProperty('application/json')) {
|
||||||
assert(context[method].requestBody.content['application/json'].schema.properties);
|
assert(context[method].requestBody.content['application/json']);
|
||||||
|
assert(context[method].requestBody.content['application/json'].schema);
|
||||||
|
assert(context[method].requestBody.content['application/json'].schema.properties);
|
||||||
|
} else if (context[method].requestBody.content.hasOwnProperty('multipart/form-data')) {
|
||||||
|
assert(context[method].requestBody.content['multipart/form-data']);
|
||||||
|
assert(context[method].requestBody.content['multipart/form-data'].schema);
|
||||||
|
assert(context[method].requestBody.content['multipart/form-data'].schema.properties);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,20 +252,34 @@ describe('API', async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let body = {};
|
let body = {};
|
||||||
if (context[method].hasOwnProperty('requestBody')) {
|
let type = 'json';
|
||||||
|
if (context[method].hasOwnProperty('requestBody') && context[method].requestBody.content['application/json']) {
|
||||||
body = buildBody(context[method].requestBody.content['application/json'].schema.properties);
|
body = buildBody(context[method].requestBody.content['application/json'].schema.properties);
|
||||||
|
} else if (context[method].hasOwnProperty('requestBody') && context[method].requestBody.content['multipart/form-data']) {
|
||||||
|
type = 'form';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// console.log(`calling ${method} ${url} with`, body);
|
if (type === 'json') {
|
||||||
response = await request(url, {
|
// console.log(`calling ${method} ${url} with`, body);
|
||||||
method: method,
|
response = await request(url, {
|
||||||
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
|
method: method,
|
||||||
json: true,
|
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
|
||||||
headers: headers,
|
json: true,
|
||||||
qs: qs,
|
headers: headers,
|
||||||
body: body,
|
qs: qs,
|
||||||
});
|
body: body,
|
||||||
|
});
|
||||||
|
} else if (type === 'form') {
|
||||||
|
response = await new Promise((resolve, reject) => {
|
||||||
|
helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken, function (err, res, body) {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert(!e, `${method.toUpperCase()} ${path} resolved with ${e.message}`);
|
assert(!e, `${method.toUpperCase()} ${path} resolved with ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user