diff --git a/test/api/v3/integration/members/GET-members_id.test.js b/test/api/v3/integration/members/GET-members_id.test.js new file mode 100644 index 0000000000..25c7600c80 --- /dev/null +++ b/test/api/v3/integration/members/GET-members_id.test.js @@ -0,0 +1,41 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /members/:memberId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns a member public data only', async () => { + let member = await generateUser({ // make sure user has all the fields that can be returned by the getMember call + contributor: {level: 1}, + backer: {tier: 3}, + preferences: { + costume: false, + background: 'volcano', + }, + }); + let memberRes = await user.get(`/members/${member._id}`); + expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys + '_id', 'preferences', 'profile', 'stats', 'achievements', 'party', + 'backer', 'contributor', 'auth', 'items', + ]); + expect(Object.keys(memberRes.auth)).to.eql(['timestamps']); + expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt', + 'costume', 'sleep', 'background'].sort()); + }); + + it('handles non-existing members', async () => { + let dummyId = generateUUID(); + await expect(user.get(`/members/${dummyId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: dummyId}), + }); + }); +}); diff --git a/test/api/v3/integration/tags/GET-tags_id.test.js b/test/api/v3/integration/tags/GET-tags_id.test.js index 1189c2af24..c46b5b8801 100644 --- a/test/api/v3/integration/tags/GET-tags_id.test.js +++ b/test/api/v3/integration/tags/GET-tags_id.test.js @@ -15,4 +15,6 @@ describe('GET /tags/:tagId', () => { expect(tag).to.deep.equal(createdTag); }); + + it('handles non-existing tags'); }); diff --git a/website/src/controllers/api-v3/members.js b/website/src/controllers/api-v3/members.js index f464fe830c..6eef598050 100644 --- a/website/src/controllers/api-v3/members.js +++ b/website/src/controllers/api-v3/members.js @@ -42,7 +42,8 @@ api.getMember = { if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); - res.respond(200, member); + // manually call toJSON with minimize: true so empty paths aren't returned + res.respond(200, member.toJSON({minimize: true})); }, }; diff --git a/website/src/models/user.js b/website/src/models/user.js index fcad88c5a6..8942a8afd2 100644 --- a/website/src/models/user.js +++ b/website/src/models/user.js @@ -472,18 +472,20 @@ export let schema = new Schema({ }, }, { strict: true, - minimize: false, // So empty objects are returned + minimize: false, // So empty objects are returned TODO make sure it's in every model }); schema.plugin(baseModel, { - // TODO revisit a lot of things are missing - noSet: ['_id', 'apiToken', 'auth.blocked', 'auth.timestamps', 'lastCron', 'auth.local.hashed_password', 'auth.local.salt', 'tasksOrder', 'tags', 'stats', 'challenges', 'guilds', 'party._id', 'party.quest', 'invitations', 'balance'], + // TODO revisit a lot of things are missing. Given how many attributes we do have here we should white-list the ones that can be updated + noSet: ['_id', 'apiToken', 'auth.blocked', 'auth.timestamps', 'lastCron', 'auth.local.hashed_password', + 'auth.local.salt', 'tasksOrder', 'tags', 'stats', 'challenges', 'guilds', 'party._id', 'party.quest', + 'invitations', 'balance', 'backer', 'contributor'], private: ['auth.local.hashed_password', 'auth.local.salt'], toJSONTransform: function userToJSON (doc) { // FIXME? Is this a reference to `doc.filters` or just disabled code? Remove? // TODO this works? - doc.filters = {}; - doc._tmp = this._tmp; // be sure to send down drop notifs + // doc.filters = {}; + // doc._tmp = this._tmp; // be sure to send down drop notifs return doc; },