mirror of
https://github.com/strapi/strapi.git
synced 2025-12-25 06:04:29 +00:00
implement PUT endpoint to update a token
This commit is contained in:
parent
ca668c78e8
commit
f7bd99cb74
@ -198,4 +198,89 @@ describe('API Token Controller', () => {
|
||||
expect(notFound).toHaveBeenCalledWith('API Token not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update API Token', () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'read-only',
|
||||
};
|
||||
|
||||
const id = 1;
|
||||
|
||||
test('Fails if the name is already taken', async () => {
|
||||
const getById = jest.fn(() => ({ id, ...body }));
|
||||
const exists = jest.fn(() => true);
|
||||
const badRequest = jest.fn();
|
||||
const ctx = createContext({ body, params: { id } }, { badRequest });
|
||||
|
||||
global.strapi = {
|
||||
admin: {
|
||||
services: {
|
||||
'api-token': {
|
||||
exists,
|
||||
getById,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await apiTokenController.update(ctx);
|
||||
|
||||
expect(exists).toHaveBeenCalledWith({ name: body.name });
|
||||
expect(badRequest).toHaveBeenCalledWith('Name already taken');
|
||||
});
|
||||
|
||||
test('Fails if the token does not exist', async () => {
|
||||
const getById = jest.fn(() => null);
|
||||
const notFound = jest.fn();
|
||||
const ctx = createContext({ body, params: { id } }, { notFound });
|
||||
|
||||
global.strapi = {
|
||||
admin: {
|
||||
services: {
|
||||
'api-token': {
|
||||
getById,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await apiTokenController.update(ctx);
|
||||
|
||||
expect(getById).toHaveBeenCalledWith(id);
|
||||
expect(notFound).toHaveBeenCalledWith('API token not found');
|
||||
});
|
||||
|
||||
test('Updates API Token Successfully', async () => {
|
||||
const update = jest.fn().mockResolvedValue(body);
|
||||
const getById = jest.fn(() => ({ id, ...body }));
|
||||
const exists = jest.fn(() => false);
|
||||
const badRequest = jest.fn();
|
||||
const notFound = jest.fn();
|
||||
const send = jest.fn();
|
||||
const ctx = createContext({ body, params: { id } }, { badRequest, notFound, send });
|
||||
|
||||
global.strapi = {
|
||||
admin: {
|
||||
services: {
|
||||
'api-token': {
|
||||
getById,
|
||||
exists,
|
||||
update,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await apiTokenController.update(ctx);
|
||||
|
||||
expect(getById).toHaveBeenCalledWith(id);
|
||||
expect(exists).toHaveBeenCalledWith({ name: body.name });
|
||||
expect(badRequest).not.toHaveBeenCalled();
|
||||
expect(notFound).not.toHaveBeenCalled();
|
||||
expect(update).toHaveBeenCalledWith(id, body);
|
||||
expect(send).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { trim } = require('lodash/fp');
|
||||
const has = require('lodash/has');
|
||||
const { getService } = require('../utils');
|
||||
const { validateApiTokenCreationInput } = require('../validation/api-tokens');
|
||||
const {
|
||||
validateApiTokenCreationInput,
|
||||
validateApiTokenUpdateInput,
|
||||
} = require('../validation/api-tokens');
|
||||
|
||||
module.exports = {
|
||||
async create(ctx) {
|
||||
@ -63,4 +67,42 @@ module.exports = {
|
||||
|
||||
ctx.send({ data: apiToken });
|
||||
},
|
||||
|
||||
async update(ctx) {
|
||||
const { body } = ctx.request;
|
||||
const { id } = ctx.params;
|
||||
const apiTokenService = getService('api-token');
|
||||
|
||||
/**
|
||||
* We trim both field to avoid having issues with either:
|
||||
* - having a space at the end or start of the value.
|
||||
* - having only spaces as value;
|
||||
*/
|
||||
const attributes = {
|
||||
name: trim(body.name),
|
||||
description: trim(body.description),
|
||||
type: body.type,
|
||||
};
|
||||
|
||||
try {
|
||||
await validateApiTokenUpdateInput(attributes);
|
||||
} catch (err) {
|
||||
return ctx.badRequest('ValidationError', err);
|
||||
}
|
||||
|
||||
const apiTokenExists = await apiTokenService.getById(id);
|
||||
if (!apiTokenExists) {
|
||||
return ctx.notFound('API token not found');
|
||||
}
|
||||
|
||||
if (has(attributes, 'name')) {
|
||||
const nameAlreadyTaken = await apiTokenService.exists({ name: attributes.name });
|
||||
if (nameAlreadyTaken) {
|
||||
return ctx.badRequest('Name already taken');
|
||||
}
|
||||
}
|
||||
|
||||
const apiToken = await apiTokenService.update(id, attributes);
|
||||
ctx.send({ data: apiToken });
|
||||
},
|
||||
};
|
||||
|
||||
@ -45,4 +45,15 @@ module.exports = [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
path: '/api-tokens/:id',
|
||||
handler: 'api-token.update',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{ name: 'admin::hasPermissions', options: { actions: ['admin::api-tokens.update'] } },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -243,4 +243,35 @@ describe('API Token', () => {
|
||||
expect(res).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
test('Updates a token', async () => {
|
||||
const update = jest.fn(({ data }) => Promise.resolve(data));
|
||||
|
||||
global.strapi = {
|
||||
query() {
|
||||
return { update };
|
||||
},
|
||||
config: {
|
||||
get: jest.fn(() => ''),
|
||||
},
|
||||
};
|
||||
|
||||
const id = 1;
|
||||
const attributes = {
|
||||
name: 'api-token_tests-updated-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'read-only',
|
||||
};
|
||||
|
||||
const res = await apiTokenService.update(id, attributes);
|
||||
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
select: ['id', 'name', 'description', 'type'],
|
||||
where: { id },
|
||||
data: attributes,
|
||||
});
|
||||
expect(res).toEqual(attributes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -116,6 +116,21 @@ const getById = async id => {
|
||||
return strapi.query('admin::api-token').findOne({ select: SELECT_FIELDS, where: { id } });
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string|number} id
|
||||
* @param {Object} attributes
|
||||
* @param {TokenType} attributes.type
|
||||
* @param {string} attributes.name
|
||||
* @param {string} [attributes.description]
|
||||
*
|
||||
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
|
||||
*/
|
||||
const update = async (id, attributes) => {
|
||||
return strapi
|
||||
.query('admin::api-token')
|
||||
.update({ where: { id }, data: attributes, select: SELECT_FIELDS });
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
exists,
|
||||
@ -124,4 +139,5 @@ module.exports = {
|
||||
list,
|
||||
revoke,
|
||||
getById,
|
||||
update,
|
||||
};
|
||||
|
||||
@ -18,6 +18,10 @@ const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
* 8. Does not return an error if the ressource to delete does not exist
|
||||
* 9. Retrieves a token (successfully)
|
||||
* 10. Returns a 404 if the ressource to retrieve does not exist
|
||||
* 11. Updates a token (successfully)
|
||||
* 12. Returns a 404 if the ressource to update does not exist
|
||||
* 13. Fails to creates an api token (missing parameters from the body)
|
||||
* 14. Fails to creates an api token (invalid `type` in the body)
|
||||
*/
|
||||
|
||||
describe('Admin API Token CRUD (e2e)', () => {
|
||||
@ -37,7 +41,7 @@ describe('Admin API Token CRUD (e2e)', () => {
|
||||
await strapi.destroy();
|
||||
});
|
||||
|
||||
test('1. Fails to creates an api token (missing parameters from the body)', async () => {
|
||||
test('1. Fails to create an api token (missing parameters from the body)', async () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-name',
|
||||
description: 'api-token_tests-description',
|
||||
@ -60,7 +64,7 @@ describe('Admin API Token CRUD (e2e)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('2. Fails to creates an api token (invalid `type` in the body)', async () => {
|
||||
test('2. Fails to create an api token (invalid `type` in the body)', async () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-name',
|
||||
description: 'api-token_tests-description',
|
||||
@ -237,4 +241,90 @@ describe('Admin API Token CRUD (e2e)', () => {
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.body.data).toBeUndefined();
|
||||
});
|
||||
|
||||
test('11. Updates a token (successfully)', async () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-updated-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'read-only',
|
||||
};
|
||||
|
||||
const res = await rq({
|
||||
url: `/admin/api-tokens/${apiTokens[0].id}`,
|
||||
method: 'PUT',
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
type: body.type,
|
||||
id: apiTokens[0].id,
|
||||
});
|
||||
});
|
||||
|
||||
test('12. Returns a 404 if the ressource to update does not exist', async () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-updated-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'read-only',
|
||||
};
|
||||
|
||||
const res = await rq({
|
||||
url: '/admin/api-tokens/42',
|
||||
method: 'PUT',
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.body.data).toBeUndefined();
|
||||
});
|
||||
|
||||
test('13. Fails to update an api token (missing parameters from the body)', async () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-updated-name',
|
||||
description: 'api-token_tests-description',
|
||||
};
|
||||
|
||||
const res = await rq({
|
||||
url: '/admin/api-tokens/1',
|
||||
method: 'PUT',
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: {
|
||||
type: ['type is a required field'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('14. Fails to update an api token (invalid `type` in the body)', async () => {
|
||||
const body = {
|
||||
name: 'api-token_tests-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'invalid-type',
|
||||
};
|
||||
|
||||
const res = await rq({
|
||||
url: '/admin/api-tokens/1',
|
||||
method: 'PUT',
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: {
|
||||
type: ['type must be one of the following values: read-only, full-access'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,6 +26,28 @@ const validateApiTokenCreationInput = async data => {
|
||||
.catch(handleReject);
|
||||
};
|
||||
|
||||
const apiTokenUpdateSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required(),
|
||||
description: yup.string().optional(),
|
||||
type: yup
|
||||
.string()
|
||||
.oneOf(Object.values(constants.API_TOKEN_TYPE))
|
||||
.required(),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
const validateApiTokenUpdateInput = async data => {
|
||||
return apiTokenUpdateSchema
|
||||
.validate(data, { strict: true, abortEarly: false })
|
||||
.catch(handleReject);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateApiTokenCreationInput,
|
||||
validateApiTokenUpdateInput,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user