diff --git a/.eslintrc.json b/.eslintrc.json index f2e18c4..2c1df9d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -66,6 +66,11 @@ "pattern": "rxjs/**", "group": "unknown" }, + { + "pattern": "@faker-js/**", + "group": "builtin", + "position": "before" + }, { "pattern": "@services/**", "group": "builtin", diff --git a/package-lock.json b/package-lock.json index a57862d..69330fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@angular-eslint/template-parser": "13.5.0", "@angular/cli": "~13.2.2", "@angular/compiler-cli": "~13.2.0", + "@faker-js/faker": "^7.6.0", "@types/jasmine": "~3.10.0", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.27.1", @@ -2704,6 +2705,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -17620,6 +17631,12 @@ } } }, + "@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "dev": true + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", diff --git a/package.json b/package.json index b24c150..c466449 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test", + "test:cov": "npm run test -- --no-watch --code-coverage --browsers=ChromeHeadlessCI", "lint": "ng lint", "lint:fix": "npx eslint src/**/*.{js,jsx,ts,tsx,html} --quiet --fix", "prepare": "npx husky install" @@ -35,6 +36,7 @@ "@angular-eslint/template-parser": "13.5.0", "@angular/cli": "~13.2.2", "@angular/compiler-cli": "~13.2.0", + "@faker-js/faker": "^7.6.0", "@types/jasmine": "~3.10.0", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.27.1", diff --git a/src/app/core/mocks/feed.mock.ts b/src/app/core/mocks/feed.mock.ts new file mode 100644 index 0000000..db06242 --- /dev/null +++ b/src/app/core/mocks/feed.mock.ts @@ -0,0 +1,25 @@ +import { faker } from '@faker-js/faker'; + +import { Feed } from '@models/feed.model'; + +import { getWebsitesMock } from './website.mock'; + +export const getFeedMock = (): Feed => ({ + _id: faker.datatype.string(), + writer: faker.datatype.string(), + title: faker.datatype.string(), + pubDate: faker.date.past(1), + content: faker.datatype.string(), + link: faker.datatype.string(), + image: faker.datatype.string(), + website: getWebsitesMock(), + inUser: faker.datatype.boolean(), +}); + +export const getFeedsMock = (size = 10): Feed[] => { + const feeds: Feed[] = []; + for (let i = 0; i < size; i++) { + feeds.push(getFeedMock()); + } + return feeds; +}; diff --git a/src/app/core/mocks/user.mock.ts b/src/app/core/mocks/user.mock.ts new file mode 100644 index 0000000..5276fc0 --- /dev/null +++ b/src/app/core/mocks/user.mock.ts @@ -0,0 +1,17 @@ +import { faker } from "@faker-js/faker"; + +import { User } from "@models/user.model"; + +export const getUserMock = (): User => ({ + email: faker.datatype.string(), + active: faker.datatype.boolean(), + google: faker.datatype.boolean(), + darkMode: faker.datatype.boolean(), + name: faker.datatype.string(), + lastName: faker.datatype.string(), + image: faker.datatype.string(), + password: faker.datatype.string(), + subscriptions: [faker.datatype.string(5)], + readFeeds: [faker.datatype.string(5)], + savedFeeds: [faker.datatype.string(5)], +}); diff --git a/src/app/core/mocks/website.mock.ts b/src/app/core/mocks/website.mock.ts new file mode 100644 index 0000000..f4e0fa6 --- /dev/null +++ b/src/app/core/mocks/website.mock.ts @@ -0,0 +1,13 @@ +import { faker } from "@faker-js/faker"; + +import { Website } from "@models/website.model"; + +export const getWebsitesMock = (): Website => ({ + name: faker.datatype.string(), + image: faker.datatype.string(), + description: faker.datatype.string(), + link: faker.datatype.string(), + linkFeed: faker.datatype.string(), + _id: faker.datatype.string(), + inUser: faker.datatype.boolean(), +}); diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index 595d8db..b9dcecb 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -46,7 +46,7 @@ export class AuthService { this.isAuthenticatedEmitter.emit({ isAuth, to }); } - get getUserActive(): User { + getUserActive(): User { return this.userActive as User; } diff --git a/src/app/core/services/feed.service.spec.ts b/src/app/core/services/feed.service.spec.ts new file mode 100644 index 0000000..4b06b82 --- /dev/null +++ b/src/app/core/services/feed.service.spec.ts @@ -0,0 +1,238 @@ +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import { TestBed } from "@angular/core/testing"; +import { getFeedMock, getFeedsMock } from '@mocks/feed.mock'; +import { getUserMock } from '@mocks/user.mock'; +import { environment } from 'environments/environment'; + +import { Feed } from '@models/feed.model'; +import { User } from '@models/user.model'; + +import { AuthService } from './auth.service'; +import { FeedService } from "./feed.service"; + +const { base_url } = environment; + +describe('Feed Service', () => { + let feedService: FeedService; + let httpController: HttpTestingController; + let spyAuthService: jasmine.SpyObj; + + beforeEach(() => { + const spyAuth = jasmine.createSpyObj( + 'AuthService', + ['getUserActive'], + { userActive: { email: 'example@test.com', savedFeeds: ['1234'] } + }); + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + FeedService, + { + provide: AuthService, + useValue: spyAuth, + }, + ], + }); + + feedService = TestBed.inject(FeedService); + httpController = TestBed.inject(HttpTestingController); + spyAuthService = TestBed.inject(AuthService) as jasmine.SpyObj; + }); + + afterEach(() => { + httpController.verify(); + }); + + it('should be created', () => { + expect(feedService).toBeTruthy(); + }); + + describe('get feeds function', () => { + it('should return feeds when user is not authenticated', (doneFn) => { + const mockFeeds: Feed[] = getFeedsMock(); + const options = { skip: 0, limit: 10 }; + + feedService.getFeeds(options.skip, options.limit).subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = `${base_url}/feed?skip=${options.skip}&limit=${options.limit}`; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + expect(req.request.headers.has('x-token')).toBe(false); + }); + + it('should return feeds when user is authenticated', (doneFn) => { + const mockFeeds: Feed[] = getFeedsMock(); + const options = { skip: 0, limit: 10 }; + + feedService.getFeeds(options.skip, options.limit, true).subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = base_url + '/feed/byUser/subscription?skip=' + options.skip + + '&limit=' + options.limit; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + }); + + it('should return by default 10 feeds from offset 0', (doneFn) => { + const options = { skip: 0, limit: 10 }; + const mockFeeds: Feed[] = getFeedsMock(options.limit); + + feedService.getFeeds().subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = `${base_url}/feed?skip=${options.skip}&limit=${options.limit}`; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + }); + }); + + describe('get feed by id function', () => { + it('should return feed by id = 1234', (doneFn) => { + const mockFeed: Feed = getFeedMock(); + mockFeed._id = '1234'; + + feedService.getFeed(mockFeed._id).subscribe((feed) => { + expect(feed).toEqual(mockFeed); + expect(feed._id).toBe(mockFeed._id); + doneFn(); + }); + + const url = `${base_url}/feed/${mockFeed._id}`; + const req = httpController.expectOne(url); + req.flush({ ok: true, feed: mockFeed }); + + expect(req.request.method).toBe('GET'); + }); + }); + + describe('saved feeds function', () => { + it('should return saved feeds', (doneFn) => { + const mockFeeds: Feed[] = getFeedsMock(); + const options = { skip: 0, limit: 10 }; + + feedService.getSavedFeeds().subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = base_url + '/feed/byUser/saved' + '?skip=' + + options.skip + '&limit=' + options.limit; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + }); + + it('should return saved feeds by default 10 feeds from offset 0', (doneFn) => { + const options = { skip: 0, limit: 10 }; + const mockFeeds: Feed[] = getFeedsMock(options.limit); + + feedService.getSavedFeeds().subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = base_url + '/feed/byUser/saved' + '?skip=' + + options.skip + '&limit=' + options.limit; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + }); + }); + + describe('search feeds function', () => { + it('should return feeds by search = "test"', (doneFn) => { + const options = { skip: 0, limit: 10 }; + const mockFeeds: Feed[] = getFeedsMock(options.limit); + const search = 'test'; + + feedService.searchFeeds(options.skip, options.limit, search).subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = base_url + '/feed/search' + '?skip=' + options.skip + '&limit=' + + options.limit + '&q=' + search; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + }); + + it('should return feeds by search = "test" by default ' + +'10 feeds from offset 0', (doneFn) => { + const options = { skip: 0, limit: 10 }; + const mockFeeds: Feed[] = getFeedsMock(options.limit); + const search = 'test'; + + feedService.searchFeeds(undefined, undefined, search).subscribe((feeds) => { + expect(feeds).toEqual(mockFeeds); + expect(feeds.length).toBe(mockFeeds.length); + doneFn(); + }); + + const url = base_url + '/feed/search' + '?skip=' + options.skip + '&limit=' + + options.limit + '&q=' + search; + const req = httpController.expectOne(url); + req.flush({ ok: true, feeds: mockFeeds }); + + expect(req.request.method).toBe('GET'); + }); + }); + + describe('map in user resource function', () => { + it('should return feeds without changes if user is not authenticated', () => { + const mockFeeds: Feed[] = getFeedsMock(); + const feeds = feedService.mapInUserResource(mockFeeds); + + expect(feeds).toEqual(mockFeeds); + }); + + it('should return feeds with user resource if user is authenticated', () => { + const mockFeeds: Feed[] = getFeedsMock(2); + const mockUser: User = getUserMock(); + + spyAuthService.getUserActive.and.returnValue(mockUser); + + const feeds = feedService.mapInUserResource(mockFeeds); + + expect(feeds).toEqual(mockFeeds); + }); + + it('should return feed with user resource if user is authenticated', () => { + const mockFeed: Feed = getFeedMock(); + const mockUser: User = getUserMock(); + + spyAuthService.getUserActive.and.returnValue(mockUser); + + const feeds = feedService.mapInUserResource(mockFeed); + + expect(feeds).toEqual(mockFeed); + }); + }); +}); diff --git a/src/app/core/services/feed.service.ts b/src/app/core/services/feed.service.ts index e39bd3b..641fb60 100644 --- a/src/app/core/services/feed.service.ts +++ b/src/app/core/services/feed.service.ts @@ -65,8 +65,8 @@ export class FeedService { return this.http.get(url).pipe(map(resp => resp.feeds)); } - private mapInUserResource(feeds: Feed[] | Feed): Feed[] | Feed { - const userActive = this.authService.getUserActive; + public mapInUserResource(feeds: Feed[] | Feed): Feed[] | Feed { + const userActive = this.authService.getUserActive(); if(userActive) { const { savedFeeds } = userActive; if(Array.isArray(feeds)) { diff --git a/tsconfig.json b/tsconfig.json index af559cf..a7a805e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,6 +32,7 @@ "@interceptors/*": [ "app/core/interceptors/*" ], "@guards/*": [ "app/core/guards/*" ], "@constants/*": [ "app/core/constants/*" ], + "@mocks/*": [ "app/core/mocks/*" ], } }, "angularCompilerOptions": {