diff --git a/src/core/interceptors/page.interceptor.ts b/src/core/interceptors/page.interceptor.ts index 1c9e0fd..934f058 100644 --- a/src/core/interceptors/page.interceptor.ts +++ b/src/core/interceptors/page.interceptor.ts @@ -1,8 +1,7 @@ import { HttpService } from "@nestjs/axios"; import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; import { Observable, map, take } from "rxjs"; -import { EsResponseDto, PageDto } from "../domain/dtos"; -import { EsHitDto } from "../domain/dtos/es-hit.dto"; +import { PageDto } from "../domain/dtos"; import { EsQueryDto } from "../domain/dtos/es-query.dto"; import { RequestDto } from "../domain/dtos/request.dto"; import { SearchQueryDto } from "../domain/dtos/search-q.dto"; @@ -10,12 +9,11 @@ import { EsTime } from "../domain/enums/es-time.enum"; import { Order } from "../domain/enums/page-order.enum"; import { PageMeta } from "../domain/interfaces"; import { EsPit } from "../domain/interfaces/es-pit.interface"; -import { SearchInfo } from "../domain/interfaces/search-info.interface"; /** * Previous search data storage */ -class PrevSearch implements SearchInfo { +class PrevSearch { /** * Constructs an uninitialized object */ @@ -28,17 +26,35 @@ class PrevSearch implements SearchInfo { /** * PIT object of the previous search */ - pit: EsPit; + private pit: EsPit; + set _pit(pit: EsPit) { + this.pit = pit; + } + get _pit(): EsPit { + return this.pit; + } /** * Tiebreaker and sort parameters */ - tiebreaker: unknown[]; + private tiebreaker: unknown[]; + set _tiebreaker(tiebreaker: unknown[]) { + this.tiebreaker = tiebreaker; + } + get _tiebreaker(): unknown[] { + return this.tiebreaker; + } /** * Number of the previous page */ - prevPage: number; + private prevPage: number; + set _prevPage(page: number) { + this.prevPage = page; + } + get _prevPage(): number { + return this.prevPage; + } /** * Checks if there was the search before current one @@ -89,23 +105,23 @@ export class PageInterceptor implements NestInterceptor { ]; if (this.prevSearch.isSet()) { - request.es_query.pit = this.prevSearch.pit; - request.es_query.search_after = this.prevSearch.tiebreaker; + request.es_query.pit = this.prevSearch._pit; + request.es_query.search_after = this.prevSearch._tiebreaker; let limit = !query?.limit ? 10 : query.limit; - request.es_query.size = limit * Math.abs(query.page - this.prevSearch.prevPage); + request.es_query.size = limit * Math.abs(query.page - this.prevSearch._prevPage); - if (query.page < this.prevSearch.prevPage) { + if (query.page < this.prevSearch._prevPage) { request.es_query.sort = [{ _score: { order: 'asc' } }]; request.es_query.size += limit - 1; reverse = true; - } else if (query.page == this.prevSearch.prevPage) { + } else if (query.page == this.prevSearch._prevPage) { // Caching should be HERE request.es_query.sort = [{ _score: { order: 'asc' } }]; reverse = true; } } else { - this.prevSearch.pit = request.es_query.pit = await this.getPIT(1); + this.prevSearch._pit = request.es_query.pit = await this.getPIT(1); let limit = !query?.limit ? 10 : query.limit; request.es_query.size = limit * query.page; @@ -116,24 +132,24 @@ export class PageInterceptor implements NestInterceptor { // Setting the page meta-data let meta: PageMeta = { total: res.hits.total.value, - pagenum: !query?.page ? 1 : query.page, + pagenum: !query?.page ? 1 : +query.page, order: query?.order?.toUpperCase() === Order.ASC ? Order.ASC : Order.DESC, + pagesize: !query?.limit ? 10 : query.limit, hasNext: undefined, hasPrev: undefined, - pagesize: !query?.limit ? 10 : query.limit, }; meta.hasNext = meta.pagenum * meta.pagesize < meta.total ? true : false; meta.hasPrev = meta.pagenum != 1 ? true : false; // Saving the search info - this.prevSearch.pit.id = res.pit_id; - this.prevSearch.tiebreaker = res.hits.hits[res.hits.hits.length - 1]?.sort; - this.prevSearch.prevPage = query.page; + this.prevSearch._pit.id = res.pit_id; + this.prevSearch._tiebreaker = res.hits.hits[res.hits.hits.length - 1]?.sort; + this.prevSearch._prevPage = query.page; - // Check if the performed search is a backward search + // Check if the performed search is a backwards search let data = res.hits.hits.slice(-meta.pagesize); if (reverse) { - this.prevSearch.tiebreaker = data[0]?.sort; + this.prevSearch._tiebreaker = data[0]?.sort; data.reverse(); reverse = false; } @@ -165,12 +181,12 @@ export class PageInterceptor implements NestInterceptor { public async getPIT(alive: number, unit: EsTime = EsTime.min): Promise { return new Promise((resolve, reject) => { try { - (this.httpService.post(`http://localhost:${this.ES_PORT}/papers/_pit?keep_alive=${alive+unit}`) + this.httpService.post(`http://localhost:${this.ES_PORT}/papers/_pit?keep_alive=${alive+unit}`) .pipe(take(1), map(axiosRes => axiosRes.data)) - .subscribe((res) => { + .subscribe((res: EsPit) => { res.keep_alive = alive + unit; resolve(res); - })); + }); } catch (error) { reject(error); } diff --git a/src/test/e2e/papers.endpoint.spec.ts b/src/test/e2e/papers.endpoint.spec.ts new file mode 100644 index 0000000..1fa3122 --- /dev/null +++ b/src/test/e2e/papers.endpoint.spec.ts @@ -0,0 +1,82 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication } from "@nestjs/common"; +import request from 'supertest' +import { AppModule } from "src/infrastructure/modules"; + +describe('E2E Testing of /papers', () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleRef: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleRef.createNestApplication(); + await app.init(); + }); + + it('GET /papers/{uuid} | Should return one exact item on page', async () => { + const test = await request(app.getHttpServer()) + .get('/papers/eeeb2d01-8315-454e-b33f-3d6caa25db42') // ??? Fetch a random object from DB + .expect(200); + + // Checking received data + expect(test.body.data).toBeDefined(); + + expect(test.body.data.length).toBe(1); + expect(test.body.data[0].id).toBeDefined(); + expect(test.body.data[0].title).toBeDefined(); + expect(test.body.data[0].authors).toBeDefined(); + expect(test.body.data[0].summary).toBeDefined(); + expect(test.body.data[0].tags).toBeDefined(); + expect(test.body.data[0].content).toBeDefined(); + expect(test.body.data[0].id).toBe('eeeb2d01-8315-454e-b33f-3d6caa25db42'); + + // Checking received meta + expect(test.body.meta).toBeDefined(); + + expect(test.body.meta.total).toBeDefined(); + expect(test.body.meta.pagenum).toBeDefined(); + expect(test.body.meta.order).toBeDefined(); + expect(test.body.meta.pagesize).toBeDefined(); + expect(test.body.meta.hasNext).toBeDefined(); + expect(test.body.meta.hasPrev).toBeDefined(); + expect(test.body.meta.total).toBe(1); + expect(test.body.meta.pagenum).toBe(1); + }); + + it('GET /papers/search? | Should return multiple items', async () => { + const test = await request(app.getHttpServer()) + .get('/papers/search?query=at&page=1') + .expect(200); + + // Checking received data + expect(test.body.data).toBeDefined(); + + expect(test.body.data.length).toBeGreaterThan(0); + for (const paper of test.body.data) { + expect(paper.id).toBeDefined(); + expect(paper.title).toBeDefined(); + expect(paper.authors).toBeDefined(); + expect(paper.summary).toBeDefined(); + expect(paper.tags).toBeDefined(); + expect(paper.content).toBeDefined(); + } + + // Checking received meta + expect(test.body.meta).toBeDefined(); + + expect(test.body.meta.total).toBeDefined(); + expect(test.body.meta.pagenum).toBeDefined(); + expect(test.body.meta.order).toBeDefined(); + expect(test.body.meta.pagesize).toBeDefined(); + expect(test.body.meta.hasNext).toBeDefined(); + expect(test.body.meta.hasPrev).toBeDefined(); + expect(test.body.meta.total).toBeGreaterThan(0); + expect(test.body.meta.pagenum).toBe(1); + }); + + afterAll(async () => { + await app.close(); + }) +}); \ No newline at end of file diff --git a/src/test/page.interceptor.spec.ts b/src/test/page.interceptor.spec.ts index 8ac120e..c6fe8a7 100644 --- a/src/test/page.interceptor.spec.ts +++ b/src/test/page.interceptor.spec.ts @@ -1,12 +1,12 @@ -import { HttpModule } from "@nestjs/axios"; +import { HttpService } from "@nestjs/axios"; +import { ConfigModule } from "@nestjs/config"; +import { ModuleRef } from "@nestjs/core"; import { Test } from "@nestjs/testing"; -import exp from "constants"; import { Observable, of } from "rxjs"; -import { PapersController } from "src/application"; -import { Order } from "src/core/domain"; -import { PageDto, SearchQueryDto } from "src/core/domain/dtos"; +import { EsTime, Order } from "src/core/domain"; +import { PageDto } from "src/core/domain/dtos"; +import { HttpResponseException } from "src/core/exceptions"; import { PageInterceptor } from "src/core/interceptors/page.interceptor"; -import { SearchService } from "src/core/services/common/search.service"; const execCtxMock = { switchToHttp: jest.fn().mockReturnThis(), @@ -26,18 +26,31 @@ const callHandlerMock = { describe('Unit tests for PageInterceptor', () => { let pageInter: PageInterceptor; - let moduleRef; + let httpService: HttpService; beforeAll(async () => { - moduleRef = await Test.createTestingModule({ - imports: [HttpModule], - controllers: [PapersController], - providers: [SearchService, PageInterceptor], + const moduleRef = await Test.createTestingModule({ + providers: [ + { + provide: HttpService, + useValue: { + post: jest.fn(), + delete: jest.fn() + }, + }, + PageInterceptor, + ], + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + cache: true, + expandVariables: true, + }) + ], }).compile(); pageInter = moduleRef.get(PageInterceptor); - - pageInter.getPIT = jest.fn().mockReturnValue({}); + httpService = moduleRef.get(HttpService); execCtxMock.getRequest.mockReturnValue({ query: { @@ -48,19 +61,32 @@ describe('Unit tests for PageInterceptor', () => { } }); - callHandlerMock.handle.mockReturnValueOnce( + callHandlerMock.handle.mockReturnValue( of({ - total: { value: 1 }, - hits: { hits: [{}] } + hits: { + total: { value: 1 }, + hits: [{}] + } }) ); }); it('Should be defined', () => { expect(pageInter).toBeDefined(); + expect(httpService).toBeDefined(); }); describe('intercept()', () => { + let tmp; + beforeAll(() => { + tmp = pageInter.getPIT; + pageInter.getPIT = jest.fn().mockReturnValue({}); + }); + + afterAll(() => { + pageInter.getPIT = tmp; + }); + it('Should return a Promise', () => { expect(pageInter.intercept(execCtxMock, callHandlerMock)).toBeInstanceOf(Promise); }); @@ -73,81 +99,234 @@ describe('Unit tests for PageInterceptor', () => { }); }); }); - - it.todo('Should touch CallHandler.handle() method'); + it('Should touch CallHandler.handle() method', () => { + let chHandleSpy = jest.spyOn(callHandlerMock, 'handle'); + pageInter.intercept(execCtxMock, callHandlerMock); + expect(chHandleSpy).toBeCalled(); + }); - // it('Should return an Observable with a page of type PageDto', (done) => { - // executionContext.getRequest.mockReturnValue( { query: new SearchQueryDto('someQuery', 1, 10, 'desc') }); - // callHandler.handle.mockReturnValue( of({ - // total: { value: 1 }, - // hits: [{},], - // })); - - - // expect(pageInter.intercept(executionContext, callHandler)).toBeInstanceOf(Promise); - // pageInter.intercept(executionContext, callHandler).then(res => res.subscribe((data) => { - // expect(data).toBeInstanceOf(PageDto); - // done(); - // })); - // }) + it('Should construct a page with proper data on it', () => { + callHandlerMock.handle.mockReturnValueOnce( + of({ + hits: { + total: { value: 1 }, + hits: [{ + _source: { + dummy: 'dum' + } + }] + } + }) + ); - // it('Should hold content on the returned page', (done) => { - // executionContext.getRequest.mockReturnValueOnce( { query: new SearchQueryDto('someQuery', 1, 10, 'desc') }); - // callHandler.handle.mockReturnValueOnce(of({ - // total: { value: 1 }, - // hits: [{dummy: 'dum'}], - // })); + pageInter.intercept(execCtxMock, callHandlerMock).then((res) => { + res.subscribe((page) => { + expect(page.data.length).toBe(1); + expect(page.data[0]).toEqual({ dummy: 'dum' }); + }); + }); + }); - // pageInter.intercept(executionContext, callHandler).then(res => res.subscribe((data) => { - // expect(data).toEqual({ - // data: expect.anything(), - // meta: expect.anything(), - // }); - // done(); - // })); - // }); + it('Should construct correct meta-data of the page', () => { + execCtxMock.getRequest.mockReturnValueOnce({ + query: { + page: 5, + order: 'desc', + limit: 100, + } + }); - // it('Should have next page', (done) => { - // executionContext.getRequest.mockReturnValue({ query: new SearchQueryDto('someQuery', 1, 5, 'desc') }); - // callHandler.handle.mockReturnValue(of({ - // total: { value: 10 }, - // hits: Array(10).fill({dummy: 'dum'}, 0, 10), - // })); + callHandlerMock.handle.mockReturnValueOnce( + of({ + hits: { + total: { value: 921 }, + hits: [] + } + }) + ); - // pageInter.intercept(executionContext, callHandler).then(res => res.subscribe((data) => { - // expect(data.meta.hasNext).toEqual(true); - // expect(data.meta.hasPrev).toEqual(false); - // done(); - // })); - // }); + pageInter.intercept(execCtxMock, callHandlerMock).then((res) => { + res.subscribe((page) => { + expect(page.meta).toEqual({ + total: 921, + pagenum: 5, + order: 'desc', + hasNext: true, + hasPrev: true, + pagesize: 100 + }); + }); + }); + }); - // it('Should have correct meta-data', (done) => { - // executionContext.getRequest.mockReturnValue({ query: new SearchQueryDto('someQuery', 2, 5, 'asc') }); - // callHandler.handle.mockReturnValue(of({ - // total: { value: 15 }, - // hits: Array(15).fill({dummy: 'dum'}, 0, 15), - // })); + it('Should reverse the search results', () => { + execCtxMock.getRequest.mockReturnValueOnce({ + query: { + page: 1, + order: 'desc', + limit: 3 + } + }); - // pageInter.intercept(executionContext, callHandler).then(res => res.subscribe((data) => { - // expect(data.meta).toEqual({ - // total: 15, - // pagenum: 2, - // order: Order.ASC, - // hasNext: true, - // hasPrev: true, - // pagesize: 5 - // }); - // done(); - // })); - // }); + pageInter['prevSearch']._prevPage = 3; + pageInter['prevSearch'].isSet = jest.fn().mockImplementationOnce(() => { + return true; + }) + + callHandlerMock.handle.mockReturnValueOnce( + of({ + hits: { + total: { value: 1 }, + hits: [ + { sort: ['1', 'less relevant'], _source: '1' }, + { sort: ['2', 'average'], _source: '2' }, + { sort: ['3', 'most relevant'], _source: '3' } + ] + } + }) + ); + + pageInter.intercept(execCtxMock, callHandlerMock).then((res) => { + res.subscribe((page) => { + expect(pageInter['prevSearch']._tiebreaker).toEqual(['1', 'less relevant']); + expect(page.data).toEqual(['3', '2', '1']); + }); + }); + }); }); - // describe('getPIT()', () => { + describe('getPIT()', () => { + it('Should touch HttpService.post() method', () => { + let httpPostMock = jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + data: {id: '2567'}, + status: 0, + statusText: '', + headers: {}, + config: {}, + })); - // }); + pageInter.getPIT(1); + expect(httpPostMock).toHaveBeenCalled(); + }); + + it('Should contain correct port in the URI from .env', () => { + let httpPostMock = jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + data: {id: '2567'}, + status: 0, + statusText: '', + headers: {}, + config: {}, + })); + + pageInter.getPIT(1); + expect(httpPostMock).toHaveBeenCalledWith(`http://localhost:${process.env.ES_PORT}/papers/_pit?keep_alive=1m`); + }); + + it('Should touch HttpService with correct URI when time alive and time-unit are set', () => { + let httpPostMock = jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + data: {id: '2567'}, + status: 0, + statusText: '', + headers: {}, + config: {}, + })); + + let time = 2; + let unit = EsTime.sec; + + pageInter.getPIT(time, unit); + expect(httpPostMock).toHaveBeenCalledWith(`http://localhost:${process.env.ES_PORT}/papers/_pit?keep_alive=${time+unit}`); + }); + + it('Should return error exeception when HttpService fails', () => { + jest.spyOn(httpService, 'post').mockImplementationOnce(() => { + throw HttpResponseException; + }); + + expect(pageInter.getPIT(1)).rejects.toEqual(HttpResponseException); + }); + + it('Should return a non-empty string when HttpService request succeedes', () => { + jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + data: {id: '2567', keep_alive: '1m'}, + status: 0, + statusText: '', + headers: {}, + config: {}, + })); + + expect(pageInter.getPIT(1)).resolves.toEqual({ + id: '2567', + keep_alive: '1m', + }); + }); + }); // describe('deletePIT()', () => { + // it('Should touch HttpService.delete() method', () => { + // let httpPostMock = jest.spyOn(httpService, 'delete').mockReturnValueOnce(of({ + // data: {id: '2567'}, + // status: 0, + // statusText: '', + // headers: {}, + // config: {}, + // })); + // pageInter.getPIT(1); + // expect(httpPostMock).toHaveBeenCalled(); + // }); + + // it('Should contain correct port in the URI from .env', () => { + // let httpPostMock = jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + // data: {id: '2567'}, + // status: 0, + // statusText: '', + // headers: {}, + // config: {}, + // })); + + // pageInter.getPIT(1); + // expect(httpPostMock).toHaveBeenCalledWith(`http://localhost:${process.env.ES_PORT}/papers/_pit?keep_alive=1m`); + // }); + + // it('Should touch HttpService with correct URI when time alive and time-unit are set', () => { + // let httpPostMock = jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + // data: {id: '2567'}, + // status: 0, + // statusText: '', + // headers: {}, + // config: {}, + // })); + + // let time = 2; + // let unit = EsTime.sec; + + // pageInter.getPIT(time, unit); + // expect(httpPostMock).toHaveBeenCalledWith(`http://localhost:${process.env.ES_PORT}/papers/_pit?keep_alive=${time+unit}`); + // }); + + // it('Should return error exeception when HttpService fails', () => { + // jest.spyOn(httpService, 'post').mockImplementationOnce(() => { + // throw HttpResponseException; + // }); + + // expect(pageInter.getPIT(1)).rejects.toEqual(HttpResponseException); + // }); + + // it('Should return a non-empty string when HttpService request succeedes', () => { + // jest.spyOn(httpService, 'post').mockReturnValueOnce(of({ + // data: {id: '2567', keep_alive: '1m'}, + // status: 0, + // statusText: '', + // headers: {}, + // config: {}, + // })); + + // expect(pageInter.getPIT(1)).resolves.toEqual({ + // id: '2567', + // keep_alive: '1m', + // }); + // }); // }); }); \ No newline at end of file diff --git a/src/test/papers.endpoint.spec.ts b/src/test/papers.endpoint.spec.ts deleted file mode 100644 index 2ed6b09..0000000 --- a/src/test/papers.endpoint.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Test, TestingModule } from "@nestjs/testing"; -import { INestApplication } from "@nestjs/common"; -import request from 'supertest' -import { AppModule } from "src/infrastructure/modules"; - -describe('E2E Testing of /papers', () => { - let app: INestApplication; - - beforeAll(async () => { - const moduleRef: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleRef.createNestApplication(); - await app.init(); - }); - - it('Should return one exact item on page', async () => { - return request(app.getHttpServer()) - .get('/papers/eeeb2d01-8315-454e-b33f-3d6caa25db42') - .expect(200) - .expect((res) => { - res.body.data.length === 1; - }) - .expect((res) => { - res.body.data[0]._source.id === 'eeeb2d01-8315-454e-b33f-3d6caa25db42'; - }); - }); - - it('Should return multiple items', async () => { - return request(app.getHttpServer()) - .get('/papers/search?query=at&page=1') - .expect(200) - .expect((res) => { - res.body.data.length > 0; - }) - .expect((res) => { - for (const value of res.body.data) { - if(Object.keys(value).length === 0) return false; - } - }) - }); - - afterAll(async () => { - await app.close(); - }) -}); \ No newline at end of file diff --git a/src/test/search.service.spec.ts b/src/test/search.service.spec.ts index 2be5b00..95ded23 100644 --- a/src/test/search.service.spec.ts +++ b/src/test/search.service.spec.ts @@ -10,7 +10,6 @@ describe('Unit tests for SearchService', () => { let searchService: SearchService; let httpService: HttpService; - beforeAll(async () => { const moduleRef = await Test.createTestingModule({ providers: [ @@ -114,7 +113,6 @@ describe('Unit tests for SearchService', () => { searchService.findByID('').catch((err) => { expect(err).toBeInstanceOf(GatewayTimeoutException); - console.log(err) }); }); @@ -211,7 +209,6 @@ describe('Unit tests for SearchService', () => { searchService.findByContext(null).catch((err) => { expect(err).toBeInstanceOf(GatewayTimeoutException); - console.log(err) }); }); @@ -227,80 +224,4 @@ describe('Unit tests for SearchService', () => { }); }); }); -}); - -/** - * describe('getPIT()', () => { - it('Should touch HttpService.post() method', () => { - let postMock = jest.spyOn(httpService, 'post').mockReturnValue(of({ - data: {id: '2567'}, - status: 0, - statusText: '', - headers: {}, - config: {}, - })); - - searchService.getPIT(1); - expect(postMock).toHaveBeenCalled(); - }); - - it('Should contain correct port in the URI from .env', () => { - let postMock = jest.spyOn(httpService, 'post').mockReturnValue(of({ - data: {id: '2567'}, - status: 0, - statusText: '', - headers: {}, - config: {}, - })); - - searchService.getPIT(1); - expect(postMock).toHaveBeenCalledWith(`http://localhost:${process.env.ES_PORT}/papers/_pit?keep_alive=1m`); - }); - - it('Should touch HttpService with correct URI when time alive and time-unit are set', () => { - let postMock = jest.spyOn(httpService, 'post').mockReturnValue(of({ - data: {id: '2567'}, - status: 0, - statusText: '', - headers: {}, - config: {}, - })); - - let time = 2; - let unit = EsTime.sec; - - searchService.getPIT(time, unit); - expect(postMock).toHaveBeenCalledWith(`http://localhost:${process.env.ES_PORT}/papers/_pit?keep_alive=${time+unit}`); - }); - - it('Should return error exeception when HttpService fails', () => { - jest.spyOn(httpService, 'post').mockImplementation(() => { - throw HttpResponseException; - }); - - expect(searchService.getPIT(1)).rejects.toEqual(HttpResponseException); - }); - - it('Should return a non-empty string when HttpService request succeedes', () => { - jest.spyOn(httpService, 'post').mockReturnValue(of({ - data: {id: '2567', keep_alive: '1m'}, - status: 0, - statusText: '', - headers: {}, - config: {}, - })); - - expect(searchService.getPIT(1)).resolves.toEqual({ - id: '2567', - keep_alive: '1m', - }); - }); - - - }); - - describe('deletePIT()', () => { - it.todo('Should fail to delete, because the requested PIT ID is invalid'); - it.todo('Should call HttpService.delete() method with correct body'); - }); - */ \ No newline at end of file +}); \ No newline at end of file