mirror of https://github.com/ghostfolio/ghostfolio
3 changed files with 280 additions and 1 deletions
@ -0,0 +1,254 @@ |
|||
import { UserService } from '@ghostfolio/api/app/user/user.service'; |
|||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|||
import { PropertyService } from '@ghostfolio/api/services/property/property.service'; |
|||
|
|||
import { InternalServerErrorException } from '@nestjs/common'; |
|||
import { JwtService } from '@nestjs/jwt'; |
|||
import { Test, TestingModule } from '@nestjs/testing'; |
|||
|
|||
import { AuthService } from './auth.service'; |
|||
|
|||
describe('AuthService', () => { |
|||
let authService: AuthService; |
|||
let userService: jest.Mocked<UserService>; |
|||
let jwtService: jest.Mocked<JwtService>; |
|||
let configurationService: jest.Mocked<ConfigurationService>; |
|||
let propertyService: jest.Mocked<PropertyService>; |
|||
let module: TestingModule; |
|||
|
|||
const mockUser = { |
|||
id: 'user-123', |
|||
accessToken: 'hashed-token', |
|||
authChallenge: null, |
|||
createdAt: new Date('2024-01-01'), |
|||
provider: 'GOOGLE' as any, |
|||
role: 'USER' as any, |
|||
thirdPartyId: 'google-123', |
|||
updatedAt: new Date('2024-01-01') |
|||
}; |
|||
|
|||
const mockJwtToken = 'jwt-token-xyz'; |
|||
|
|||
beforeEach(async () => { |
|||
const mockUserService = { |
|||
createAccessToken: jest.fn(), |
|||
users: jest.fn(), |
|||
createUser: jest.fn() |
|||
}; |
|||
|
|||
const mockJwtService = { |
|||
sign: jest.fn() |
|||
}; |
|||
|
|||
const mockConfigurationService = { |
|||
get: jest.fn() |
|||
}; |
|||
|
|||
const mockPropertyService = { |
|||
isUserSignupEnabled: jest.fn() |
|||
}; |
|||
|
|||
module = await Test.createTestingModule({ |
|||
providers: [ |
|||
AuthService, |
|||
{ |
|||
provide: UserService, |
|||
useValue: mockUserService |
|||
}, |
|||
{ |
|||
provide: JwtService, |
|||
useValue: mockJwtService |
|||
}, |
|||
{ |
|||
provide: ConfigurationService, |
|||
useValue: mockConfigurationService |
|||
}, |
|||
{ |
|||
provide: PropertyService, |
|||
useValue: mockPropertyService |
|||
} |
|||
] |
|||
}).compile(); |
|||
|
|||
authService = module.get<AuthService>(AuthService); |
|||
userService = module.get(UserService); |
|||
jwtService = module.get(JwtService); |
|||
configurationService = module.get(ConfigurationService); |
|||
propertyService = module.get(PropertyService); |
|||
}); |
|||
|
|||
afterEach(async () => { |
|||
await module.close(); |
|||
}); |
|||
|
|||
it('should be defined', () => { |
|||
expect(authService).toBeDefined(); |
|||
}); |
|||
|
|||
describe('validateAnonymousLogin', () => { |
|||
const accessToken = 'test-access-token'; |
|||
const hashedToken = 'hashed-test-token'; |
|||
const salt = 'test-salt'; |
|||
|
|||
beforeEach(() => { |
|||
configurationService.get.mockReturnValue(salt); |
|||
userService.createAccessToken.mockReturnValue(hashedToken); |
|||
jwtService.sign.mockReturnValue(mockJwtToken); |
|||
}); |
|||
|
|||
it('should return JWT token when user is found with valid access token', async () => { |
|||
userService.users.mockResolvedValue([mockUser]); |
|||
|
|||
const result = await authService.validateAnonymousLogin(accessToken); |
|||
|
|||
expect(result).toBe(mockJwtToken); |
|||
expect(configurationService.get).toHaveBeenCalledWith( |
|||
'ACCESS_TOKEN_SALT' |
|||
); |
|||
expect(userService.createAccessToken).toHaveBeenCalledWith({ |
|||
password: accessToken, |
|||
salt |
|||
}); |
|||
expect(userService.users).toHaveBeenCalledWith({ |
|||
where: { accessToken: hashedToken } |
|||
}); |
|||
expect(jwtService.sign).toHaveBeenCalledWith({ |
|||
id: mockUser.id |
|||
}); |
|||
}); |
|||
|
|||
it('should reject when user is not found', async () => { |
|||
userService.users.mockResolvedValue([]); |
|||
|
|||
await expect( |
|||
authService.validateAnonymousLogin(accessToken) |
|||
).rejects.toBeUndefined(); |
|||
}); |
|||
|
|||
it('should reject when users query throws error', async () => { |
|||
userService.users.mockRejectedValue(new Error('Database error')); |
|||
|
|||
await expect( |
|||
authService.validateAnonymousLogin(accessToken) |
|||
).rejects.toBeUndefined(); |
|||
}); |
|||
|
|||
it('should use correct salt from configuration', async () => { |
|||
const customSalt = 'custom-salt-value'; |
|||
configurationService.get.mockReturnValue(customSalt); |
|||
userService.users.mockResolvedValue([mockUser]); |
|||
|
|||
await authService.validateAnonymousLogin(accessToken); |
|||
|
|||
expect(userService.createAccessToken).toHaveBeenCalledWith({ |
|||
password: accessToken, |
|||
salt: customSalt |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('validateOAuthLogin', () => { |
|||
const oAuthParams = { |
|||
provider: 'GOOGLE' as any, |
|||
thirdPartyId: 'google-user-123' |
|||
}; |
|||
|
|||
beforeEach(() => { |
|||
jwtService.sign.mockReturnValue(mockJwtToken); |
|||
}); |
|||
|
|||
it('should return JWT token when existing user is found', async () => { |
|||
userService.users.mockResolvedValue([mockUser]); |
|||
|
|||
const result = await authService.validateOAuthLogin(oAuthParams); |
|||
|
|||
expect(result).toBe(mockJwtToken); |
|||
expect(userService.users).toHaveBeenCalledWith({ |
|||
where: { |
|||
provider: oAuthParams.provider, |
|||
thirdPartyId: oAuthParams.thirdPartyId |
|||
} |
|||
}); |
|||
expect(jwtService.sign).toHaveBeenCalledWith({ |
|||
id: mockUser.id |
|||
}); |
|||
expect(userService.createUser).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it('should create new user and return JWT when user not found and signup is enabled', async () => { |
|||
const newUser = { id: 'new-user-123', ...oAuthParams }; |
|||
userService.users.mockResolvedValue([]); |
|||
propertyService.isUserSignupEnabled.mockResolvedValue(true); |
|||
userService.createUser.mockResolvedValue(newUser as any); |
|||
|
|||
const result = await authService.validateOAuthLogin(oAuthParams); |
|||
|
|||
expect(result).toBe(mockJwtToken); |
|||
expect(propertyService.isUserSignupEnabled).toHaveBeenCalled(); |
|||
expect(userService.createUser).toHaveBeenCalledWith({ |
|||
data: { |
|||
provider: oAuthParams.provider, |
|||
thirdPartyId: oAuthParams.thirdPartyId |
|||
} |
|||
}); |
|||
expect(jwtService.sign).toHaveBeenCalledWith({ |
|||
id: newUser.id |
|||
}); |
|||
}); |
|||
|
|||
it('should throw InternalServerErrorException when user not found and signup is disabled', async () => { |
|||
userService.users.mockResolvedValue([]); |
|||
propertyService.isUserSignupEnabled.mockResolvedValue(false); |
|||
|
|||
await expect(authService.validateOAuthLogin(oAuthParams)).rejects.toThrow( |
|||
InternalServerErrorException |
|||
); |
|||
expect(userService.createUser).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it('should throw InternalServerErrorException with error message on failure', async () => { |
|||
const errorMessage = 'Database connection failed'; |
|||
userService.users.mockRejectedValue(new Error(errorMessage)); |
|||
|
|||
await expect(authService.validateOAuthLogin(oAuthParams)).rejects.toThrow( |
|||
InternalServerErrorException |
|||
); |
|||
}); |
|||
|
|||
it('should handle different OAuth providers', async () => { |
|||
const githubParams = { |
|||
provider: 'GITHUB' as any, |
|||
thirdPartyId: 'github-user-456' |
|||
}; |
|||
const githubUser = { id: 'github-user-id', ...githubParams }; |
|||
userService.users.mockResolvedValue([githubUser as any]); |
|||
|
|||
const result = await authService.validateOAuthLogin(githubParams); |
|||
|
|||
expect(result).toBe(mockJwtToken); |
|||
expect(userService.users).toHaveBeenCalledWith({ |
|||
where: { |
|||
provider: githubParams.provider, |
|||
thirdPartyId: githubParams.thirdPartyId |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('JWT token generation', () => { |
|||
it('should generate token with correct user id', async () => { |
|||
const userId = 'test-user-id-123'; |
|||
const user = { ...mockUser, id: userId }; |
|||
userService.users.mockResolvedValue([user]); |
|||
configurationService.get.mockReturnValue('salt'); |
|||
userService.createAccessToken.mockReturnValue('hash'); |
|||
jwtService.sign.mockReturnValue(mockJwtToken); |
|||
|
|||
await authService.validateAnonymousLogin('token'); |
|||
|
|||
expect(jwtService.sign).toHaveBeenCalledWith({ |
|||
id: userId |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue