From 3a035cba8b4eecacc93a37d8a63919652e9ef442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 7 Dec 2023 09:19:16 +0100 Subject: [PATCH] test(core): Expand user service tests (no-changelog) (#7941) ## Summary Expand user service tests ... #### How to test the change: 1. ... ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers ... ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e). --- .../test/unit/services/user.service.test.ts | 106 ++++++++++++++---- 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/packages/cli/test/unit/services/user.service.test.ts b/packages/cli/test/unit/services/user.service.test.ts index 6c1110ff39..b61cd244a6 100644 --- a/packages/cli/test/unit/services/user.service.test.ts +++ b/packages/cli/test/unit/services/user.service.test.ts @@ -7,30 +7,79 @@ import { UserRepository } from '@db/repositories/user.repository'; import { UserService } from '@/services/user.service'; import { mockInstance } from '../../shared/mocking'; import { RoleService } from '@/services/role.service'; +import { v4 as uuid } from 'uuid'; describe('UserService', () => { config.set('userManagement.jwtSecret', 'random-secret'); mockInstance(Logger); - const repository = mockInstance(UserRepository); mockInstance(RoleService); - const service = Container.get(UserService); - const testUser = Object.assign(new User(), { - id: '1234', + + const userRepository = mockInstance(UserRepository); + const userService = Container.get(UserService); + + const commonMockUser = Object.assign(new User(), { + id: uuid(), password: 'passwordHash', - mfaEnabled: false, }); - beforeEach(() => { - jest.resetAllMocks(); + describe('toPublic', () => { + it('should remove sensitive properties', async () => { + const mockUser = Object.assign(new User(), { + id: uuid(), + password: 'passwordHash', + mfaEnabled: false, + mfaSecret: 'test', + mfaRecoveryCodes: ['test'], + updatedAt: new Date(), + authIdentities: [], + }); + + type MaybeSensitiveProperties = Partial< + Pick + >; + + // to prevent typechecking from blocking assertions + const publicUser: MaybeSensitiveProperties = await userService.toPublic(mockUser); + + expect(publicUser.password).toBeUndefined(); + expect(publicUser.mfaSecret).toBeUndefined(); + expect(publicUser.mfaRecoveryCodes).toBeUndefined(); + expect(publicUser.updatedAt).toBeUndefined(); + expect(publicUser.authIdentities).toBeUndefined(); + }); + + it('should add scopes if requested', async () => { + const scopeless = await userService.toPublic(commonMockUser, { withScopes: false }); + + const scoped = await userService.toPublic(commonMockUser, { withScopes: true }); + + expect(Array.isArray(scopeless.globalScopes)).toBe(false); + expect(Array.isArray(scoped.globalScopes)).toBe(true); + }); + + it('should add invite URL if requested', async () => { + const mockUser = Object.assign(new User(), { id: uuid(), isPending: true }); + + const withUrl = await userService.toPublic(mockUser, { withInviteUrl: true }); + const withoutUrl = await userService.toPublic(mockUser, { withInviteUrl: false }); + + expect(typeof withUrl.inviteAcceptUrl === 'string').toBe(true); + expect(withoutUrl.inviteAcceptUrl).toBeUndefined(); + }); }); describe('generatePasswordResetToken', () => { it('should generate valid password-reset tokens', () => { - const token = service.generatePasswordResetToken(testUser); + const token = userService.generatePasswordResetToken(commonMockUser); + const decoded = jwt.decode(token) as jwt.JwtPayload; - expect(decoded.sub).toEqual(testUser.id); - expect(decoded.exp! - decoded.iat!).toEqual(1200); // Expires in 20 minutes + + if (!decoded.exp) fail('Token does not contain expiry'); + if (!decoded.iat) fail('Token does not contain issued-at'); + + expect(decoded.sub).toEqual(commonMockUser.id); + expect(decoded.exp - decoded.iat).toEqual(1200); // Expires in 20 minutes expect(decoded.passwordSha).toEqual( '31513c5a9e3c5afe5c06d5675ace74e8bc3fadd9744ab5d89c311f2a62ccbd39', ); @@ -39,37 +88,46 @@ describe('UserService', () => { describe('resolvePasswordResetToken', () => { it('should not return a user if the token in invalid', async () => { - const user = await service.resolvePasswordResetToken('invalid-token'); + const user = await userService.resolvePasswordResetToken('invalid-token'); + expect(user).toBeUndefined(); }); it('should not return a user if the token in expired', async () => { - const token = service.generatePasswordResetToken(testUser, '-1h'); - const user = await service.resolvePasswordResetToken(token); + const token = userService.generatePasswordResetToken(commonMockUser, '-1h'); + + const user = await userService.resolvePasswordResetToken(token); + expect(user).toBeUndefined(); }); it('should not return a user if the user does not exist in the DB', async () => { - repository.findOne.mockResolvedValueOnce(null); - const token = service.generatePasswordResetToken(testUser); - const user = await service.resolvePasswordResetToken(token); + userRepository.findOne.mockResolvedValueOnce(null); + const token = userService.generatePasswordResetToken(commonMockUser); + + const user = await userService.resolvePasswordResetToken(token); + expect(user).toBeUndefined(); }); it('should not return a user if the password sha does not match', async () => { - const token = service.generatePasswordResetToken(testUser); - const updatedUser = Object.create(testUser); + const token = userService.generatePasswordResetToken(commonMockUser); + const updatedUser = Object.create(commonMockUser); updatedUser.password = 'something-else'; - repository.findOne.mockResolvedValueOnce(updatedUser); - const user = await service.resolvePasswordResetToken(token); + userRepository.findOne.mockResolvedValueOnce(updatedUser); + + const user = await userService.resolvePasswordResetToken(token); + expect(user).toBeUndefined(); }); it('should not return the user if all checks pass', async () => { - const token = service.generatePasswordResetToken(testUser); - repository.findOne.mockResolvedValueOnce(testUser); - const user = await service.resolvePasswordResetToken(token); - expect(user).toEqual(testUser); + const token = userService.generatePasswordResetToken(commonMockUser); + userRepository.findOne.mockResolvedValueOnce(commonMockUser); + + const user = await userService.resolvePasswordResetToken(token); + + expect(user).toEqual(commonMockUser); }); }); });