// @ts-nocheck import { expect } from 'chai'; import * as sinon from 'sinon'; import { User } from '../../src/user'; import { userData, userProfile, newUserProfile, newUserData, userStudentInformation, graphqlUserData } from '../fixtures/user-data'; import { TestConfiguration } from '../../src/configuration'; import { NotFoundError, ValidationError, ServerError } from '../../src/errors'; import { User as UserModel } from '../../src/models/user'; describe('User SDK', () => { const mockAuthToken = 'test-auth-token'; let sandbox; let instance; let config; beforeEach(() => { sandbox = sinon.createSandbox(); config = new TestConfiguration({ userProfileServiceApiKey: 'user-profile-svc-key', originSystemId: 'biz-ops-system-code', requestUserPurgeApiKey: 'user-purge-api-key', }); instance = new User(config); sandbox.stub(instance, 'requestHead').resolves({ status: 200 }); sandbox.stub(instance, 'requestPost').resolves({}); sandbox.stub(instance, 'requestPut').resolves({}); sandbox.stub(instance, 'requestGet').resolves({}); }); afterEach(() => { sandbox.restore(); }); it('requires host and key configuration properties', () => { try { new User({}); expect.fail('Should have thrown an error!'); } catch (error) { } }); describe('doesUserExist', () => { const mockEmail = 'test'; it('passes correct parameters to requestHead', async () => { const url = `https://api-t.ft.com/users/profile?email=${mockEmail}`; await instance.doesUserExist(mockEmail); const headParams = instance.requestHead.getCall(0).args[0]; // Path checks expect(headParams).to.have.property('url'); expect(headParams.url).to.equal(url); }); context('when a 200 status is returned', () => { const mockResponse = { status: 200 }; beforeEach(() => { instance.requestHead.resolves(mockResponse); }); it('resolves true', async () => { const data = await instance.doesUserExist(mockEmail); expect(data).to.equal(true); }); }); context('when a 404 status is returned', () => { const errorResponse = new NotFoundError('Not Found'); beforeEach(() => { instance.requestHead.rejects(errorResponse); }); it('resolves false', async () => { const data = await instance.doesUserExist(mockEmail); expect(data).to.equal(false); }); }); context('when another status is returned', () => { const errorResponse = new ServerError('Server Error 500'); beforeEach(() => { instance.requestHead.rejects(errorResponse); }); it('throws an error', async () => { try { await instance.doesUserExist(mockEmail); expect.fail('Should have thrown!'); } catch (error) { expect(error.message).to.equal('Server Error 500'); } }); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestHead.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.doesUserExist(mockEmail); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('getUserResources', () => { const mockEmail = 'test@ft.com'; it('returns empty list when user does not exist', async () => { const url = `https://api-t.ft.com/users/profile?email=${mockEmail}`; const mockResponse = { items: [], }; instance.requestGet.resolves(mockResponse); const userResources = await instance.getUserResources(mockEmail); const getParams = instance.requestGet.getCall(0).args[0]; // Path checks expect(getParams).to.have.property('url'); expect(getParams.url).to.equal(url); // Response checks expect(userResources).to.have.property('items'); expect(userResources.items).to.be.empty; }); it('returns the user resource when exists', async () => { const url = `https://api-t.ft.com/users/profile?email=${mockEmail}`; const mockResponse = { items: [ { id: '0000-0000-0000-0000', href: '/users/0000-0000-0000-0000' } ], }; instance.requestGet.resolves(mockResponse); const userResources = await instance.getUserResources(mockEmail); const getParams = instance.requestGet.getCall(0).args[0]; // Path checks expect(getParams).to.have.property('url'); expect(getParams.url).to.equal(url); // Response checks expect(userResources).to.have.property('items'); expect(userResources).to.equal(mockResponse); }); }); describe('createProfile', () => { const mockPassword = 'test-password'; const mockProfileData = Object.assign({ password: mockPassword }, newUserData); const mockFormattedProfile = JSON.parse(JSON.stringify(newUserProfile)); it('passes correct parameters to requestPost', async () => { await instance.createProfile(mockProfileData); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams).to.have.property('url'); expect(postParams.url).to.equal('https://api-t.ft.com/users/profile'); // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('user'); expect(postParams.body.user).to.have.property('password'); const userDetails = Object.assign({ password: mockPassword }, mockFormattedProfile); expect(postParams.body.user).to.have.property('source'); expect(postParams.body.user).to.deep.equal(userDetails); }); it('passes an additional parameter to requestPost to send a registration email to the user', async () => { const sendRegistrationEmail = true; await instance.createProfile(mockProfileData, sendRegistrationEmail); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams).to.have.property('url'); expect(postParams.url).to.equal('https://api-t.ft.com/users/profile?sendRegistrationEmail=true'); }); it('passes a named source to the body that is POSTed', async () => { const noSourceProfileData = mockProfileData; delete noSourceProfileData.source; await instance.createProfile(noSourceProfileData, false); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.user.source).to.equal('biz-ops-system-code'); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPost.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.createProfile(mockProfileData); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); it('passes externalUserId parameter to requestPost', async () => { await instance.createProfile({ ...mockProfileData, externalUserId: 'test-external-id' }); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.user.externalUserId).to.equal('test-external-id'); }); }); describe('updateProfile', () => { it('passes correct parameters to requestPut', async () => { const url = `https://api-t.ft.com/users/${userData.id}/profile`; await instance.updateProfile(userData, mockAuthToken); const putParams = instance.requestPut.getCall(0).args[0]; // Path checks expect(putParams).to.have.property('url'); expect(putParams.url).to.equal(url); // additional headers check expect(putParams).to.have.property('additionalHeaders'); expect(putParams.additionalHeaders).to.have.property('Authorization'); expect(putParams.additionalHeaders['Authorization']).to.eql(`Bearer ${mockAuthToken}`); // checks for correct profile structure expect(putParams).to.have.property('body'); expect(putParams.body).to.have.property('user'); expect(putParams.body.user).to.have.property('demographics'); expect(putParams.body.user.demographics).to.have.property('industry'); expect(putParams.body.user.demographics.industry).to.have.property('code'); expect(putParams.body.user).to.deep.equal(userProfile); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPut.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.updateProfile(userData, mockAuthToken); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('updateExternalUserId', () => { it('passes correct parameters to requestPut', async () => { const user = { ...userData, externalUserId: 'test-external-id', }; await instance.updateExternalUserId(user, mockAuthToken); const putParams = instance.requestPut.getCall(0).args[0]; expect(putParams.url).to.equal(`https://api-t.ft.com/users/profile/${user.id}/external-user-id`); expect(putParams.additionalHeaders['Authorization']).to.eql(`Bearer ${mockAuthToken}`); expect(putParams.additionalHeaders['X-Api-Key']).to.eql('user-profile-svc-key'); expect(putParams.body).to.deep.equal({ id: 'test-external-id' }); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPut.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.updateExternalUserId(userData.id, 'test', mockAuthToken); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.equal(errorResponse); } }); }); }); describe('changeEmail', () => { const newEmail = 'bruce.wayne@ftqa.org'; it('passes correct parameters to requestPost', async () => { const url = `https://api-t.ft.com/users/${userData.id}/profile/basic-profile/change-email`; await instance.changeEmail(userData.id, newEmail, mockAuthToken); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams.url).to.equal(url); // additional headers check expect(postParams).to.have.property('additionalHeaders'); expect(postParams.additionalHeaders).to.have.property('Authorization'); expect(postParams.additionalHeaders['Authorization']).to.eql(`Bearer ${mockAuthToken}`); // checks for correct profile structure expect(postParams).to.have.property('body'); expect(postParams.body).to.equal(newEmail); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPost.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.changeEmail(userData.id, newEmail, mockAuthToken); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('updateStudentInformation', () => { it('passes correct parameters to requestPut', async () => { const url = 'https://api-t.ft.com/users/fakeId/profile/student-information'; await instance.updateStudentInformation('fakeId', userStudentInformation, mockAuthToken); const putParams = instance.requestPut.getCall(0).args[0]; // Path checks expect(putParams.url).to.equal(url); // check Authorization expect(putParams.additionalHeaders['Authorization']).to.eql(`Bearer ${mockAuthToken}`); // check API Key expect(putParams.additionalHeaders['x-api-key']).to.eql('user-profile-svc-key'); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPut.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.updateStudentInformation('fakeId', userStudentInformation, mockAuthToken); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('requestPurge', () => { it('passess correct parameters to requestPost', async () => { const url = `${config.get('requestUserPurgeEndpoint')}/delete`; await instance.requestPurge('fakeId', 'Test User'); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams.url).to.equal(url); // check API Key expect(postParams.additionalHeaders['ft-api-key']).to.equal('user-purge-api-key'); expect(postParams.body.userId).to.equal('fakeId'); expect(postParams.body.deletionCategory).to.equal('Test User'); }); it('validates the deletion category', async () => { try { await instance.requestPurge('fakeId', 'Invalid deletion category'); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.be.instanceOf(ValidationError); expect(error.message).to.equal('Invalid deletionCategory. Allowed values are: Erasure Request, Retention Policy, Test User'); } }); // this test doesn't follow the beforeEach conventions above as it just doesn't make sense // to extract the setup of a single test in a beforeEach // // it is slightly more work for mocha (obv neglectable) but harder to read for developers // // I think the whole file needs refactoring in that respect but I don't have the time to do it now // hopefully this will act as a TODO and potentialy it will get better at some point // // it('it...') is redundant as well it('throws the request error if any', async () => { const errorResponse = new Error('Test failure'); instance.requestPost.rejects(errorResponse); try { await instance.requestPurge('fakeId', 'Test User'); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); describe('mapUserDetailsResponse', () => { it('should return User object when called with raw user data', async () => { const user = instance.mapUserDetailsResponse(graphqlUserData, false); expect(user instanceof UserModel).to.be.true; expect(user.firstName).equals(graphqlUserData.profiles.basic.firstName); expect(user.email).equals(graphqlUserData.profiles.basic.email); expect(user.postcode).equals(graphqlUserData.profiles.restricted.homeAddress.postcode); expect(user.isB2C).equals(graphqlUserData.access.isB2c); expect(user.isProfileRestricted).equals(false); }); }); });