allow for partial payload to update a token

This commit is contained in:
Dieter Stinglhamber 2021-09-08 14:38:43 +02:00
parent f7bd99cb74
commit 31d74d2710
6 changed files with 138 additions and 34 deletions

View File

@ -210,7 +210,7 @@ describe('API Token Controller', () => {
test('Fails if the name is already taken', async () => {
const getById = jest.fn(() => ({ id, ...body }));
const exists = jest.fn(() => true);
const getByName = jest.fn(() => ({ id: 2, name: body.name }));
const badRequest = jest.fn();
const ctx = createContext({ body, params: { id } }, { badRequest });
@ -218,8 +218,8 @@ describe('API Token Controller', () => {
admin: {
services: {
'api-token': {
exists,
getById,
getByName,
},
},
},
@ -227,7 +227,7 @@ describe('API Token Controller', () => {
await apiTokenController.update(ctx);
expect(exists).toHaveBeenCalledWith({ name: body.name });
expect(getByName).toHaveBeenCalledWith(body.name);
expect(badRequest).toHaveBeenCalledWith('Name already taken');
});
@ -255,7 +255,7 @@ describe('API Token Controller', () => {
test('Updates API Token Successfully', async () => {
const update = jest.fn().mockResolvedValue(body);
const getById = jest.fn(() => ({ id, ...body }));
const exists = jest.fn(() => false);
const getByName = jest.fn(() => null);
const badRequest = jest.fn();
const notFound = jest.fn();
const send = jest.fn();
@ -266,7 +266,7 @@ describe('API Token Controller', () => {
services: {
'api-token': {
getById,
exists,
getByName,
update,
},
},
@ -276,7 +276,7 @@ describe('API Token Controller', () => {
await apiTokenController.update(ctx);
expect(getById).toHaveBeenCalledWith(id);
expect(exists).toHaveBeenCalledWith({ name: body.name });
expect(getByName).toHaveBeenCalledWith(body.name);
expect(badRequest).not.toHaveBeenCalled();
expect(notFound).not.toHaveBeenCalled();
expect(update).toHaveBeenCalledWith(id, body);

View File

@ -73,16 +73,19 @@ module.exports = {
const { id } = ctx.params;
const apiTokenService = getService('api-token');
const attributes = body;
/**
* 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,
};
if (has(attributes, 'name')) {
attributes.name = trim(body.name);
}
if (has(attributes, 'description') || attributes.description === null) {
attributes.description = trim(body.description);
}
try {
await validateApiTokenUpdateInput(attributes);
@ -96,8 +99,8 @@ module.exports = {
}
if (has(attributes, 'name')) {
const nameAlreadyTaken = await apiTokenService.exists({ name: attributes.name });
if (nameAlreadyTaken) {
const nameAlreadyTaken = await apiTokenService.getByName(attributes.name);
if (!!nameAlreadyTaken && nameAlreadyTaken.id !== id) {
return ctx.badRequest('Name already taken');
}
}

View File

@ -199,7 +199,7 @@ describe('API Token', () => {
});
});
describe('get', () => {
describe('getById', () => {
const token = {
id: 1,
name: 'api-token_tests-name',
@ -274,4 +274,48 @@ describe('API Token', () => {
expect(res).toEqual(attributes);
});
});
describe('getByName', () => {
const token = {
id: 1,
name: 'api-token_tests-name',
description: 'api-token_tests-description',
type: 'read-only',
};
test('It retrieves the token', async () => {
const findOne = jest.fn().mockResolvedValue(token);
global.strapi = {
query() {
return { findOne };
},
};
const res = await apiTokenService.getByName(token.name);
expect(findOne).toHaveBeenCalledWith({
select: ['id', 'name', 'description', 'type'],
where: { name: token.name },
});
expect(res).toEqual(token);
});
test('It returns `null` if the resource does not exist', async () => {
const findOne = jest.fn().mockResolvedValue(null);
global.strapi = {
query() {
return { findOne };
},
};
const res = await apiTokenService.getByName('unexistant-name');
expect(findOne).toHaveBeenCalledWith({
select: ['id', 'name', 'description', 'type'],
where: { name: 'unexistant-name' },
});
expect(res).toEqual(null);
});
});
});

View File

@ -116,6 +116,15 @@ const getById = async id => {
return strapi.query('admin::api-token').findOne({ select: SELECT_FIELDS, where: { id } });
};
/**
* @param {string} name
*
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
*/
const getByName = async name => {
return strapi.query('admin::api-token').findOne({ select: SELECT_FIELDS, where: { name } });
};
/**
* @param {string|number} id
* @param {Object} attributes
@ -140,4 +149,5 @@ module.exports = {
revoke,
getById,
update,
getByName,
};

View File

@ -8,8 +8,8 @@ const { createAuthRequest } = require('../../../../../test/helpers/request');
*
* N° Description
* -------------------------------------------
* 1. Fails to creates an api token (missing parameters from the body)
* 2. Fails to creates an api token (invalid `type` in the body)
* 1. Fails to create an api token (missing parameters from the body)
* 2. Fails to create an api token (invalid `type` in the body)
* 3. Creates an api token (successfully)
* 4. Creates an api token without a description (successfully)
* 5. Creates an api token with trimmed description and name (successfully)
@ -20,8 +20,10 @@ const { createAuthRequest } = require('../../../../../test/helpers/request');
* 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)
* 13. Updates a token with partial payload (successfully)
* 14. Fails to update an api token (invalid `type` in the body)
* 15. Updates a token when passing a `null` description (successfully)
* 16. Updates a token but not the description of no description is passed (successfully)
*/
describe('Admin API Token CRUD (e2e)', () => {
@ -262,12 +264,14 @@ describe('Admin API Token CRUD (e2e)', () => {
type: body.type,
id: apiTokens[0].id,
});
apiTokens[0] = res.body.data;
});
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',
description: 'api-token_tests-updated-description',
type: 'read-only',
};
@ -281,27 +285,26 @@ describe('Admin API Token CRUD (e2e)', () => {
expect(res.body.data).toBeUndefined();
});
test('13. Fails to update an api token (missing parameters from the body)', async () => {
test('13. Updates a token with partial payload (successfully)', async () => {
const body = {
name: 'api-token_tests-updated-name',
description: 'api-token_tests-description',
description: 'api-token_tests-re-updated-description',
};
const res = await rq({
url: '/admin/api-tokens/1',
url: `/admin/api-tokens/${apiTokens[0].id}`,
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'],
},
expect(res.statusCode).toBe(200);
expect(res.body.data).toMatchObject({
name: apiTokens[0].name,
description: body.description,
type: apiTokens[0].type,
id: apiTokens[0].id,
});
apiTokens[0] = res.body.data;
});
test('14. Fails to update an api token (invalid `type` in the body)', async () => {
@ -327,4 +330,48 @@ describe('Admin API Token CRUD (e2e)', () => {
},
});
});
test('15. Updates a token when passing a `null` description (successfully)', async () => {
const body = {
description: null,
};
const res = await rq({
url: `/admin/api-tokens/${apiTokens[0].id}`,
method: 'PUT',
body,
});
expect(res.statusCode).toBe(200);
expect(res.body.data).toMatchObject({
name: apiTokens[0].name,
description: '',
type: apiTokens[0].type,
id: apiTokens[0].id,
});
apiTokens[0] = res.body.data;
});
test('16. Updates a token but not the description of no description is passed (successfully)', async () => {
const body = {
name: 'api-token_tests-name',
};
const res = await rq({
url: `/admin/api-tokens/${apiTokens[0].id}`,
method: 'PUT',
body,
});
expect(res.statusCode).toBe(200);
expect(res.body.data).toMatchObject({
name: body.name,
description: apiTokens[0].description,
type: apiTokens[0].type,
id: apiTokens[0].id,
});
apiTokens[0] = res.body.data;
});
});

View File

@ -32,12 +32,12 @@ const apiTokenUpdateSchema = yup
name: yup
.string()
.min(1)
.required(),
description: yup.string().optional(),
.notNull(),
description: yup.string().nullable(),
type: yup
.string()
.oneOf(Object.values(constants.API_TOKEN_TYPE))
.required(),
.notNull(),
})
.noUnknown();