test: add failing specs for AuthService login API integration
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController
|
||||
} from '@angular/common/http/testing';
|
||||
import {
|
||||
AuthService,
|
||||
LoginResultType,
|
||||
UserInfo
|
||||
} from './auth.service';
|
||||
import { ApiConfigService } from '../../core/services/api-config.service';
|
||||
|
||||
const MOCK_USER: UserInfo = {
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
roles: ['Admin'],
|
||||
languagePreference: 'en'
|
||||
};
|
||||
|
||||
const MOCK_API_RESPONSE = {
|
||||
accessToken: 'mock-access-token',
|
||||
expiresIn: 900,
|
||||
user: MOCK_USER
|
||||
};
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
let httpMock: HttpTestingController;
|
||||
let apiConfig: ApiConfigService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [AuthService, ApiConfigService]
|
||||
});
|
||||
service = TestBed.inject(AuthService);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
apiConfig = TestBed.inject(ApiConfigService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
// ── login() ────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('login()', () => {
|
||||
it('should POST to /api/auth/login with email and password', () => {
|
||||
service.login({ email: 'test@example.com', password: 'secret' }).subscribe();
|
||||
const req = httpMock.expectOne(`${apiConfig.authUrl}/login`);
|
||||
expect(req.request.method).toBe('POST');
|
||||
expect(req.request.body).toEqual({ email: 'test@example.com', password: 'secret' });
|
||||
expect(req.request.withCredentials).toBeTrue();
|
||||
req.flush(MOCK_API_RESPONSE);
|
||||
});
|
||||
|
||||
it('should return LoginResultType.Success and store token + user on 200', () => {
|
||||
let result: any;
|
||||
service.login({ email: 'test@example.com', password: 'secret' }).subscribe(r => result = r);
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(MOCK_API_RESPONSE);
|
||||
|
||||
expect(result.result).toBe(LoginResultType.Success);
|
||||
expect(result.responseData).toEqual(MOCK_USER);
|
||||
expect(service.getToken()).toBe('mock-access-token');
|
||||
expect(service.getCurrentUser()).toEqual(MOCK_USER);
|
||||
expect(service.isAuthenticated()).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return LoginResultType.InvalidCredentials on 401', () => {
|
||||
let result: any;
|
||||
service.login({ email: 'bad@example.com', password: 'wrong' }).subscribe(r => result = r);
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(
|
||||
{ message: 'Invalid credentials' },
|
||||
{ status: 401, statusText: 'Unauthorized' }
|
||||
);
|
||||
|
||||
expect(result.result).toBe(LoginResultType.InvalidCredentials);
|
||||
expect(service.getToken()).toBeNull();
|
||||
expect(service.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should return LoginResultType.Error on non-401 HTTP error', () => {
|
||||
let result: any;
|
||||
service.login({ email: 'test@example.com', password: 'secret' }).subscribe(r => result = r);
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(
|
||||
{ message: 'Server error' },
|
||||
{ status: 500, statusText: 'Internal Server Error' }
|
||||
);
|
||||
|
||||
expect(result.result).toBe(LoginResultType.Error);
|
||||
});
|
||||
});
|
||||
|
||||
// ── refresh() ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe('refresh()', () => {
|
||||
it('should POST to /api/auth/refresh with withCredentials', () => {
|
||||
service.refresh().subscribe();
|
||||
const req = httpMock.expectOne(`${apiConfig.authUrl}/refresh`);
|
||||
expect(req.request.method).toBe('POST');
|
||||
expect(req.request.withCredentials).toBeTrue();
|
||||
req.flush(MOCK_API_RESPONSE);
|
||||
});
|
||||
|
||||
it('should return true and update token + user on 200', () => {
|
||||
let result: boolean | undefined;
|
||||
service.refresh().subscribe(r => result = r);
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(MOCK_API_RESPONSE);
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(service.getToken()).toBe('mock-access-token');
|
||||
expect(service.getCurrentUser()).toEqual(MOCK_USER);
|
||||
});
|
||||
|
||||
it('should return false and leave state unchanged on 401', () => {
|
||||
let result: boolean | undefined;
|
||||
service.refresh().subscribe(r => result = r);
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(
|
||||
{ message: 'Refresh token expired' },
|
||||
{ status: 401, statusText: 'Unauthorized' }
|
||||
);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(service.getToken()).toBeNull();
|
||||
expect(service.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
// ── logout() ───────────────────────────────────────────────────────────────
|
||||
|
||||
describe('logout()', () => {
|
||||
it('should clear token and user from memory immediately', () => {
|
||||
// Seed state
|
||||
service['accessToken$'].next('some-token');
|
||||
service['currentUser$'].next(MOCK_USER);
|
||||
|
||||
service.logout();
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/logout`).flush(null, { status: 204, statusText: 'No Content' });
|
||||
|
||||
expect(service.getToken()).toBeNull();
|
||||
expect(service.getCurrentUser()).toBeNull();
|
||||
expect(service.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should POST to /api/auth/logout with withCredentials', () => {
|
||||
service.logout();
|
||||
const req = httpMock.expectOne(`${apiConfig.authUrl}/logout`);
|
||||
expect(req.request.method).toBe('POST');
|
||||
expect(req.request.withCredentials).toBeTrue();
|
||||
req.flush(null, { status: 204, statusText: 'No Content' });
|
||||
});
|
||||
|
||||
it('should not throw if the logout API call fails', () => {
|
||||
expect(() => {
|
||||
service.logout();
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/logout`).flush(
|
||||
{ message: 'Server error' },
|
||||
{ status: 500, statusText: 'Internal Server Error' }
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// ── initializeFromRefreshToken() ───────────────────────────────────────────
|
||||
|
||||
describe('initializeFromRefreshToken()', () => {
|
||||
it('should resolve even when refresh returns 401 (does not block bootstrap)', async () => {
|
||||
const promise = service.initializeFromRefreshToken();
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(
|
||||
{ message: 'No cookie' },
|
||||
{ status: 401, statusText: 'Unauthorized' }
|
||||
);
|
||||
await expectAsync(promise).toBeResolved();
|
||||
});
|
||||
|
||||
it('should resolve and authenticate user when refresh succeeds', async () => {
|
||||
const promise = service.initializeFromRefreshToken();
|
||||
httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(MOCK_API_RESPONSE);
|
||||
await expectAsync(promise).toBeResolved();
|
||||
expect(service.isAuthenticated()).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
// ── setCurrentUser() / getCurrentUser() ────────────────────────────────────
|
||||
|
||||
describe('setCurrentUser()', () => {
|
||||
it('should update currentUser$ and mark authenticated', () => {
|
||||
service.setCurrentUser(MOCK_USER);
|
||||
expect(service.getCurrentUser()).toEqual(MOCK_USER);
|
||||
expect(service.isAuthenticated()).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
// ── redirect URL helpers ────────────────────────────────────────────────────
|
||||
|
||||
describe('redirect URL helpers', () => {
|
||||
it('should default redirect to /dashboard', () => {
|
||||
expect(service.getRedirectUrl()).toBe('/dashboard');
|
||||
});
|
||||
|
||||
it('should store and return a custom redirect URL', () => {
|
||||
service.setRedirectUrl('/members');
|
||||
expect(service.getRedirectUrl()).toBe('/members');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user