feat(auth): implement authentication service, guards, and interceptors
- Add AuthService for handling login, registration, and session management. - Create auth guards to protect routes based on authentication status. - Implement auth interceptor to attach JWT token to HTTP requests and handle 401 errors. - Add unit tests for AuthService, guards, and interceptor. - Create login and registration components with form validation and error handling. - Update routing to include guards for login and registration pages.
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { computed, signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter, Router } from '@angular/router';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { App } from './app';
|
||||
import { routes } from './app.routes';
|
||||
import { AuthService } from './core/services/auth/auth.service';
|
||||
import { CartService } from './core/services/cart/cart.service';
|
||||
import { Tenant } from './core/services/tenant.interface';
|
||||
import { TenantService } from './core/services/tenant.service';
|
||||
import { CartService } from './core/services/cart/cart.service';
|
||||
import { routes } from './app.routes';
|
||||
|
||||
const tenant: Tenant = {
|
||||
id: 1,
|
||||
@@ -47,9 +48,33 @@ function createTenantServiceStub(
|
||||
};
|
||||
}
|
||||
|
||||
function createAuthServiceStub(isAuthenticated = false) {
|
||||
const tokenState = signal<string | null>(isAuthenticated ? 'test-token' : null);
|
||||
const userState = signal(
|
||||
isAuthenticated ? { id: 1, nombre_apellido: 'Ada Lovelace', email: 'ada@example.com' } : null
|
||||
);
|
||||
|
||||
return {
|
||||
user: userState.asReadonly(),
|
||||
token: tokenState.asReadonly(),
|
||||
isAuthenticated: computed(() => tokenState() !== null),
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
loadCurrentUser: vi.fn(),
|
||||
hydrateSession: vi.fn(),
|
||||
bootstrap: vi.fn().mockResolvedValue(undefined),
|
||||
clearSession: vi.fn(() => {
|
||||
userState.set(null);
|
||||
tokenState.set(null);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
async function renderAppAt(
|
||||
url: string,
|
||||
tenantService: ReturnType<typeof createTenantServiceStub> = createTenantServiceStub()
|
||||
tenantService: ReturnType<typeof createTenantServiceStub> = createTenantServiceStub(),
|
||||
authService: ReturnType<typeof createAuthServiceStub> = createAuthServiceStub()
|
||||
) {
|
||||
TestBed.resetTestingModule();
|
||||
|
||||
@@ -67,6 +92,10 @@ async function renderAppAt(
|
||||
cart: signal(null).asReadonly(),
|
||||
loadCart: () => of({ id: 1, items: [], subtotal: '0' })
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: authService
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
@@ -112,33 +141,41 @@ describe('app routes', () => {
|
||||
|
||||
expect(router.url).toBe('/login');
|
||||
expect(compiled.querySelector('app-login-page')).not.toBeNull();
|
||||
expect(compiled.textContent).toContain('Iniciar sesión');
|
||||
expect(compiled.textContent).toContain('Iniciar sesion');
|
||||
expect(compiled.textContent).toContain('Crear cuenta');
|
||||
});
|
||||
|
||||
it('loads the register page at /register and renders the shared auth fields', async () => {
|
||||
const { fixture, router } = await renderAppAt('/register');
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
const placeholders = Array.from(compiled.querySelectorAll('input')).map((input) =>
|
||||
const registerPage = compiled.querySelector('app-register-page');
|
||||
const placeholders = Array.from(registerPage?.querySelectorAll('input') ?? []).map((input) =>
|
||||
input.getAttribute('placeholder')
|
||||
);
|
||||
|
||||
expect(router.url).toBe('/register');
|
||||
expect(compiled.querySelector('app-register-page')).not.toBeNull();
|
||||
expect(registerPage).not.toBeNull();
|
||||
expect(compiled.textContent).toContain('CREAR CUENTA');
|
||||
expect(compiled.textContent).toContain('Volver');
|
||||
expect(placeholders).toEqual([
|
||||
'Nombre y Apellido',
|
||||
'Email',
|
||||
'Contraseña',
|
||||
'Repetir Contraseña'
|
||||
'Contrasena',
|
||||
'Repetir Contrasena'
|
||||
]);
|
||||
});
|
||||
|
||||
it('redirects authenticated users away from /login', async () => {
|
||||
const { router } = await renderAppAt('/login', createTenantServiceStub(), createAuthServiceStub(true));
|
||||
|
||||
expect(router.url).toBe('/');
|
||||
});
|
||||
|
||||
it('renders the tenant not found screen for any route when the tenant is missing', async () => {
|
||||
const { fixture } = await renderAppAt(
|
||||
'/componentes-test/reutilizables',
|
||||
createTenantServiceStub('not-found', null)
|
||||
createTenantServiceStub('not-found', null),
|
||||
createAuthServiceStub()
|
||||
);
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user