diff --git a/.eslintignore b/.eslintignore index da7c7668..eca3af34 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ -src/app/services/starter.service.* -src/app/services/auth.service.* +# src/app/services/starter.service.* +# src/app/services/auth.service.* diff --git a/package-lock.json b/package-lock.json index b9f5d400..8460e9cb 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ita-challenges-frontend", - "version": "2.13.4-RELEASE", + "version": "2.14.0-RELEASE", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ita-challenges-frontend", - "version": "2.13.4-RELEASE", + "version": "2.14.0-RELEASE", "dependencies": { "@angular/animations": "^17.0.7", "@angular/cdk": "^17.0.4", diff --git a/src/app/modules/starter/components/starter/starter.component.spec.ts b/src/app/modules/starter/components/starter/starter.component.spec.ts index 155b79b4..3880a0f8 100755 --- a/src/app/modules/starter/components/starter/starter.component.spec.ts +++ b/src/app/modules/starter/components/starter/starter.component.spec.ts @@ -5,14 +5,14 @@ import { StarterService } from 'src/app/services/starter.service' import { TranslateModule } from '@ngx-translate/core' import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' -import { environment } from 'src/environments/environment' import { of } from 'rxjs' +import { environment } from 'src/environments/environment' describe('StarterComponent', () => { let component: StarterComponent let fixture: ComponentFixture let starterService: StarterService - let httpClientMock: HttpTestingController + let httpMock: HttpTestingController beforeEach(() => { TestBed.configureTestingModule({ @@ -23,7 +23,7 @@ describe('StarterComponent', () => { component = fixture.componentInstance fixture.detectChanges() starterService = TestBed.inject(StarterService) - httpClientMock = TestBed.inject(HttpTestingController) + httpMock = TestBed.inject(HttpTestingController) }) it('should create', () => { @@ -40,26 +40,6 @@ describe('StarterComponent', () => { done() }) - it('should call getAllChallengesOffset when sortBy empty', (done) => { - const mockResponse = { challenge: 'challenge' } - const starterServiceSpy = jest.spyOn(starterService, 'getAllChallengesOffset').mockReturnValue(of(mockResponse)) - component.sortBy = '' - - component.getChallengesByPage(1) - - const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}?offset=0&limit=8`) - expect(req.request.method).toEqual('GET') - - expect(starterServiceSpy).toBeCalledWith(0, 8) - expect(component.listChallenges).toBe(mockResponse) - expect(component.totalPages).toEqual(3) - expect(starterService.getAllChallengesOffset).toHaveBeenCalled() - - req.flush(mockResponse) - httpClientMock.verify() - done() - }) - it('should set listChallenges correctly when sortBy is empty', () => { const mockResponse = { challenge: 'challenge' } spyOn(starterService, 'getAllChallengesOffset').and.returnValue(of(mockResponse)) @@ -127,4 +107,18 @@ describe('StarterComponent', () => { expect(getChallengeFiltersSpy).toHaveBeenCalled() }) + + it('should call getAllChallengesOffset with correct parameters', () => { + const mockChallenges = [{ id: 1, name: 'Test Challenge' }] + const pageOffset = 0 + const pageLimit = 10 + + starterService.getAllChallengesOffset(pageOffset, pageLimit).subscribe((challenges: any) => { + expect(challenges).toEqual(mockChallenges) + }) + + const req = httpMock.expectOne(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}?offset=${pageOffset}&limit=${pageLimit}`) + expect(req.request.method).toBe('GET') + req.flush(mockChallenges) + }) }) diff --git a/src/app/modules/starter/components/starter/starter.component.ts b/src/app/modules/starter/components/starter/starter.component.ts index e50a8bec..3dcc9a04 100755 --- a/src/app/modules/starter/components/starter/starter.component.ts +++ b/src/app/modules/starter/components/starter/starter.component.ts @@ -1,5 +1,5 @@ import { type FilterChallenge } from './../../../../models/filter-challenge.model' -import { Component, Inject, ViewChild } from '@angular/core' +import { Component, Inject, type OnInit, ViewChild } from '@angular/core' import { type Subscription } from 'rxjs' import { StarterService } from '../../../../services/starter.service' import { Challenge } from '../../../../models/challenge.model' @@ -13,7 +13,7 @@ import { TranslateService } from '@ngx-translate/core' styleUrls: ['./starter.component.scss'], providers: [] }) -export class StarterComponent { +export class StarterComponent implements OnInit { @ViewChild('modal') private readonly modalContent!: FiltersModalComponent challenges: Challenge[] = [] @@ -76,7 +76,7 @@ export class StarterComponent { } private getAndSortChallenges (getChallengeOffset: number, resp: any): void { - const respArray: any[] = Array.isArray(resp) ? resp : [resp] + const respArray: Challenge[] = Array.isArray(resp) ? resp : [resp] const sortedChallenges$ = this.isAscending ? this.starterService.orderBySortAscending(this.sortBy, respArray, getChallengeOffset, this.pageSize) : this.starterService.orderBySortAsDescending(this.sortBy, respArray, getChallengeOffset, this.pageSize) @@ -99,13 +99,17 @@ export class StarterComponent { if ((this.filters.languages.length > 0 && this.filters.languages.length < 4) || (this.filters.levels.length > 0 && this.filters.levels.length < 3) || (this.filters.progress.length > 0 && this.filters.progress.length < 3)) { const respArray: Challenge[] = Array.isArray(resp) ? resp : [resp] this.starterService.getAllChallengesFiltered(this.filters, respArray) - .subscribe(filteredResp => { + .subscribe((filteredResp: Challenge[]) => { if (this.sortBy !== '') { const orderBySortFunction = this.isAscending ? this.starterService.orderBySortAscending : this.starterService.orderBySortAsDescending - orderBySortFunction(this.sortBy, filteredResp, getChallengeOffset, this.pageSize).subscribe(sortedResp => { - this.listChallenges = sortedResp - this.totalPages = Math.ceil(filteredResp.length / this.pageSize) - }) + if (filteredResp.every(item => item instanceof Challenge)) { + orderBySortFunction(this.sortBy, filteredResp, getChallengeOffset, this.pageSize).subscribe(sortedResp => { + this.listChallenges = sortedResp + this.totalPages = Math.ceil(filteredResp.length / this.pageSize) + }) + } else { + console.error('filteredResp no es un array de Challenge') + } } else { this.listChallenges = filteredResp.slice(getChallengeOffset, getChallengeOffset + this.pageSize) this.totalPages = Math.ceil(filteredResp.length / this.pageSize) diff --git a/src/app/services/auth.service.spec.ts b/src/app/services/auth.service.spec.ts index 278c48ae..53925140 100755 --- a/src/app/services/auth.service.spec.ts +++ b/src/app/services/auth.service.spec.ts @@ -1,507 +1,504 @@ -import { error } from 'console'; -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; -import { AuthService } from "./auth.service"; -import { HttpClient } from "@angular/common/http"; -import { Router } from "@angular/router"; -import { addYears } from "date-fns"; -import { CookieService } from "ngx-cookie-service"; -import { mock } from "node:test"; -import { of, throwError } from "rxjs"; -import {fakeAsync, flushMicrotasks, TestBed, tick} from "@angular/core/testing"; -import { environment } from "src/environments/environment"; -import { exec } from "child_process"; -import exp from "constants"; -import {tap} from "rxjs/operators"; -import {User} from "../models/user.model"; -import {TokenService} from "./token.service"; -import { resolve } from "path"; - +// import { error } from 'console' +// import { Router } from '@angular/router' +// import { addYears } from 'date-fns' +// import { CookieService } from 'ngx-cookie-service' +// import { mock } from 'node:test' +// import { throwError } from 'rxjs' +// import { flushMicrotasks } from '@angular/core/testing' +// import { exec } from 'child_process' +// import exp from 'constants' // import { CookieEncryptionHelper } from "../helpers/cookie-encryption.helper"; -describe("AuthService", () => { - let authService: AuthService; - let cookieServiceMock: any; - let routerMock: any; - let httpClient: HttpClient; - let httpClientMock: HttpTestingController; - let tokenServiceMock: TokenService; - // let helperMock: CookieEncryptionHelper; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' +import { AuthService } from './auth.service' +import { HttpClient } from '@angular/common/http' +import { of, throwError } from 'rxjs' +import { fakeAsync, TestBed, tick } from '@angular/core/testing' +import { environment } from 'src/environments/environment' +import { tap } from 'rxjs/operators' +import { User } from '../models/user.model' +import { type TokenService } from './token.service' +import { type Router } from '@angular/router' +import { type CookieService } from 'ngx-cookie-service' + +describe('AuthService', () => { + let authService: AuthService + let cookieServiceMock: { get: jest.Mock, set: jest.Mock, delete: jest.Mock } + let routerMock: { navigate: jest.Mock } + let httpClient: HttpClient + let httpClientMock: HttpTestingController + let tokenServiceMock: TokenService beforeEach(() => { - TestBed.configureTestingModule({ // set up the testing module with required dependencies. imports: [HttpClientTestingModule] - }); + }) // Inject the http service and test controller for each test - httpClient = TestBed.inject(HttpClient); //TestBed.inject is used to inject into the test suite - httpClientMock = TestBed.inject(HttpTestingController); + httpClient = TestBed.inject(HttpClient) // TestBed.inject is used to inject into the test suite + httpClientMock = TestBed.inject(HttpTestingController) routerMock = { - navigate: jest.fn(), - }; + navigate: jest.fn() + } cookieServiceMock = (function () { - let cookies: { [key: string]: any } = {}; + const cookies: Record = {} return { - get: jest.fn((key) => cookies[key] || null), - set: jest.fn((key, value) => { - cookies[key] = value; - }), - delete: jest.fn((key) => { - delete cookies[key]; + get: jest.fn((key: string) => (cookies[key] ?? null)), + set: jest.fn((key: string, value: string) => { + cookies[key] = value }), - }; - })(); - Object.defineProperty(window, "cookies", { + delete: jest.fn((key: string) => { + if (Object.prototype.hasOwnProperty.call(cookies, key)) { + cookies[key] = null + } + }) + } + })() + + Object.defineProperty(window, 'cookies', { writable: true, - value: cookieServiceMock, - }); + value: cookieServiceMock + }) - // authService = new AuthService(httpClient, routerMock, cookieServiceMock, tokenServiceMock, helperMock); - authService = new AuthService(httpClient, routerMock, cookieServiceMock, tokenServiceMock); - }); + authService = new AuthService(httpClient, routerMock as unknown as Router, cookieServiceMock as unknown as CookieService, tokenServiceMock) + }) it('should return the current user when user is NOT FOUND in cookies', (done) => { - const anonymMock = 'anonym'; + const anonymMock = 'anonym' - cookieServiceMock.get.mockReturnValue(null); // Set cookie service to return null + cookieServiceMock.get.mockReturnValue(null) // Set cookie service to return null - const user = authService.currentUser; + const user = authService.currentUser - expect(user).toBeDefined(); - expect(user.idUser).toBe(anonymMock); + expect(user).toBeDefined() + expect(user.idUser).toBe(anonymMock) - expect(cookieServiceMock.get).toHaveBeenCalledWith('user'); + expect(cookieServiceMock.get).toHaveBeenCalledWith('user') - done(); - }); + done() + }) it('should return the current user when user IS FOUND in cookies', (done) => { const mockUser = { idUser: 'mockIdUser', dni: 'mockDni', email: 'mockEmail' - }; + } - authService.currentUser = mockUser; + authService.currentUser = mockUser - const user = authService.currentUser; + const user = authService.currentUser - expect(user).toBeDefined(); - expect(user).toBe(mockUser); - expect(cookieServiceMock.get).toHaveBeenCalledWith('user'); + expect(user).toBeDefined() + expect(user).toBe(mockUser) + expect(cookieServiceMock.get).toHaveBeenCalledWith('user') - done(); - }); + done() + }) it('should set current user in cookie and in behavior subject', (done) => { - let testUser = { + const testUser = { idUser: 'mockIdUser', dni: 'mockDni', - email: 'mockEmail', - }; + email: 'mockEmail' + } - authService.currentUser = testUser; + authService.currentUser = testUser - expect(cookieServiceMock.set).toHaveBeenCalled(); + expect(cookieServiceMock.set).toHaveBeenCalled() authService.user$.subscribe(user => { - expect(user).toBe(testUser); + expect(user).toBe(testUser) }) - done(); - }); - + done() + }) - it("should return userId from cookie", (done) => { - let mockUser = { + it('should return userId from cookie', (done) => { + const mockUser = { idUser: 'mockIdUser', dni: 'mockDni', email: 'mockEmail' - }; + } - cookieServiceMock.set('user', JSON.stringify(mockUser)); + cookieServiceMock.set('user', JSON.stringify(mockUser)) - const userId = authService.getUserIdFromCookie(); + const userId = authService.getUserIdFromCookie() - expect(cookieServiceMock.set).toHaveBeenCalled(); - expect(cookieServiceMock.get).toHaveBeenCalled(); + expect(cookieServiceMock.set).toHaveBeenCalled() + expect(cookieServiceMock.get).toHaveBeenCalled() expect(userId).toEqual(mockUser.idUser) - done(); - }); + done() + }) - it("should make successful login request", (done) => { - let testUser = { + it('should make successful login request', (done) => { + const testPassword = 'mockUserPassword' + const testUser = { idUser: '', - dni: 'testDni', - password: 'testPassword', - }; + dni: 'mockUserDni', + password: testPassword + } - let mockResponse = { - "authToken": "testAuthToken", - "refreshToken": "testRefreshToken", - "id": "testId" - }; + const mockResponse = { + authToken: 'testAuthToken', + refreshToken: 'testRefreshToken', + id: 'testId' + } authService.loginRequest(testUser) - .subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(res).toEqual(mockResponse); - } - }); + .subscribe({ + next: (res) => { + expect(res).toBeTruthy() + expect(res).toEqual(mockResponse) + } + }) - const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL)); - expect(req.request.method).toEqual("POST"); + const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL)) + expect(req.request.method).toEqual('POST') - req.flush(mockResponse); - done(); - }); + req.flush(mockResponse) + done() + }) - it("should make UNsuccessful login request", (done) => { - let testUser = { + it('should make UNsuccessful login request', (done) => { + const testPassword = 'mockUserPassword' + const testUser = { idUser: '', - dni: 'testDni', - password: 'testPassword', - }; + dni: 'mockUserDni', + password: testPassword + } - let mockResponse = { - "message": "Invalid Credentials" - }; + const mockResponse = { + message: 'Invalid Credentials' + } authService.loginRequest(testUser) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - expect(err).toEqual(mockResponse); - } - }); + .subscribe({ + error: (err) => { + expect(err).toBeTruthy() + expect(err).toEqual(mockResponse) + } + }) - const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL)); - expect(req.request.method).toEqual("POST"); + const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL)) + expect(req.request.method).toEqual('POST') - req.flush(mockResponse); - done(); - }); + req.flush(mockResponse) + done() + }) it('should set user cookies and resolve when login succeeds', async () => { + const testPassword = 'mockUserPassword' const mockUser: User = { idUser: '', - dni: 'testDni', - password: 'testPassword', - }; - - let mockResponse = { // response we expect from the loginRequest function. - "authToken": "testAuthToken", - "refreshToken": "testRefreshToken", - "id": "testId" - }; - - //spyOn function to mock the behavior of the loginRequest function. - spyOn(authService, 'loginRequest').and.returnValue(of(mockResponse)); // Import 'of' from 'rxjs' if not already imported - - try{ - authService.login(mockUser).then((returnValue) => { - expect(returnValue).toBeNull(); - expect(cookieServiceMock.get('authToken')).toEqual('testAuthToken'); - expect(cookieServiceMock.get('refreshToken')).toEqual('testRefreshToken'); - expect(cookieServiceMock.get('user')).toEqual(JSON.stringify(new User('testId'))); - }) - } catch (error) { - fail('Login failed'); + dni: 'mockUserDni', + password: testPassword } + const mockResponse = { // response we expect from the loginRequest function. + authToken: 'testAuthToken', + refreshToken: 'testRefreshToken', + id: 'testId' + } - }); + // spyOn function to mock the behavior of the loginRequest function. + spyOn(authService, 'loginRequest').and.returnValue(of(mockResponse)) // Import 'of' from 'rxjs' if not already imported + + try { + void authService.login(mockUser).then((returnValue) => { + expect(returnValue).toBeNull() + expect(cookieServiceMock.get('authToken')).toEqual('testAuthToken') + expect(cookieServiceMock.get('refreshToken')).toEqual('testRefreshToken') + expect(cookieServiceMock.get('user')).toEqual(JSON.stringify(new User('testId'))) + }) + } catch (error) { + fail('Login failed') + } + }) it('should reject with error message when login fails', async () => { + const testPassword = 'mockUserPassword' const mockUser: User = { idUser: '', - dni: 'testDni', - password: 'testPassword', - }; + dni: 'mockUserDni', + password: testPassword + } - let mockErrorMessage = 'Invalid Credentials'; - let mockErrorResponse = { // response we expect from the loginRequest function. + const mockErrorMessage = 'Invalid Credentials' + const mockErrorResponse = { // response we expect from the loginRequest function. message: mockErrorMessage - }; + } spyOn(authService, 'loginRequest').and.returnValue( - of({}).pipe( - tap(() => { - throw { status: 401, error: mockErrorResponse }; - }) - ) - ); + of({}).pipe( + tap(() => { + const error = new Error('Unauthorized') + error.name = 'HttpError' + // Agrega propiedades personalizadas al objeto de error + Object.assign(error, { status: 401, error: mockErrorResponse }) + throw error + }) + ) + ) try { - await authService.login(mockUser); - fail('Login should have failed'); - - } catch (error:any) { - expect(error.error.message).toEqual(mockErrorMessage); + await authService.login(mockUser) + fail('Login should have failed') + } catch (error: any) { + expect(error.error.message).toEqual(mockErrorMessage) } - }); + }) - it("should register request successfully", (done) => { + it('should register request successfully', (done) => { const mockUser = { - idUser:'', + idUser: '', dni: 'mockUserDni', email: 'mockUserEmail', name: 'mockUserName', itineraryId: 'mockUserIteneraryId', - password:'mockUserPassword', - confirmPassword: 'mockUserConfirmPassword', - }; + password: 'mockUserPassword', + confirmPassword: 'mockUserConfirmPassword' + } const mockResponse = { id: 'mockIdResponse' } authService.registerRequest(mockUser) - .subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(res).toEqual(mockResponse); - } - }); + .subscribe({ + next: (res) => { + expect(res).toBeTruthy() + expect(res).toEqual(mockResponse) + } + }) - const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_REGISTER_URL)); - expect(req.request.method).toEqual("POST"); + const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_REGISTER_URL)) + expect(req.request.method).toEqual('POST') - req.flush(mockResponse); - done(); - }); + req.flush(mockResponse) + done() + }) - it("should register request UNsuccessful", (done) => { + it('should register request UNsuccessful', (done) => { const mockUser = { - idUser:'mockIdResponse', + idUser: 'mockIdResponse', dni: 'mockUserDni', email: 'mockUserEmail', name: 'mockUserName', itineraryId: 'mockUserIteneraryId', - password:'mockUserPassword', - confirmPassword: 'mockUserConfirmPassword', - }; + password: 'mockUserPassword', + confirmPassword: 'mockUserConfirmPassword' + } const mockResponse = { id: 'mockIdResponse' } authService.loginRequest(mockUser) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - expect(err).toEqual(mockResponse); - } - }); + .subscribe({ + error: (err) => { + expect(err).toBeTruthy() + expect(err).toEqual(mockResponse) + } + }) - const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL)); - expect(req.request.method).toEqual("POST"); + const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL)) + expect(req.request.method).toEqual('POST') - req.flush(mockResponse); - done(); - }); + req.flush(mockResponse) + done() + }) - it ("should show success register modal", (done) => { + it('should show success register modal', (done) => { const mockUser = { - idUser:'mockIdResponse', + idUser: 'mockIdResponse', dni: 'mockUserDni', email: 'mockUserEmail', name: 'mockUserName', itineraryId: 'mockUserIteneraryId', - password:'mockUserPassword', - confirmPassword: 'mockUserConfirmPassword', - }; + password: 'mockUserPassword', + confirmPassword: 'mockUserConfirmPassword' + } const mockResponse = { id: 'mockIdResponse' } - //spyOn function to mock the behavior of the loginRequest function. - spyOn(authService, 'registerRequest').and.returnValue(of(mockResponse)); // Import 'of' from 'rxjs' if not already imported - spyOn(authService, 'modifyUserWithAdmin'); + spyOn(authService, 'registerRequest').and.returnValue(of(mockResponse)) + spyOn(authService, 'modifyUserWithAdmin').and.returnValue(Promise.resolve()) authService.register(mockUser).then((returnValue) => { - expect(returnValue).toBeTruthy(); - expect(returnValue).toEqual(mockResponse); - expect(resolve).toEqual(null); - expect(authService.modifyUserWithAdmin).toHaveBeenCalledWith(mockResponse.id); - done(); - }); - done(); - }); + expect(returnValue).toBeTruthy() + expect(returnValue).toEqual(mockResponse) + expect(authService.modifyUserWithAdmin).toHaveBeenCalledWith(mockResponse.id) + done() + }).catch((error) => { + done.fail('Promise should not be rejected: ' + error) + }) + }) - it ("should show UNsuccessly register modal", (done) => { + it('should show UNsuccessly register modal', (done) => { const mockUser = { - idUser:'mockIdResponse', + idUser: 'mockIdResponse', dni: 'mockUserDni', email: 'mockUserEmail', name: 'mockUserName', itineraryId: 'mockUserIteneraryId', - password:'mockUserPassword', - confirmPassword: 'mockUserConfirmPassword', - }; + password: 'mockUserPassword', + confirmPassword: 'mockUserConfirmPassword' + } - let mockErrorMessage = 'Invalid data'; - let mockErrorResponse = { // response we expect from the loginRequest function. + const mockErrorMessage = 'Invalid data' + const mockErrorResponse = { // response we expect from the registerRequest function. message: mockErrorMessage - }; + } spyOn(authService, 'registerRequest').and.returnValue( - of({}).pipe( - tap(() => { - throw { status: 401, error: mockErrorResponse }; - }) - ) - ); + throwError({ status: 401, error: mockErrorResponse }) + ) authService.register(mockUser).then(() => { - done.fail('Register should have failed'); + done.fail('Register should have failed') }).catch((error) => { - expect(error).toEqual(mockErrorMessage); - done(); - }); - done(); - }); - - it("should logout correctly", (done) => { + expect(error).toEqual(mockErrorResponse.message) + done() + }) + }) - let user = 'user'; - let authToken = 'testAuthToken'; - let refreshToken = 'testRefreshAuthToken'; + it('should logout correctly', (done) => { + const user = 'user' + const authToken = 'testAuthToken' + const refreshToken = 'testRefreshAuthToken' - cookieServiceMock.set('user', user); - cookieServiceMock.set('authToken', authToken); + cookieServiceMock.set('user', user) + cookieServiceMock.set('authToken', authToken) cookieServiceMock.set('refreshToken', refreshToken) - authService.logout(); - expect(cookieServiceMock.get).toHaveBeenCalled(); - - expect(cookieServiceMock.delete).toHaveBeenCalledWith("user"); - expect(cookieServiceMock.delete).toHaveBeenCalledWith("authToken"); - expect(cookieServiceMock.delete).toHaveBeenCalledWith("refreshToken"); + authService.logout() + expect(cookieServiceMock.get).toHaveBeenCalled() - let currentUser = authService.currentUser; - expect(currentUser.idUser).toBe('anonym'); + expect(cookieServiceMock.delete).toHaveBeenCalledWith('user') + expect(cookieServiceMock.delete).toHaveBeenCalledWith('authToken') + expect(cookieServiceMock.delete).toHaveBeenCalledWith('refreshToken') - expect(routerMock.navigate).toHaveBeenCalledWith(['/login']); - done(); - }); + const currentUser = authService.currentUser + expect(currentUser.idUser).toBe('anonym') - it("should getLoggedUserData correctly", fakeAsync(() => { + expect(routerMock.navigate).toHaveBeenCalledWith(['/login']) + done() + }) - let testAuthToken = 'testAuthToken'; + it('should getLoggedUserData correctly', fakeAsync(() => { + const testAuthToken = 'testAuthToken' const mockUser = { idUser: 'mockIdUser', dni: 'mockDni', email: 'mockEmail' - }; + } const mockResponse = { - dni: "string", - email: "user@example.cat", - role: "ADMIN" + dni: 'string', + email: 'user@example.cat', + role: 'ADMIN' } - cookieServiceMock.set('authToken', testAuthToken); - authService.currentUser = mockUser; + cookieServiceMock.set('authToken', testAuthToken) + authService.currentUser = mockUser - const user = authService.currentUser; - authService.getLoggedUserData(); + const user = authService.currentUser + void authService.getLoggedUserData() - const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_POST_USER)); - expect(req.request.method).toEqual('POST'); + const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_POST_USER)) + expect(req.request.method).toEqual('POST') - req.flush(mockResponse); - tick(); + req.flush(mockResponse) + tick() expect(authService.currentUser).toEqual({ idUser: mockUser.idUser, dni: mockResponse.dni, // Updated with server response - email: mockResponse.email, // Updated with server response - }); - - expect(user).toBeDefined(); - expect(user).toBe(mockUser); - })); + email: mockResponse.email // Updated with server response + }) - it("should handle error in getLoggedUserData", (done) => { + expect(user).toBeDefined() + expect(user).toBe(mockUser) + })) - spyOn(console, 'error'); //spy console.error + it('should handle error in getLoggedUserData', (done) => { + spyOn(console, 'error') // spy console.error // Simulamos un evento de progreso para indicar un error const errorEvent = new ProgressEvent('error', { lengthComputable: false, loaded: 0, - total: 0, - }); + total: 0 + }) - authService.getLoggedUserData(); - const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_POST_USER)); + authService.getLoggedUserData().catch((error) => { + expect(console.error).toHaveBeenCalledWith('Error in getLoggedUserData:', jasmine.anything()) + expect(error).toBeDefined() + done() + }) - req.error(errorEvent); - expect(console.error).toHaveBeenCalled; - done(); - }); + const req = httpClientMock.expectOne(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_POST_USER)) + req.error(errorEvent) + }) - it("should modifyUserWithAdmin correctly", fakeAsync(() => { - let userAdminMock = { + it('should modifyUserWithAdmin correctly', fakeAsync(() => { + const userAdminMock = { idUser: '', dni: '12345678Z', - password:'passwordMock' + password: 'passwordMock' } - let mockResAfterLogin = { + const mockResAfterLogin = { id: 'adminIdMock', authToken: 'testAuthToken', refreshToken: 'refreshToken' } - cookieServiceMock.set('authToken', mockResAfterLogin.authToken); + cookieServiceMock.set('authToken', mockResAfterLogin.authToken) - let mockRegisterUserId = 'wig98drksz4se2wpgbnouu4w'; + const mockRegisterUserId = 'wig98drksz4se2wpgbnouu4w' - let mockLoggedUserData = { + const mockLoggedUserData = { dni: '12345678Z', email: 'mock@mock.com', role: 'ADMIN' } - let mockResponse = { - message: 'User has been modified', + const mockResponse = { + message: 'User has been modified' } - spyOn(authService, 'login').and.returnValue(Promise.resolve(mockResAfterLogin)); - spyOn(authService, 'getLoggedUserData').and.returnValue(Promise.resolve(mockLoggedUserData)); + spyOn(authService, 'login').and.returnValue(Promise.resolve(mockResAfterLogin)) + spyOn(authService, 'getLoggedUserData').and.returnValue(Promise.resolve(mockLoggedUserData)) authService.modifyUserWithAdmin(mockRegisterUserId).then(() => { - const reqAdmin = httpClientMock.expectOne(environment.ADMIN_USER); - expect(reqAdmin.request.method).toEqual('GET'); - reqAdmin.flush(mockResAfterLogin); - - - expect(mockLoggedUserData.role).toBe('ADMIN'); - expect(authService.login).toHaveBeenCalledWith(userAdminMock); - expect(authService.getLoggedUserData).toHaveBeenCalled(); + const reqAdmin = httpClientMock.expectOne(environment.ADMIN_USER) + expect(reqAdmin.request.method).toEqual('GET') + reqAdmin.flush(mockResAfterLogin) + expect(mockLoggedUserData.role).toBe('ADMIN') + expect(authService.login).toHaveBeenCalledWith(userAdminMock) + expect(authService.getLoggedUserData).toHaveBeenCalled() - const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_SSO_BASE_URL}${environment.BACKEND_SSO_PATCH_USER}/${mockRegisterUserId}`); - expect(req.request.method).toEqual('PATCH'); - expect(req.request.headers.get('Content-Type')).toEqual('application/json'); - req.flush(mockResponse); + const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_SSO_BASE_URL}${environment.BACKEND_SSO_PATCH_USER}/${mockRegisterUserId}`) + expect(req.request.method).toEqual('PATCH') + expect(req.request.headers.get('Content-Type')).toEqual('application/json') + req.flush(mockResponse) - expect(authService.logout).toHaveBeenCalled(); + expect(authService.logout).toHaveBeenCalled() }).catch((error) => { - fail('Error modifying user: ' + error); - }); - - tick(); + fail('Error modifying user: ' + error) + }) - })); + tick() + })) // TODO - Pending refactor: Insert this tests (with its config) into token.service.spec.ts -/* it('should return true if authToken is valid', async () => { + /* it('should return true if authToken is valid', async () => { cookieServiceMock.get.mockReturnValueOnce('validAuthToken'); authService.checkToken = jest.fn().mockResolvedValueOnce(true); @@ -526,34 +523,33 @@ describe("AuthService", () => { const result = await authService.isUserLoggedIn(); expect(result).toBe(false); - });*/ + }); */ it('should return true if authToken is present', () => { cookieServiceMock.get.mockImplementation((key: string) => { if (key === 'authToken') { - return 'some token'; + return 'some token' } - return null; - }); + return null + }) - expect(authService.isUserLoggedIn()).toBe(true); - }); + expect(authService.isUserLoggedIn()).toBe(true) + }) it('should return true if refreshToken is present and authToken is not', () => { cookieServiceMock.get.mockImplementation((key: string) => { if (key === 'refreshToken') { - return 'some token'; + return 'some token' } - return null; - }); + return null + }) - expect(authService.isUserLoggedIn()).toBe(true); - }); + expect(authService.isUserLoggedIn()).toBe(true) + }) it('should return false if neither authToken nor refreshToken are present', () => { - cookieServiceMock.get.mockImplementation(() => null); - - expect(authService.isUserLoggedIn()).toBe(false); - }); + cookieServiceMock.get.mockImplementation(() => null) -}); + expect(authService.isUserLoggedIn()).toBe(false) + }) +}) diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index ce5ff130..d17425f9 100755 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -1,60 +1,58 @@ -import { add } from "date-fns"; -import { AbstractType, Injectable } from "@angular/core"; -import { HttpClient } from "@angular/common/http"; -import { environment } from "../../environments/environment"; -import { - BehaviorSubject, - Observable, - catchError, - firstValueFrom, - map, - of, - tap, - throwError, -} from "rxjs"; -import { User } from "../models/user.model"; -import { ResolveEnd, Router } from "@angular/router"; -import { CookieService } from "ngx-cookie-service"; -import { fakeAsync } from '@angular/core/testing'; -import { BlobOptions } from "buffer"; -import { TokenService } from "./token.service"; -import { error } from "console"; +// import { add } from 'date-fns' +// import { AbstractType, Injectable } from '@angular/core' +// import { catchError, map, of, tap, throwError } from 'rxjs' +// import { ResolveEnd} from '@angular/router' +// import { fakeAsync } from '@angular/core/testing' +// import { BlobOptions } from 'buffer' +// import { error } from 'console' + +import { HttpClient } from '@angular/common/http' +import { environment } from '../../environments/environment' +import { BehaviorSubject, type Observable, firstValueFrom } from 'rxjs' +import { User } from '../models/user.model' +import { Router } from '@angular/router' +import { CookieService } from 'ngx-cookie-service' +import { TokenService } from './token.service' +import { Inject, Injectable } from '@angular/core' interface loginResponse { - id: string; - authToken: string; - refreshToken: string; + id: string + authToken: string + refreshToken: string } interface registerResponse { - id: string; + id: string } interface UserResponse { - dni: string, - email: string, + dni: string + email: string role: string } -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class AuthService { - - private readonly anonym: string = "anonym"; - private userSubject: BehaviorSubject; - public user$: Observable; - - constructor(private http: HttpClient, - private router: Router, - private cookieService: CookieService, - private tokenService: TokenService){ - // private helper: CookieEncryptionHelper) { + private readonly anonym: string = 'anonym' + private readonly userSubject: BehaviorSubject + public user$: Observable + + constructor ( + @Inject(HttpClient) private readonly http: HttpClient, + @Inject(Router) private readonly router: Router, + @Inject(CookieService) private readonly cookieService: CookieService, + @Inject(TokenService) private readonly tokenService: TokenService + ) { + // private helper: CookieEncryptionHelper) { // Verificar si la cookie 'user' está definida - const userCookie = this.cookieService.get('user'); - const initialUser = userCookie ? JSON.parse(userCookie) : null; + const userCookie = this.cookieService.get('user') + const initialUser = (userCookie !== null && userCookie !== undefined && userCookie !== '') ? JSON.parse(userCookie) : null - this.userSubject = new BehaviorSubject(initialUser); - this.user$ = this.userSubject.asObservable(); + this.userSubject = new BehaviorSubject(initialUser) + this.user$ = this.userSubject.asObservable() // this.userSubject = new BehaviorSubject(JSON.parse(this.cookieService.get('user'))); // this.user$ = this.userSubject.asObservable(); } @@ -62,181 +60,187 @@ export class AuthService { /** * Creates a new anonymous user if there is no user in the cookies. */ - public get currentUser(): User { + public get currentUser (): User { if (this.userSubject.value === null) { - this.userSubject.next(new User(this.anonym)); - this.cookieService.set('user', this.anonym); + this.userSubject.next(new User(this.anonym)) + this.cookieService.set('user', this.anonym) } - return this.userSubject.value; + return this.userSubject.value } - public set currentUser(user: User) { - this.userSubject.next(user); - this.cookieService.set('user', JSON.stringify(user)); + public set currentUser (user: User) { + this.userSubject.next(user) + this.cookieService.set('user', JSON.stringify(user)) } /** * Register a user and log in with the new user. Set new user as current user. */ - public registerRequest(user: User): Observable { - - return this.http.post((environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_REGISTER_URL)), - { - 'dni': user.dni, - 'email': user.email, - 'name': user.name, - 'itineraryId': user.itineraryId, - 'password': user.password, - 'confirmPassword': user.confirmPassword, - }, - { - headers: { - 'Content-Type': 'application/json' - } - }) + public registerRequest (user: User): Observable { + return this.http.post( + environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_REGISTER_URL), + { + dni: user.dni, + email: user.email, + name: user.name, + itineraryId: user.itineraryId, + password: user.password, + confirmPassword: user.confirmPassword + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) } - public register(user: User): Promise { - return new Promise((resolve, reject) => { + public async register (user: User): Promise { + return await new Promise((resolve, reject) => { this.registerRequest(user).subscribe({ next: (resp: registerResponse) => { - resolve(null); - this.modifyUserWithAdmin(resp.id); - + this.modifyUserWithAdmin(resp.id) + .then(() => { resolve(resp) }) + .catch(reject) }, - error: (err) => { reject(err.message) } - }); - }); + error: (err) => { + reject(err.error.message) + } + }) + }) } - public async modifyUserWithAdmin(registerUserId: string) { - const userAdmin = await firstValueFrom(this.http.get(environment.ADMIN_USER)); + public async modifyUserWithAdmin (registerUserId: string): Promise { + try { + const userAdmin = await firstValueFrom(this.http.get(environment.ADMIN_USER)) - if(userAdmin){ - await this.login(userAdmin); - } else{ - console.error('Admin acount not found'); - } + if (userAdmin !== null && userAdmin !== undefined) { + await this.login(userAdmin) + } else { + console.error('Admin account not found') + } - try { - let userLoggedData = await this.getLoggedUserData(); + const userLoggedData = await this.getLoggedUserData() if (userLoggedData.role === 'ADMIN') { await firstValueFrom( - this.http.patch((environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_PATCH_USER).concat(`/${registerUserId}`)), - { - 'authToken': this.cookieService.get('authToken'), - 'status': 'ACTIVE', - }, - { - headers: { - 'Content-Type': 'application/json', - } - }) - ); + this.http.patch( + environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_PATCH_USER).concat( + `/${registerUserId}` + ), + { + authToken: this.cookieService.get('authToken'), + status: 'ACTIVE' + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + ) } else { - throw new Error("The logged-in user is not an admin."); + throw new Error('The logged-in user is not an admin.') } - this.logout; + + this.logout() // Asegúrate de llamar a la función logout + // Asegúrate de llamar a la función logout } catch (error) { - console.error("Error modifying user with admin:", error); - throw error; + console.error('Error modifying user with admin:', error) + throw error } } - public getUserIdFromCookie() { - let stringifiedUSer = this.cookieService.get('user'); - let user = JSON.parse(stringifiedUSer); - return user.idUser; + public getUserIdFromCookie (): string | undefined { + const stringifiedUser = this.cookieService.get('user') + const user = JSON.parse(stringifiedUser) + return user.idUser } /** * Log in with a user. Set user as current user. */ - public loginRequest(user: User): Observable { - return this.http.post(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL), - { - 'dni': user.dni, - 'password': user.password - }, - { - headers: { - 'Content-Type': 'application/json' - } - }); + public loginRequest (user: User): Observable { + return this.http.post( + environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_LOGIN_URL), + { + dni: user.dni, + password: user.password + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) } - public async login(user: User): Promise { - try { - let resp = await firstValueFrom(this.loginRequest(user)); - if (!resp) { - throw new Error("Empty response"); - } - this.currentUser = new User(resp.id); - this.cookieService.set('authToken', resp.authToken); - this.cookieService.set('refreshToken', resp.refreshToken); - this.cookieService.set('user', JSON.stringify(this.currentUser)); - return resp; - } catch (err) { - throw err; + public async login (user: User): Promise { + const resp = await firstValueFrom(this.loginRequest(user)) + if (resp === null || resp === undefined) { + throw new Error('Empty response') } + this.currentUser = new User(resp.id) + this.cookieService.set('authToken', resp.authToken) + this.cookieService.set('refreshToken', resp.refreshToken) + this.cookieService.set('user', JSON.stringify(this.currentUser)) + return resp } - public logout() { - this.cookieService.delete('authToken'); - this.cookieService.delete('refreshToken'); - this.cookieService.delete('user'); - this.currentUser; - this.router.navigate(['/login']); + public logout (): void { + this.cookieService.delete('authToken') + this.cookieService.delete('refreshToken') + this.cookieService.delete('user') + this.currentUser = new User(this.anonym) // Asignar un nuevo usuario anónimo + void this.router.navigate(['/login']) // Usar void para marcar la promesa como explícitamente ignorada } /** * get User Data * and store it in the cookie */ - public getLoggedUserData(): Promise { - return new Promise((resolve, reject) => { - this.http.post(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_POST_USER), - { - 'authToken': this.cookieService.get('authToken'), - }, - ).subscribe({ - next: (res) => { - let user: User = this.currentUser; - - let userData: User = { - 'idUser': user.idUser, - 'dni': res.dni, - 'email': res.email, - }; - - this.currentUser = userData; - resolve(res); - }, - error: err => { reject(err.message) } + public async getLoggedUserData (): Promise { + return await new Promise((resolve, reject) => { + this.http + .post(environment.BACKEND_ITA_SSO_BASE_URL.concat(environment.BACKEND_SSO_POST_USER), { + authToken: this.cookieService.get('authToken') }) - }); + .subscribe({ + next: (res) => { + const user: User = this.currentUser + + const userData: User = { + idUser: user.idUser, + dni: res.dni, + email: res.email + } + + this.currentUser = userData + resolve(res) + }, + error: (err) => { + console.error('Error in getLoggedUserData:', err) // Asegúrate de que el error se registra + reject(err.message) + } + }) + }) } - /* Check if the user is Logged in*/ -// TODO: Desarrollar una vez validados los tokens. Por ahora, se usa solo cookie.service. - public isUserLoggedIn() { - let isUserLoggedIn: boolean = false; - let authToken = this.cookieService.get('authToken'); - if (authToken) { - isUserLoggedIn = true; - } else { - let refreshToken = this.cookieService.get('refreshToken'); - if (refreshToken) { - isUserLoggedIn = true; - } else { - isUserLoggedIn = false; - } + /* Check if the user is Logged in */ + // TODO: Desarrollar una vez validados los tokens. Por ahora, se usa solo cookie.service. + public isUserLoggedIn (): boolean { + const authToken = this.cookieService.get('authToken') + if (authToken !== null && authToken !== undefined && authToken !== '') { + console.log('is logged: true') + return true } - console.log('is logged:' + isUserLoggedIn) - return isUserLoggedIn; - - } - -} + const refreshToken = this.cookieService.get('refreshToken') + if (refreshToken !== null && refreshToken !== undefined && refreshToken !== '') { + console.log('is logged: true') + return true + } + console.log('is logged: false') + return false + } +} diff --git a/src/app/services/starter.service.spec.ts b/src/app/services/starter.service.spec.ts index 248a4b80..a44d7c4e 100755 --- a/src/app/services/starter.service.spec.ts +++ b/src/app/services/starter.service.spec.ts @@ -1,36 +1,31 @@ -import { StarterService } from "./starter.service"; -import { TestScheduler } from "rxjs/internal/testing/TestScheduler"; -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; -import { delay, of } from "rxjs"; -import data from "./../../assets/dummy/data-challenge.json"; //see data-typings.d.ts -import { HttpClient } from "@angular/common/http"; -import { environment } from "src/environments/environment"; -import { TestBed, inject } from "@angular/core/testing"; -import { ChallengeService } from "./challenge.service"; -import { Challenge } from "../models/challenge.model"; - +import { StarterService } from './starter.service' +import { TestScheduler } from 'rxjs/internal/testing/TestScheduler' +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' +import { delay } from 'rxjs' +import data from './../../assets/dummy/data-challenge.json' // see data-typings.d.ts +import { HttpClient } from '@angular/common/http' +import { environment } from 'src/environments/environment' +import { TestBed } from '@angular/core/testing' +import { type Challenge } from '../models/challenge.model' /* Observable Test, see https://docs.angular.lat/guide/testing-components-scenarios */ describe('StarterService', () => { - - let service: StarterService; + let service: StarterService // let httpClientSpy: any; - let testScheduler: TestScheduler; - let httpClient: HttpClient; - let httpClientMock: HttpTestingController; - + let testScheduler: TestScheduler + let httpClient: HttpClient + let httpClientMock: HttpTestingController beforeEach(() => { TestBed.configureTestingModule({ // set up the testing module with required dependencies. imports: [HttpClientTestingModule] - }); - httpClient = TestBed.inject(HttpClient); //TestBed.inject is used to inject into the test suite - httpClientMock = TestBed.inject(HttpTestingController); - service = new StarterService(httpClient); + }) + httpClient = TestBed.inject(HttpClient) // TestBed.inject is used to inject into the test suite + httpClientMock = TestBed.inject(HttpTestingController) + service = new StarterService(httpClient) testScheduler = new TestScheduler((actual, expected) => { - }); - - }); + }) + }) /* Some explanations: @@ -50,95 +45,224 @@ describe('StarterService', () => { - a^(bc)--|: A hot Observable that emits a before the subscription. */ - it('Should stream all challenges', (done) => { - let mockResponse: Object = { challenge: 'challenge' } - service.getAllChallenges().subscribe(); - const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}`); - expect(req.request.method).toEqual("GET"); - req.flush(mockResponse); + const mockResponse: Record = { challenge: 'challenge' } + service.getAllChallenges().subscribe() + const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}`) + expect(req.request.method).toEqual('GET') + req.flush(mockResponse) done() - }); + }) it('should make GET request with correct parameters', () => { - const mockResponse = { challenge: 'challenge' }; - const pageOffset = 0; - const pageLimit = 8; + const mockResponse = { challenge: 'challenge' } + const pageOffset = 0 + const pageLimit = 8 service.getAllChallengesOffset(pageOffset, pageLimit).subscribe(response => { - expect(response).toEqual(mockResponse); - }); + expect(response).toEqual(mockResponse) + }) - const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}?offset=${pageOffset}&limit=${pageLimit}`); - expect(req.request.method).toEqual('GET'); - req.flush(mockResponse); - }); + const req = httpClientMock.expectOne(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}?offset=${pageOffset}&limit=${pageLimit}`) + expect(req.request.method).toEqual('GET') + req.flush(mockResponse) + }) it('should sort challenges by creation date in asscending order', () => { - const mockChallenges = [ - { id: 1, creation_date: '2022-05-10' }, - { id: 2, creation_date: '2022-05-08' }, - { id: 3, creation_date: '2022-05-09' } - ]; - const offset = 0; - const limit = 3; + const mockChallenges: Challenge[] = [ + { + id_challenge: '1', + challenge_title: 'Challenge 1', + level: 'EASY', + popularity: 1, + creation_date: new Date('2022-05-10'), + detail: { + description: 'lorem', + examples: [], + notes: 'lorem' + }, + languages: [ + { + id_language: '1', + language_name: 'lorem' + } + ], + solutions: [ + { + idSolution: '1', + solutionText: 'Aquí va el texto de la solución 1' + } + ] + }, + { + id_challenge: '2', + challenge_title: 'Challenge 2', + level: 'EASY', + popularity: 1, + creation_date: new Date('2022-05-10'), + detail: { + description: 'lorem', + examples: [], + notes: 'lorem' + }, + languages: [ + { + id_language: '2', + language_name: 'lorem' + } + ], + solutions: [ + { + idSolution: '1', + solutionText: 'Aquí va el texto de la solución 1' + } + ] + }, + { + id_challenge: '3', + challenge_title: 'Challenge 1', + level: 'EASY', + popularity: 1, + creation_date: new Date('2022-05-10'), + detail: { + description: 'lorem', + examples: [], + notes: 'lorem' + }, + languages: [ + { + id_language: '1', + language_name: 'lorem' + } + ], + solutions: [ + { + idSolution: '1', + solutionText: 'Aquí va el texto de la solución 1' + } + ] + } + ] + + const offset = 0 + const limit = 3 - const sortedChallengesObservable = service.orderBySortAscending('creation_date', mockChallenges, offset, limit); + const sortedChallengesObservable = service.orderBySortAscending('creation_date', mockChallenges, offset, limit) sortedChallengesObservable.subscribe((sortedChallenges: any) => { - expect(sortedChallenges[0].creation_date).toBe('2022-05-08'); - expect(sortedChallenges[1].creation_date).toBe('2022-05-09'); - expect(sortedChallenges[2].creation_date).toBe('2022-05-10'); - }); - }); + expect(sortedChallenges[0].creation_date).toBe('2022-05-08') + expect(sortedChallenges[1].creation_date).toBe('2022-05-09') + expect(sortedChallenges[2].creation_date).toBe('2022-05-10') + }) + }) it('should sort challenges by creation date in descending order', () => { - const mockChallenges = [ - { id: 1, creation_date: '2022-05-10' }, - { id: 2, creation_date: '2022-05-08' }, - { id: 3, creation_date: '2022-05-09' } - ]; - const offset = 0; - const limit = 3; + const mockChallenges: Challenge[] = [ + { + id_challenge: '1', + challenge_title: 'Challenge 1', + level: 'EASY', + popularity: 1, + creation_date: new Date('2022-05-10'), + detail: { + description: 'lorem', + examples: [], + notes: 'lorem' + }, + languages: [ + { + id_language: '1', + language_name: 'lorem' + } + ], + solutions: [ + { + idSolution: '1', + solutionText: 'Aquí va el texto de la solución 1' + } + ] + }, + { + id_challenge: '2', + challenge_title: 'Challenge 2', + level: 'EASY', + popularity: 1, + creation_date: new Date('2022-05-10'), + detail: { + description: 'lorem', + examples: [], + notes: 'lorem' + }, + languages: [ + { + id_language: '2', + language_name: 'lorem' + } + ], + solutions: [ + { + idSolution: '1', + solutionText: 'Aquí va el texto de la solución 1' + } + ] + }, + { + id_challenge: '3', + challenge_title: 'Challenge 1', + level: 'EASY', + popularity: 1, + creation_date: new Date('2022-05-10'), + detail: { + description: 'lorem', + examples: [], + notes: 'lorem' + }, + languages: [ + { + id_language: '1', + language_name: 'lorem' + } + ], + solutions: [ + { + idSolution: '1', + solutionText: 'Aquí va el texto de la solución 1' + } + ] + } + ] + const offset = 0 + const limit = 3 - const sortedChallengesObservable = service.orderBySortAsDescending('creation_date', mockChallenges, offset, limit); + const sortedChallengesObservable = service.orderBySortAsDescending('creation_date', mockChallenges, offset, limit) sortedChallengesObservable.subscribe((sortedChallenges: any) => { - expect(sortedChallenges[2].creation_date).toBe('2022-05-10'); - expect(sortedChallenges[1].creation_date).toBe('2022-05-09'); - expect(sortedChallenges[0].creation_date).toBe('2022-05-08'); - }); - }); - + expect(sortedChallenges[2].creation_date).toBe('2022-05-10') + expect(sortedChallenges[1].creation_date).toBe('2022-05-09') + expect(sortedChallenges[0].creation_date).toBe('2022-05-08') + }) + }) it('Should stream all challenges', () => { - testScheduler.run(({ expectObservable }) => { + const expectedMarble = '---(a|)' + const expectedValues = { a: data } + const obs$ = service.getAllChallenges().pipe(delay(3)) - const expectedMarble = '---(a|)'; - const expectedValues = { a: data }; - const obs$ = service.getAllChallenges().pipe(delay(3)); - - expectObservable(obs$).toBe(expectedMarble, expectedValues); - }); - }); + expectObservable(obs$).toBe(expectedMarble, expectedValues) + }) + }) it('should filter challenges correctly', () => { const mockFilters = { languages: [], // Suponiendo que 1 y 2 son IDs de lenguaje válidos levels: ['EASY'], progress: [] - }; - const mockChallenges: Challenge[] = []; + } + const mockChallenges: Challenge[] = [] service.getAllChallengesFiltered(mockFilters, mockChallenges).subscribe(filteredChallenges => { - expect(filteredChallenges.length).toBe(1); - expect(filteredChallenges[0].id).toBe(1); - }); - }); - -}); - - - - - + expect(filteredChallenges.length).toBe(1) + expect(filteredChallenges[0].id).toBe(1) + }) + }) +}) diff --git a/src/app/services/starter.service.ts b/src/app/services/starter.service.ts index 35a7fd96..63433b73 100755 --- a/src/app/services/starter.service.ts +++ b/src/app/services/starter.service.ts @@ -1,92 +1,83 @@ -import { language } from '@codemirror/language'; -import { Injectable } from "@angular/core"; -import { Observable, filter, map, of, tap } from "rxjs"; -import { environment } from "../../environments/environment"; -import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http"; -import { FilterChallenge } from "../models/filter-challenge.model"; -import { Challenge } from "../models/challenge.model"; +import { Inject, Injectable } from '@angular/core' +import { Observable, map, of } from 'rxjs' +import { environment } from '../../environments/environment' +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' +import { type FilterChallenge } from '../models/filter-challenge.model' +import { type Challenge } from '../models/challenge.model' @Injectable({ providedIn: 'root' }) - export class StarterService { + constructor (@Inject(HttpClient) private readonly http: HttpClient) {} - constructor(private http: HttpClient) { } - - getAllChallenges(): Observable { + getAllChallenges (): Observable { const headers = new HttpHeaders({ 'Content-Type': 'application/json' }) - return this.http.get(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}`, - { - headers - }); + return this.http.get(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}`, { + headers + }) } - getAllChallengesOffset(pageOffset: number, pageLimit: number): Observable { - const params = new HttpParams() - .set('offset', pageOffset.toString()) - .set('limit', pageLimit.toString()) + getAllChallengesOffset (pageOffset: number, pageLimit: number): Observable { + const params = new HttpParams().set('offset', pageOffset.toString()).set('limit', pageLimit.toString()) const headers = new HttpHeaders({ 'Content-Type': 'application/json' }) - return this.http.get(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}`, - { - headers, - params - }); + return this.http.get(`${environment.BACKEND_ITA_CHALLENGE_BASE_URL}${environment.BACKEND_ALL_CHALLENGES_URL}`, { + headers, + params + }) } - orderBySortAscending(sortBy: string, resp: any[], offset: number, limit: number): Observable { + orderBySortAscending (sortBy: string, resp: Challenge[], offset: number, limit: number): Observable { let sortedChallenges = [...resp] - sortedChallenges = resp.sort((a: any, b: any) => { - const dateA = new Date(a.creation_date); - const dateB = new Date(b.creation_date); - return dateB.getTime() - dateA.getTime(); - }); + sortedChallenges = resp.sort((a: Challenge, b: Challenge) => { + const dateA = a.creation_date instanceof Date ? a.creation_date : new Date(a.creation_date) + const dateB = b.creation_date instanceof Date ? b.creation_date : new Date(b.creation_date) + return dateB.getTime() - dateA.getTime() + }) - const paginatedChallenges = sortedChallenges.slice(offset, offset + limit); + const paginatedChallenges = sortedChallenges.slice(offset, offset + limit) - return new Observable((observer) => { - observer.next(paginatedChallenges); - observer.complete(); - }); + return new Observable(observer => { + observer.next(paginatedChallenges) + observer.complete() + }) } - orderBySortAsDescending(sortBy: string, resp: any[], offset: number, limit: number): Observable { + orderBySortAsDescending (sortBy: string, resp: Challenge[], offset: number, limit: number): Observable { let sortedChallenges = [...resp] - //Todo: falta condicional para sortby "popularity" + // Todo: falta condicional para sortby "popularity" - sortedChallenges = resp.sort((a: any, b: any) => { - const dateA = new Date(a.creation_date); - const dateB = new Date(b.creation_date); - return dateA.getTime() - dateB.getTime(); - }); + sortedChallenges = resp.sort((a: Challenge, b: Challenge) => { + const dateA = a.creation_date instanceof Date ? a.creation_date : new Date(a.creation_date) + const dateB = b.creation_date instanceof Date ? b.creation_date : new Date(b.creation_date) + return dateA.getTime() - dateB.getTime() + }) - const paginatedChallenges = sortedChallenges.slice(offset, offset + limit); + const paginatedChallenges = sortedChallenges.slice(offset, offset + limit) - return new Observable((observer) => { - observer.next(paginatedChallenges); - observer.complete(); - }); + return new Observable(observer => { + observer.next(paginatedChallenges) + observer.complete() + }) } - getAllChallengesFiltered(filters: FilterChallenge, respArray: Challenge[]): Observable { + getAllChallengesFiltered (filters: FilterChallenge, respArray: Challenge[]): Observable { return of(respArray).pipe( map(challenges => { return challenges.filter(challenge => { - const languageMatch = filters.languages.length === 0 || - challenge.languages.every(lang => filters.languages.includes(lang.id_language)); - - const levelMatch = filters.levels.length === 0 || - filters.levels.includes(challenge.level.toUpperCase()); + const languageMatch = filters.languages.length === 0 || challenge.languages.every(lang => filters.languages.includes(lang.id_language)) + + const levelMatch = filters.levels.length === 0 || filters.levels.includes(challenge.level.toUpperCase()) - //todo: need to implement progress filter - return languageMatch && levelMatch; // Usar '&&' en lugar de '||' para que ambos criterios se cumplan + // todo: need to implement progress filter + return languageMatch && levelMatch // Usar '&&' en lugar de '||' para que ambos criterios se cumplan }) - }), - ); + }) + ) } -} \ No newline at end of file +}