diff --git a/core-web/apps/dotcdn/src/app/app.module.ts b/core-web/apps/dotcdn/src/app/app.module.ts index 201202584b96..ffca11f77283 100644 --- a/core-web/apps/dotcdn/src/app/app.module.ts +++ b/core-web/apps/dotcdn/src/app/app.module.ts @@ -14,7 +14,6 @@ import { SkeletonModule } from 'primeng/skeleton'; import { TabViewModule } from 'primeng/tabview'; import { - CoreWebService, DotcmsConfigService, DotcmsEventsService, DotEventsSocket, @@ -56,7 +55,6 @@ const dotEventSocketURLFactory = () => { ReactiveFormsModule ], providers: [ - CoreWebService, LoggerService, StringUtils, SiteService, diff --git a/core-web/apps/dotcdn/src/app/dotcdn.component.store.spec.ts b/core-web/apps/dotcdn/src/app/dotcdn.component.store.spec.ts index 1c7deb2ec62e..23db7572965d 100644 --- a/core-web/apps/dotcdn/src/app/dotcdn.component.store.spec.ts +++ b/core-web/apps/dotcdn/src/app/dotcdn.component.store.spec.ts @@ -1,14 +1,11 @@ import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { - CoreWebService, - CoreWebServiceMock, - SiteService, - SiteServiceMock -} from '@dotcms/dotcms-js'; +import { SiteService } from '@dotcms/dotcms-js'; +import { SiteServiceMock } from '@dotcms/utils-testing'; import { DotCDNStats } from './app.models'; import { DotCDNStore } from './dotcdn.component.store'; @@ -102,10 +99,10 @@ describe('DotCDNComponentStore', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotCDNStore, - { provide: CoreWebService, useClass: CoreWebServiceMock }, { provide: SiteService, useClass: SiteServiceMock }, { provide: DotCDNService, diff --git a/core-web/apps/dotcdn/src/app/dotcdn.service.spec.ts b/core-web/apps/dotcdn/src/app/dotcdn.service.spec.ts index 04b0a7175e6b..400f9444d9a3 100644 --- a/core-web/apps/dotcdn/src/app/dotcdn.service.spec.ts +++ b/core-web/apps/dotcdn/src/app/dotcdn.service.spec.ts @@ -1,313 +1,159 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; - import { - CoreWebService, - CoreWebServiceMock, - SiteService, - SiteServiceMock -} from '@dotcms/dotcms-js'; + createHttpFactory, + HttpMethod, + mockProvider, + SpectatorHttp, + SpyObject +} from '@ngneat/spectator/jest'; + +import { SiteService } from '@dotcms/dotcms-js'; import { DotCDNService } from './dotcdn.service'; -const fakeDotCDNViewData = { - resp: { - headers: { - normalizedNames: {}, - lazyUpdate: null +const MOCK_SITE_IDENTIFIER = '123-xyz-567-xxl'; + +const fakeDotCDNStats = { + stats: { + bandwidthPretty: '238.85 MB', + bandwidthUsedChart: { + '2021-04-16T00:00:00Z': 15506849, + '2021-04-17T00:00:00Z': 0, + '2021-04-18T00:00:00Z': 0, + '2021-04-19T00:00:00Z': 15301470, + '2021-04-20T00:00:00Z': 5061682 }, - status: 200, - statusText: 'OK', - url: 'http://localhost:4200/api/v1/dotcdn/stats?hostId=48190c8c-42c4-46af-8d1a-0cd5db894797&dateFrom=2021-04-16&dateTo=2021-05-01', - ok: true, - type: 4, - body: { - entity: { - stats: { - bandwidthPretty: '238.85 MB', - bandwidthUsedChart: { - '2021-04-16T00:00:00Z': 15506849, - '2021-04-17T00:00:00Z': 0, - '2021-04-18T00:00:00Z': 0, - '2021-04-19T00:00:00Z': 15301470, - '2021-04-20T00:00:00Z': 5061682, - '2021-04-21T00:00:00Z': 0, - '2021-04-22T00:00:00Z': 0, - '2021-04-23T00:00:00Z': 0, - '2021-04-24T00:00:00Z': 0, - '2021-04-25T00:00:00Z': 0, - '2021-04-26T00:00:00Z': 0, - '2021-04-27T00:00:00Z': 0, - '2021-04-28T00:00:00Z': 110186951, - '2021-04-29T00:00:00Z': 92793786, - '2021-04-30T00:00:00Z': 0, - '2021-05-01T00:00:00Z': 0 - }, - cacheHitRate: 64.58333333333334, - cdnDomain: 'https://erick-demo.b-cdn.net', - dateFrom: '2021-04-15T22:00:00Z', - dateTo: '2021-04-30T22:00:00Z', - geographicDistribution: { - NA: { - ' Miami, FL': 238850738 - } - }, - requestsServedChart: { - '2021-04-16T00:00:00Z': 3, - '2021-04-17T00:00:00Z': 0, - '2021-04-18T00:00:00Z': 0, - '2021-04-19T00:00:00Z': 6, - '2021-04-20T00:00:00Z': 3, - '2021-04-21T00:00:00Z': 0, - '2021-04-22T00:00:00Z': 0, - '2021-04-23T00:00:00Z': 0, - '2021-04-24T00:00:00Z': 0, - '2021-04-25T00:00:00Z': 0, - '2021-04-26T00:00:00Z': 0, - '2021-04-27T00:00:00Z': 0, - '2021-04-28T00:00:00Z': 19, - '2021-04-29T00:00:00Z': 17, - '2021-04-30T00:00:00Z': 0, - '2021-05-01T00:00:00Z': 0 - }, - totalBandwidthUsed: 238850738, - totalRequestsServed: 48 - } - }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] - } - }, - bodyJsonObject: { - entity: { - stats: { - bandwidthPretty: '238.85 MB', - bandwidthUsedChart: { - '2021-04-16T00:00:00Z': 15506849, - '2021-04-17T00:00:00Z': 0, - '2021-04-18T00:00:00Z': 0, - '2021-04-19T00:00:00Z': 15301470, - '2021-04-20T00:00:00Z': 5061682, - '2021-04-21T00:00:00Z': 0, - '2021-04-22T00:00:00Z': 0, - '2021-04-23T00:00:00Z': 0, - '2021-04-24T00:00:00Z': 0, - '2021-04-25T00:00:00Z': 0, - '2021-04-26T00:00:00Z': 0, - '2021-04-27T00:00:00Z': 0, - '2021-04-28T00:00:00Z': 110186951, - '2021-04-29T00:00:00Z': 92793786, - '2021-04-30T00:00:00Z': 0, - '2021-05-01T00:00:00Z': 0 - }, - cacheHitRate: 64.58333333333334, - cdnDomain: 'https://erick-demo.b-cdn.net', - dateFrom: '2021-04-15T22:00:00Z', - dateTo: '2021-04-30T22:00:00Z', - geographicDistribution: { - NA: { - ' Miami, FL': 238850738 - } - }, - requestsServedChart: { - '2021-04-16T00:00:00Z': 3, - '2021-04-17T00:00:00Z': 0, - '2021-04-18T00:00:00Z': 0, - '2021-04-19T00:00:00Z': 6, - '2021-04-20T00:00:00Z': 3, - '2021-04-21T00:00:00Z': 0, - '2021-04-22T00:00:00Z': 0, - '2021-04-23T00:00:00Z': 0, - '2021-04-24T00:00:00Z': 0, - '2021-04-25T00:00:00Z': 0, - '2021-04-26T00:00:00Z': 0, - '2021-04-27T00:00:00Z': 0, - '2021-04-28T00:00:00Z': 19, - '2021-04-29T00:00:00Z': 17, - '2021-04-30T00:00:00Z': 0, - '2021-05-01T00:00:00Z': 0 - }, - totalBandwidthUsed: 238850738, - totalRequestsServed: 48 + cacheHitRate: 64.58333333333334, + cdnDomain: 'https://erick-demo.b-cdn.net', + dateFrom: '2021-04-15T22:00:00Z', + dateTo: '2021-04-30T22:00:00Z', + geographicDistribution: { + NA: { + ' Miami, FL': 238850738 } }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] - }, - headers: { - normalizedNames: {}, - lazyUpdate: null + requestsServedChart: { + '2021-04-16T00:00:00Z': 3, + '2021-04-17T00:00:00Z': 0, + '2021-04-18T00:00:00Z': 0, + '2021-04-19T00:00:00Z': 6, + '2021-04-20T00:00:00Z': 3 + }, + totalBandwidthUsed: 238850738, + totalRequestsServed: 48 } }; -const fakePurgeUrlsResp = { - resp: { - headers: { - normalizedNames: {}, - lazyUpdate: null - }, - status: 200, - statusText: 'OK', - url: 'http://localhost:4200/api/v1/dotcdn', - ok: true, - type: 4, - body: { - entity: { - 'All Urls Sent Purged: ': true - }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] - } - }, - bodyJsonObject: { - entity: { - 'All Urls Sent Purged: ': true - }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] +const fakePurgeUrlsResponse = { + entity: { + 'All Urls Sent Purged: ': true }, - headers: { - normalizedNames: {}, - lazyUpdate: null - } + errors: [], + i18nMessagesMap: {}, + messages: [], + permissions: [] }; -const fakePurgeAllResp = { - resp: { - headers: { - normalizedNames: {}, - lazyUpdate: null - }, - status: 200, - statusText: 'OK', - url: 'http://localhost:4200/api/v1/dotcdn', - ok: true, - type: 4, - body: { - entity: { - 'Entire Cache Purged: ': true - }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] - } - }, - bodyJsonObject: { - entity: { - 'Entire Cache Purged: ': true - }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] +const fakePurgeAllResponse = { + entity: { + 'Entire Cache Purged: ': true }, - headers: { - normalizedNames: {}, - lazyUpdate: null - } + errors: [], + i18nMessagesMap: {}, + messages: [], + permissions: [] }; -describe('DotcdnService', () => { - let service: DotCDNService; - let dotSiteService: SiteService; - let dotCoreWebService: CoreWebService; - let httpMock: HttpTestingController; +describe('DotCDNService', () => { + let spectator: SpectatorHttp; + let siteService: SpyObject; + + const createHttp = createHttpFactory({ + service: DotCDNService, + providers: [mockProvider(SiteService)] + }); beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - { provide: SiteService, useClass: SiteServiceMock } - ] - }); - service = TestBed.inject(DotCDNService); - dotSiteService = TestBed.inject(SiteService); - dotCoreWebService = TestBed.inject(CoreWebService); - httpMock = TestBed.inject(HttpTestingController); - jest.spyOn(dotSiteService, 'getCurrentSite'); - jest.restoreAllMocks(); + spectator = createHttp(); + siteService = spectator.inject(SiteService); jest.useFakeTimers(); jest.setSystemTime(new Date('2021-05-03')); }); - // Skipping because the `useFakeTimers` and `setSystemTime` is not pickinh up win GHA - xit('should return the stats', (done) => { - jest.spyOn(dotCoreWebService, 'requestView'); - - const { - bodyJsonObject: { entity } - } = fakeDotCDNViewData; + afterEach(() => { + jest.useRealTimers(); + }); - service.requestStats('30').subscribe((value) => { - expect(value).toStrictEqual(entity); - done(); + describe('requestStats', () => { + beforeEach(() => { + const { of } = jest.requireActual('rxjs'); + siteService.getCurrentSite.mockReturnValue(of({ identifier: MOCK_SITE_IDENTIFIER })); }); - const req = httpMock.expectOne( - `/api/v1/dotcdn/stats?hostId=123-xyz-567-xxl&dateFrom=2021-04-02&dateTo=2021-05-02` - ); + it('should return the stats', () => { + spectator.service.requestStats('30').subscribe((value) => { + expect(value).toStrictEqual(fakeDotCDNStats); + }); - req.flush({ entity }); - }); + const req = spectator.expectOne( + `/api/v1/dotcdn/stats?hostId=${MOCK_SITE_IDENTIFIER}&dateFrom=2021-04-02&dateTo=2021-05-02`, + HttpMethod.GET + ); - it('should purge cache with URLs', (done) => { - const requestViewSpy = jest.spyOn(dotCoreWebService, 'requestView'); - service.purgeCache(['url1, url2']).subscribe((value) => { - expect(value).toStrictEqual({ - entity: { - 'All Urls Sent Purged: ': true - }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] - }); - done(); + req.flush({ entity: fakeDotCDNStats }); }); - const req = httpMock.expectOne('/api/v1/dotcdn'); - expect(req.request.method).toBe('DELETE'); + }); - expect(requestViewSpy).toHaveBeenCalledWith({ - body: '{"urls":["url1, url2"],"invalidateAll":false,"hostId":"123-xyz-567-xxl"}', - method: 'DELETE', - url: '/api/v1/dotcdn' + describe('purgeCache', () => { + beforeEach(() => { + const { of } = jest.requireActual('rxjs'); + siteService.getCurrentSite.mockReturnValue(of({ identifier: MOCK_SITE_IDENTIFIER })); }); - req.flush(fakePurgeUrlsResp.bodyJsonObject); - }); + it('should purge cache with URLs', () => { + spectator.service.purgeCache(['url1', 'url2']).subscribe((value) => { + expect(value).toStrictEqual(fakePurgeUrlsResponse); + }); - it('should purge all cache', (done) => { - const requestViewSpy = jest.spyOn(dotCoreWebService, 'requestView'); - service.purgeCacheAll().subscribe((value) => { - expect(value).toStrictEqual({ - entity: { 'Entire Cache Purged: ': true }, - errors: [], - i18nMessagesMap: {}, - messages: [], - permissions: [] + const req = spectator.expectOne('/api/v1/dotcdn', HttpMethod.DELETE); + expect(req.request.body).toBe( + `{"urls":["url1","url2"],"invalidateAll":false,"hostId":"${MOCK_SITE_IDENTIFIER}"}` + ); + + req.flush({ bodyJsonObject: fakePurgeUrlsResponse }); + }); + + it('should purge cache with empty URLs array when no URLs provided', () => { + spectator.service.purgeCache([]).subscribe((value) => { + expect(value).toStrictEqual(fakePurgeUrlsResponse); }); - done(); + + const req = spectator.expectOne('/api/v1/dotcdn', HttpMethod.DELETE); + expect(req.request.body).toBe( + `{"urls":[],"invalidateAll":false,"hostId":"${MOCK_SITE_IDENTIFIER}"}` + ); + + req.flush({ bodyJsonObject: fakePurgeUrlsResponse }); }); - const req = httpMock.expectOne('/api/v1/dotcdn'); - expect(req.request.method).toBe('DELETE'); + }); - expect(requestViewSpy).toHaveBeenCalledWith({ - body: '{"urls":[],"invalidateAll":true,"hostId":"123-xyz-567-xxl"}', - method: 'DELETE', - url: '/api/v1/dotcdn' + describe('purgeCacheAll', () => { + beforeEach(() => { + const { of } = jest.requireActual('rxjs'); + siteService.getCurrentSite.mockReturnValue(of({ identifier: MOCK_SITE_IDENTIFIER })); }); - req.flush(fakePurgeAllResp.bodyJsonObject); + it('should purge all cache', () => { + spectator.service.purgeCacheAll().subscribe((value) => { + expect(value).toStrictEqual(fakePurgeAllResponse); + }); + + const req = spectator.expectOne('/api/v1/dotcdn', HttpMethod.DELETE); + expect(req.request.body).toBe( + `{"urls":[],"invalidateAll":true,"hostId":"${MOCK_SITE_IDENTIFIER}"}` + ); + + req.flush({ bodyJsonObject: fakePurgeAllResponse }); + }); }); }); diff --git a/core-web/apps/dotcdn/src/app/dotcdn.service.ts b/core-web/apps/dotcdn/src/app/dotcdn.service.ts index d8567941008a..292d944bba9f 100644 --- a/core-web/apps/dotcdn/src/app/dotcdn.service.ts +++ b/core-web/apps/dotcdn/src/app/dotcdn.service.ts @@ -1,11 +1,13 @@ import { format, subDays } from 'date-fns'; import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; -import { mergeMap, pluck } from 'rxjs/operators'; +import { mergeMap, map } from 'rxjs/operators'; -import { CoreWebService, ResponseView, SiteService } from '@dotcms/dotcms-js'; +import { SiteService } from '@dotcms/dotcms-js'; +import { DotCMSResponse, DotCMSResponseJsonObject } from '@dotcms/dotcms-models'; import { DotCDNStats, PurgeReturnData, PurgeUrlOptions } from './app.models'; @@ -13,7 +15,7 @@ import { DotCDNStats, PurgeReturnData, PurgeUrlOptions } from './app.models'; providedIn: 'root' }) export class DotCDNService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private siteService = inject(SiteService); /** @@ -25,16 +27,17 @@ export class DotCDNService { */ requestStats(period: string): Observable { return this.siteService.getCurrentSite().pipe( - pluck('identifier'), + map((site) => site.identifier), mergeMap((hostId: string) => { const dateTo = format(new Date(), 'yyyy-MM-dd'); const dateFrom = format(subDays(new Date(), parseInt(period, 10)), 'yyyy-MM-dd'); - return this.coreWebService.requestView({ - url: `/api/v1/dotcdn/stats?hostId=${hostId}&dateFrom=${dateFrom}&dateTo=${dateTo}` - }); - }), - pluck('entity') + return this.http + .get< + DotCMSResponse + >(`/api/v1/dotcdn/stats?hostId=${hostId}&dateFrom=${dateFrom}&dateTo=${dateTo}`) + .pipe(map((response) => response.entity)); + }) ); } @@ -42,30 +45,30 @@ export class DotCDNService { * Makes a request to purge the cache * * @param {string[]} [urls=[]] - * @return {Observable>} + * @return {Observable} * @memberof DotCDNService */ purgeCache(urls?: string[]): Observable { return this.siteService.getCurrentSite().pipe( - pluck('identifier'), + map((site) => site.identifier), mergeMap((hostId: string) => { return this.purgeUrlRequest({ hostId, invalidateAll: false, urls }); }), - pluck('bodyJsonObject') + map((response) => response.bodyJsonObject) ); } /** * Makes a request to purge the cache * - * @return {Observable>} + * @return {Observable} * @memberof DotCDNService */ purgeCacheAll(): Observable { return this.siteService.getCurrentSite().pipe( - pluck('identifier'), + map((site) => site.identifier), mergeMap((hostId: string) => this.purgeUrlRequest({ hostId, invalidateAll: true })), - pluck('bodyJsonObject') + map((response) => response.bodyJsonObject) ); } @@ -73,10 +76,8 @@ export class DotCDNService { urls = [], invalidateAll, hostId - }: PurgeUrlOptions): Observable> { - return this.coreWebService.requestView({ - url: `/api/v1/dotcdn`, - method: 'DELETE', + }: PurgeUrlOptions): Observable> { + return this.http.delete>('/api/v1/dotcdn', { body: JSON.stringify({ urls, invalidateAll, diff --git a/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.spec.ts b/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.spec.ts index cdf1bec425a9..d3ae06b17a05 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.spec.ts @@ -1,30 +1,15 @@ -import { mockProvider } from '@ngneat/spectator/jest'; -import { throwError } from 'rxjs'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { getTestBed, TestBed } from '@angular/core/testing'; +import { DotHttpErrorManagerService } from '@dotcms/data-access'; +import { MockDotHttpErrorManagerService } from '@dotcms/utils-testing'; -import { ConfirmationService } from 'primeng/api'; - -import { - DotHttpErrorManagerService, - DotMessageDisplayService, - DotRouterService, - DotAlertConfirmService, - DotMessageService, - DotFormatDateService -} from '@dotcms/data-access'; -import { CoreWebService, LoginService } from '@dotcms/dotcms-js'; import { - CoreWebServiceMock, - LoginServiceMock, - DotMessageDisplayServiceMock, - MockDotRouterService, - DotFormatDateServiceMock, - mockResponseView -} from '@dotcms/utils-testing'; - -import { DotAddToMenuService, DotCreateCustomTool } from './add-to-menu.service'; + DotAddToMenuService, + DotCreateCustomTool, + DotCustomToolToLayout +} from './add-to-menu.service'; const customToolData: DotCreateCustomTool = { contentTypes: 'Blog', @@ -33,39 +18,30 @@ const customToolData: DotCreateCustomTool = { }; describe('DotAddToMenuService', () => { - let injector: TestBed; let dotAddToMenuService: DotAddToMenuService; let dotHttpErrorManagerService: DotHttpErrorManagerService; - let coreWebService: CoreWebService; - let httpMock: HttpTestingController; + let httpTesting: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - { - provide: LoginService, - useClass: LoginServiceMock - }, - { - provide: DotMessageDisplayService, - useClass: DotMessageDisplayServiceMock - }, - { provide: DotRouterService, useClass: MockDotRouterService }, - { provide: DotFormatDateService, useClass: DotFormatDateServiceMock }, - ConfirmationService, + provideHttpClient(), + provideHttpClientTesting(), DotAddToMenuService, - DotAlertConfirmService, - DotHttpErrorManagerService, - mockProvider(DotMessageService) + { + provide: DotHttpErrorManagerService, + useClass: MockDotHttpErrorManagerService + } ] }); - injector = getTestBed(); - dotAddToMenuService = injector.inject(DotAddToMenuService); - dotHttpErrorManagerService = injector.inject(DotHttpErrorManagerService); - coreWebService = injector.inject(CoreWebService); - httpMock = injector.inject(HttpTestingController); + + dotAddToMenuService = TestBed.inject(DotAddToMenuService); + dotHttpErrorManagerService = TestBed.inject(DotHttpErrorManagerService); + httpTesting = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTesting.verify(); }); it('should clean up Portlet Id value', () => { @@ -75,13 +51,11 @@ describe('DotAddToMenuService', () => { }); it('should create a custom tool portlet', () => { - const url = `v1/portlet/custom`; - dotAddToMenuService.createCustomTool(customToolData).subscribe((response: string) => { expect(response).toEqual('ok'); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne('/api/v1/portlet/custom'); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual({ ...customToolData, @@ -93,41 +67,45 @@ describe('DotAddToMenuService', () => { }); it('should throw null on create custom tool error 400', () => { - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAddToMenuService.createCustomTool(customToolData).subscribe((response: string) => { expect(response).toEqual(null); }); + + const req = httpTesting.expectOne('/api/v1/portlet/custom'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + expect(dotHttpErrorManagerService.handle).not.toHaveBeenCalled(); }); it('should throw error 500 on create custom tool error', () => { - const error404 = mockResponseView(500); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAddToMenuService.createCustomTool(customToolData).subscribe((response: string) => { expect(response).toEqual(null); }); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(500)); + + const req = httpTesting.expectOne('/api/v1/portlet/custom'); + req.flush(null, { status: 500, statusText: 'Internal Server Error' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); it('should add to layout a custom tool portlet', () => { - const url = `v1/portlet/custom/c_${customToolData.portletName}_${customToolData.dataViewMode}/_addtolayout/123`; - - dotAddToMenuService - .addToLayout({ - portletName: customToolData.portletName, - dataViewMode: customToolData.dataViewMode, - layoutId: '123' - }) - .subscribe((response: string) => { - expect(response).toEqual('ok'); - }); - - const req = httpMock.expectOne(url); + const layoutData: DotCustomToolToLayout = { + portletName: customToolData.portletName, + dataViewMode: customToolData.dataViewMode, + layoutId: '123' + }; + + dotAddToMenuService.addToLayout(layoutData).subscribe((response: string) => { + expect(response).toEqual('ok'); + }); + + const req = httpTesting.expectOne( + `/api/v1/portlet/custom/c_${customToolData.portletName}_${customToolData.dataViewMode}/_addtolayout/123` + ); expect(req.request.method).toBe('PUT'); req.flush({ entity: 'ok' @@ -135,23 +113,23 @@ describe('DotAddToMenuService', () => { }); it('should throw error 400 on add to layout custom portlet', () => { - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); - - dotAddToMenuService - .addToLayout({ - portletName: customToolData.portletName, - dataViewMode: customToolData.dataViewMode, - layoutId: '123' - }) - .subscribe((response: string) => { - expect(response).toEqual(null); - }); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); - }); - afterEach(() => { - httpMock.verify(); + const layoutData: DotCustomToolToLayout = { + portletName: customToolData.portletName, + dataViewMode: customToolData.dataViewMode, + layoutId: '123' + }; + + dotAddToMenuService.addToLayout(layoutData).subscribe((response: string) => { + expect(response).toEqual(null); + }); + + const req = httpTesting.expectOne( + `/api/v1/portlet/custom/c_${customToolData.portletName}_${customToolData.dataViewMode}/_addtolayout/123` + ); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); }); diff --git a/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.ts b/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.ts index 2285ea8c1924..f27c20a38b1a 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/add-to-menu/add-to-menu.service.ts @@ -1,14 +1,14 @@ import { Observable, of } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map, pluck, take } from 'rxjs/operators'; +import { catchError, map, take } from 'rxjs/operators'; import { DotHttpErrorManagerService } from '@dotcms/data-access'; -import { CoreWebService } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; -const addToMenuUrl = `v1/portlet`; +const addToMenuUrl = '/api/v1/portlet'; export interface DotCreateCustomTool { contentTypes: string; @@ -29,7 +29,7 @@ export interface DotCustomToolToLayout { */ @Injectable() export class DotAddToMenuService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private httpErrorManagerService = inject(DotHttpErrorManagerService); /** @@ -51,17 +51,13 @@ export class DotAddToMenuService { * @memberof DotAddToMenuService */ createCustomTool(params: DotCreateCustomTool): Observable { - return this.coreWebService - .requestView({ - body: { - ...params, - portletId: `${this.cleanUpPorletId(params.portletName)}_${params.dataViewMode}` - }, - method: 'POST', - url: `${addToMenuUrl}/custom` + return this.http + .post>(`${addToMenuUrl}/custom`, { + ...params, + portletId: `${this.cleanUpPorletId(params.portletName)}_${params.dataViewMode}` }) .pipe( - pluck('entity'), + map((response) => response.entity), catchError((error: HttpErrorResponse) => { if (error.status === 400) { return of(null); @@ -85,13 +81,12 @@ export class DotAddToMenuService { addToLayout(params: DotCustomToolToLayout): Observable { const portletId = `${this.cleanUpPorletId(params.portletName)}_${params.dataViewMode}`; - return this.coreWebService - .requestView({ - method: 'PUT', - url: `${addToMenuUrl}/custom/c_${portletId}/_addtolayout/${params.layoutId}` - }) + return this.http + .put< + DotCMSResponse + >(`${addToMenuUrl}/custom/c_${portletId}/_addtolayout/${params.layoutId}`, {}) .pipe( - pluck('entity'), + map((response) => response.entity), catchError((error: HttpErrorResponse) => { return this.httpErrorManagerService.handle(error).pipe( take(1), diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.spec.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.spec.ts index cf4ec582dd56..3db7484f2eda 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.spec.ts @@ -1,62 +1,38 @@ -import { throwError } from 'rxjs'; - -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TestBed, waitForAsync } from '@angular/core/testing'; - -import { ConfirmationService } from 'primeng/api'; - -import { - DotAlertConfirmService, - DotHttpErrorManagerService, - DotMessageDisplayService, - DotRouterService, - DotFormatDateService -} from '@dotcms/data-access'; -import { CoreWebService, LoginService } from '@dotcms/dotcms-js'; -import { - CoreWebServiceMock, - DotFormatDateServiceMock, - DotMessageDisplayServiceMock, - LoginServiceMock, - MockDotRouterService, - mockResponseView -} from '@dotcms/utils-testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { DotHttpErrorManagerService } from '@dotcms/data-access'; +import { MockDotHttpErrorManagerService } from '@dotcms/utils-testing'; import { DotAccountService, DotAccountUser } from './dot-account-service'; describe('DotAccountService', () => { let service: DotAccountService; - let coreWebService: CoreWebService; - let httpTestingController: HttpTestingController; + let httpTesting: HttpTestingController; let dotHttpErrorManagerService: DotHttpErrorManagerService; - beforeEach(waitForAsync(() => { + beforeEach(() => { TestBed.configureTestingModule({ providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotAccountService, - DotHttpErrorManagerService, - DotAlertConfirmService, - ConfirmationService, - { - provide: DotMessageDisplayService, - useClass: DotMessageDisplayServiceMock - }, - { provide: DotFormatDateService, useClass: DotFormatDateServiceMock }, - { provide: CoreWebService, useClass: CoreWebServiceMock }, - { provide: DotRouterService, useClass: MockDotRouterService }, { - provide: LoginService, - useClass: LoginServiceMock + provide: DotHttpErrorManagerService, + useClass: MockDotHttpErrorManagerService } - ], - imports: [HttpClientTestingModule] + ] }); service = TestBed.inject(DotAccountService); - httpTestingController = TestBed.inject(HttpTestingController); + httpTesting = TestBed.inject(HttpTestingController); dotHttpErrorManagerService = TestBed.inject(DotHttpErrorManagerService); - coreWebService = TestBed.inject(CoreWebService); - })); + }); + + afterEach(() => { + httpTesting.verify(); + }); it('Should update user data', () => { const user: DotAccountUser = { @@ -68,48 +44,55 @@ describe('DotAccountService', () => { }; service.updateUser(user).subscribe(); - const reqMock = httpTestingController.expectOne((req) => { - return req.url === 'v1/users/current'; - }); - expect(reqMock.request.method).toBe('PUT'); - reqMock.flush({}); + const req = httpTesting.expectOne('/api/v1/users/current'); + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(user); + req.flush({}); }); it('Should do the put request to add the getting starter portlet to menu', () => { - service.addStarterPage().subscribe(); - - const reqMock = httpTestingController.expectOne((req) => { - return req.url === '/api/v1/toolgroups/gettingstarted/_addtouser'; + service.addStarterPage().subscribe((response) => { + expect(response).toEqual('ok'); }); - expect(reqMock.request.method).toBe('PUT'); - reqMock.flush({}); + + const req = httpTesting.expectOne('/api/v1/toolgroups/gettingstarted/_addtouser'); + expect(req.request.method).toBe('PUT'); + req.flush({ entity: 'ok' }); }); - it('should throw error on get apps and handle it', () => { - const error404 = mockResponseView(400); + it('should handle error on addStarterPage and return null', () => { jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); - service.addStarterPage().subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); - }); + service.addStarterPage().subscribe((response) => { + expect(response).toBeNull(); + }); - it('Should do the put request to remove the getting starter portlet to menu', () => { - service.removeStarterPage().subscribe(); + const req = httpTesting.expectOne('/api/v1/toolgroups/gettingstarted/_addtouser'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); + }); - const reqMock = httpTestingController.expectOne((req) => { - return req.url === '/api/v1/toolgroups/gettingstarted/_removefromuser'; + it('Should do the put request to remove the getting starter portlet from menu', () => { + service.removeStarterPage().subscribe((response) => { + expect(response).toEqual('ok'); }); - expect(reqMock.request.method).toBe('PUT'); - reqMock.flush({}); + + const req = httpTesting.expectOne('/api/v1/toolgroups/gettingstarted/_removefromuser'); + expect(req.request.method).toBe('PUT'); + req.flush({ entity: 'ok' }); }); - it('should throw error on get apps and handle it', () => { - const error404 = mockResponseView(400); + it('should handle error on removeStarterPage and return null', () => { jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); - service.removeStarterPage().subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); + service.removeStarterPage().subscribe((response) => { + expect(response).toBeNull(); + }); + + const req = httpTesting.expectOne('/api/v1/toolgroups/gettingstarted/_removefromuser'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); }); diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.ts index 67abb3ec44e2..7acc6d4a9a08 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-account-service.ts @@ -1,31 +1,42 @@ import { Observable } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map, pluck, take } from 'rxjs/operators'; +import { catchError, map, take } from 'rxjs/operators'; import { DotHttpErrorManagerService } from '@dotcms/data-access'; -import { CoreWebService, ResponseView } from '@dotcms/dotcms-js'; +import { User } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; + +interface UpdateUserResponse { + reauthenticate: boolean; + userID: string; + user: User; +} +export interface DotAccountUser { + userId: string; + givenName: string; + surname: string; + newPassword?: string; + currentPassword: string; + email: string; +} @Injectable() export class DotAccountService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private httpErrorManagerService = inject(DotHttpErrorManagerService); /** * Updates user data * * @param {DotAccountUser} user - * @returns {Observable} + * @returns {Observable>} * @memberof DotAccountService */ - updateUser(user: DotAccountUser): Observable { - return this.coreWebService.requestView({ - body: user, - method: 'PUT', - url: 'v1/users/current' - }); + updateUser(user: DotAccountUser): Observable> { + return this.http.put>('/api/v1/users/current', user); } /** @@ -35,14 +46,10 @@ export class DotAccountService { * @memberof DotAccountService */ addStarterPage(): Observable { - return this.coreWebService - .requestView({ - method: 'PUT', - url: '/api/v1/toolgroups/gettingstarted/_addtouser' - }) + return this.http + .put>('/api/v1/toolgroups/gettingstarted/_addtouser', {}) .pipe( - take(1), - pluck('entity'), + map((response) => response.entity), catchError((error: HttpErrorResponse) => { return this.httpErrorManagerService.handle(error).pipe( take(1), @@ -59,14 +66,10 @@ export class DotAccountService { * @memberof DotAccountService */ removeStarterPage(): Observable { - return this.coreWebService - .requestView({ - method: 'PUT', - url: '/api/v1/toolgroups/gettingstarted/_removefromuser' - }) + return this.http + .put>('/api/v1/toolgroups/gettingstarted/_removefromuser', {}) .pipe( - take(1), - pluck('entity'), + map((response) => response.entity), catchError((error: HttpErrorResponse) => { return this.httpErrorManagerService.handle(error).pipe( take(1), @@ -76,12 +79,3 @@ export class DotAccountService { ); } } - -export interface DotAccountUser { - userId: string; - givenName: string; - surname: string; - newPassword?: string; - currentPassword: string; - email: string; -} diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.spec.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.spec.ts index 7f170e163a91..d3bfd28f1260 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.spec.ts @@ -1,37 +1,15 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { mockProvider } from '@ngneat/spectator/jest'; -import { throwError } from 'rxjs'; - -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { ConfirmationService } from 'primeng/api'; - -import { - DotAlertConfirmService, - DotFormatDateService, - DotHttpErrorManagerService, - DotMessageDisplayService, - DotMessageService, - DotRouterService -} from '@dotcms/data-access'; -import { CoreWebService, LoginService } from '@dotcms/dotcms-js'; +import { DotHttpErrorManagerService } from '@dotcms/data-access'; import { DotApp, DotAppsImportConfiguration, DotAppsSaveData } from '@dotcms/dotcms-models'; -import { - CoreWebServiceMock, - DotFormatDateServiceMock, - DotMessageDisplayServiceMock, - LoginServiceMock, - MockDotRouterService, - mockResponseView -} from '@dotcms/utils-testing'; -// eslint-disable-next-line import/order import * as dotUtils from '@dotcms/utils/lib/dot-utils'; +import { MockDotHttpErrorManagerService } from '@dotcms/utils-testing'; import { DotAppsService } from './dot-apps.service'; -// INFO: needs to import this way so we can spy on. - const mockDotApps = [ { allowExtraParams: true, @@ -54,45 +32,36 @@ const mockDotApps = [ describe('DotAppsService', () => { let dotAppsService: DotAppsService; let dotHttpErrorManagerService: DotHttpErrorManagerService; - let coreWebService: CoreWebService; - let httpMock: HttpTestingController; + let httpTesting: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - { - provide: LoginService, - useClass: LoginServiceMock - }, - { - provide: DotMessageDisplayService, - useClass: DotMessageDisplayServiceMock - }, - { provide: DotRouterService, useClass: MockDotRouterService }, - { provide: DotFormatDateService, useClass: DotFormatDateServiceMock }, - ConfirmationService, - DotAlertConfirmService, + provideHttpClient(), + provideHttpClientTesting(), DotAppsService, - DotHttpErrorManagerService, - mockProvider(DotMessageService) + { + provide: DotHttpErrorManagerService, + useClass: MockDotHttpErrorManagerService + } ] }); + dotAppsService = TestBed.inject(DotAppsService); dotHttpErrorManagerService = TestBed.inject(DotHttpErrorManagerService); - coreWebService = TestBed.inject(CoreWebService); - httpMock = TestBed.inject(HttpTestingController); + httpTesting = TestBed.inject(HttpTestingController); }); - it('should get apps', () => { - const url = 'v1/apps'; + afterEach(() => { + httpTesting.verify(); + }); + it('should get apps', () => { dotAppsService.get().subscribe((apps: DotApp[]) => { expect(apps).toEqual(mockDotApps); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne('/api/v1/apps'); expect(req.request.method).toBe('GET'); req.flush({ entity: mockDotApps @@ -101,13 +70,12 @@ describe('DotAppsService', () => { it('should get filtered app', () => { const filter = 'asana'; - const url = `v1/apps?filter=${filter}`; dotAppsService.get(filter).subscribe((apps: DotApp[]) => { expect(apps).toEqual([mockDotApps[1]]); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne(`/api/v1/apps?filter=${filter}`); expect(req.request.method).toBe('GET'); req.flush({ entity: [mockDotApps[1]] @@ -115,65 +83,90 @@ describe('DotAppsService', () => { }); it('should throw error on get apps and handle it', () => { - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAppsService.get().subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); + + const req = httpTesting.expectOne('/api/v1/apps'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); it('should get a specific app', () => { const appKey = '1'; - const url = `v1/apps/${appKey}`; dotAppsService.getConfigurationList(appKey).subscribe((apps: DotApp) => { expect(apps).toEqual(mockDotApps[1]); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne(`/api/v1/apps/${appKey}`); expect(req.request.method).toBe('GET'); req.flush({ entity: mockDotApps[1] }); }); + it('should get a specific configuration of an app', () => { + const appKey = 'test'; + const id = '1'; + + dotAppsService.getConfiguration(appKey, id).subscribe((app: DotApp) => { + expect(app).toEqual(mockDotApps[0]); + }); + + const req = httpTesting.expectOne(`/api/v1/apps/${appKey}/${id}`); + expect(req.request.method).toBe('GET'); + req.flush({ + entity: mockDotApps[0] + }); + }); + it('should throw error on get a specific app and handle it', () => { - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAppsService.getConfiguration('test', '1').subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); + + const req = httpTesting.expectOne('/api/v1/apps/test/1'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); it('should import apps', () => { - jest.spyOn(coreWebService, 'requestView'); const conf: DotAppsImportConfiguration = { file: null, json: { password: 'test' } }; - const sentBody = new FormData(); - sentBody.append('json', JSON.stringify(conf.json)); - sentBody.append('file', conf.file); dotAppsService.importConfiguration(conf).subscribe((status: string) => { expect(status).toEqual('OK'); }); - const req = httpMock.expectOne(`/api/v1/apps/import`); - expect(coreWebService.requestView).toHaveBeenCalledWith({ - url: `/api/v1/apps/import`, - body: sentBody, - headers: { 'Content-Type': 'multipart/form-data' }, - method: 'POST' - }); - + const req = httpTesting.expectOne('/api/v1/apps/import'); + expect(req.request.method).toBe('POST'); + expect(req.request.body instanceof FormData).toBeTruthy(); req.flush({ entity: 'OK' }); }); + it('should throw error on import apps and handle it', () => { + jest.spyOn(dotHttpErrorManagerService, 'handle'); + + const conf: DotAppsImportConfiguration = { + file: null, + json: { password: 'test' } + }; + + dotAppsService.importConfiguration(conf).subscribe(); + + const req = httpTesting.expectOne('/api/v1/apps/import'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); + }); + it('should export apps configuration', fakeAsync(() => { const blobMock = new Blob(['']); const fileName = 'asd-01EDSTVT6KGQ8CQ80PPA8717AN.tar.gz'; @@ -243,7 +236,6 @@ describe('DotAppsService', () => { const params: DotAppsSaveData = { name: { hidden: false, value: 'test' } }; - const url = `v1/apps/${appKey}/${hostId}`; dotAppsService .saveSiteConfiguration(appKey, hostId, params) @@ -251,7 +243,7 @@ describe('DotAppsService', () => { expect(response).toEqual('ok'); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne(`/api/v1/apps/${appKey}/${hostId}`); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(params); req.flush({ @@ -263,24 +255,25 @@ describe('DotAppsService', () => { const params: DotAppsSaveData = { name: { hidden: false, value: 'test' } }; - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAppsService.saveSiteConfiguration('test', '123', params).subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); + + const req = httpTesting.expectOne('/api/v1/apps/test/123'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); it('should delete a specific configuration from an app', () => { const appKey = '1'; const hostId = 'abc'; - const url = `v1/apps/${appKey}/${hostId}`; dotAppsService.deleteConfiguration(appKey, hostId).subscribe((response: string) => { expect(response).toEqual('ok'); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne(`/api/v1/apps/${appKey}/${hostId}`); expect(req.request.method).toBe('DELETE'); req.flush({ entity: 'ok' @@ -288,23 +281,24 @@ describe('DotAppsService', () => { }); it('should throw error on delete a specific app and handle it', () => { - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAppsService.deleteConfiguration('test', '123').subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); + + const req = httpTesting.expectOne('/api/v1/apps/test/123'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); it('should delete all configurations from an app', () => { const appKey = '1'; - const url = `v1/apps/${appKey}`; dotAppsService.deleteAllConfigurations(appKey).subscribe((response: string) => { expect(response).toEqual('ok'); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne(`/api/v1/apps/${appKey}`); expect(req.request.method).toBe('DELETE'); req.flush({ entity: 'ok' @@ -312,15 +306,13 @@ describe('DotAppsService', () => { }); it('should throw error on delete all configurations from an app and handle it', () => { - const error404 = mockResponseView(400); jest.spyOn(dotHttpErrorManagerService, 'handle'); - jest.spyOn(coreWebService, 'requestView').mockReturnValue(throwError(error404)); dotAppsService.deleteAllConfigurations('test').subscribe(); - expect(dotHttpErrorManagerService.handle).toHaveBeenCalledWith(mockResponseView(400)); - }); - afterEach(() => { - httpMock.verify(); + const req = httpTesting.expectOne('/api/v1/apps/test'); + req.flush(null, { status: 400, statusText: 'Bad Request' }); + + expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); }); }); diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.ts index 969c7364cc07..a78007fbec0e 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-apps/dot-apps.service.ts @@ -1,21 +1,21 @@ import { Observable } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map, pluck, take } from 'rxjs/operators'; +import { catchError, map, take } from 'rxjs/operators'; import { DotHttpErrorManagerService } from '@dotcms/data-access'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotApp, DotAppsExportConfiguration, DotAppsImportConfiguration, - DotAppsSaveData + DotAppsSaveData, + DotCMSResponse } from '@dotcms/dotcms-models'; import { getDownloadLink } from '@dotcms/utils'; -const appsUrl = `v1/apps`; +const appsUrl = '/api/v1/apps'; /** * Provide util methods to get apps in the system. @@ -24,7 +24,7 @@ const appsUrl = `v1/apps`; */ @Injectable() export class DotAppsService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private httpErrorManagerService = inject(DotHttpErrorManagerService); /** @@ -36,19 +36,15 @@ export class DotAppsService { get(filter?: string): Observable { const url = filter ? `${appsUrl}?filter=${filter}` : appsUrl; - return this.coreWebService - .requestView({ - url + return this.http.get>(url).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) + ); }) - .pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) - ); + ); } /** @@ -58,19 +54,15 @@ export class DotAppsService { * @memberof DotAppsService */ getConfigurationList(appKey: string): Observable { - return this.coreWebService - .requestView({ - url: `${appsUrl}/${appKey}` + return this.http.get>(`${appsUrl}/${appKey}`).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) + ); }) - .pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) - ); + ); } /** @@ -81,19 +73,15 @@ export class DotAppsService { * @memberof DotAppsService */ getConfiguration(appKey: string, id: string): Observable { - return this.coreWebService - .requestView({ - url: `${appsUrl}/${appKey}/${id}` + return this.http.get>(`${appsUrl}/${appKey}/${id}`).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) + ); }) - .pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) - ); + ); } /** @@ -109,16 +97,12 @@ export class DotAppsService { id: string, params: DotAppsSaveData ): Observable { - return this.coreWebService - .requestView({ - body: { - ...params - }, - method: 'POST', - url: `${appsUrl}/${appKey}/${id}` + return this.http + .post>(`${appsUrl}/${appKey}/${id}`, { + ...params }) .pipe( - pluck('entity'), + map((response) => response.entity), catchError((error: HttpErrorResponse) => { return this.httpErrorManagerService.handle(error).pipe( take(1), @@ -137,7 +121,7 @@ export class DotAppsService { exportConfiguration(conf: DotAppsExportConfiguration): Promise { let fileName = ''; - return fetch(`/api/${appsUrl}/export`, { + return fetch(`${appsUrl}/export`, { method: 'POST', cache: 'no-cache', headers: { @@ -179,22 +163,15 @@ export class DotAppsService { formData.append('json', JSON.stringify(conf.json)); formData.append('file', conf.file); - return this.coreWebService - .requestView({ - url: `/api/${appsUrl}/import`, - body: formData, - headers: { 'Content-Type': 'multipart/form-data' }, - method: 'POST' + return this.http.post>(`${appsUrl}/import`, formData).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map((err) => err.status.toString()) + ); }) - .pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map((err) => err.status.toString()) - ); - }) - ); + ); } /** @@ -205,20 +182,15 @@ export class DotAppsService { * @memberof DotAppsService */ deleteConfiguration(appKey: string, hostId: string): Observable { - return this.coreWebService - .requestView({ - method: 'DELETE', - url: `${appsUrl}/${appKey}/${hostId}` + return this.http.delete>(`${appsUrl}/${appKey}/${hostId}`).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) + ); }) - .pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) - ); + ); } /** @@ -228,19 +200,14 @@ export class DotAppsService { * @memberof DotAppsService */ deleteAllConfigurations(appKey: string): Observable { - return this.coreWebService - .requestView({ - method: 'DELETE', - url: `${appsUrl}/${appKey}` + return this.http.delete>(`${appsUrl}/${appKey}`).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) + ); }) - .pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) - ); + ); } } diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-containers/dot-containers.service.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-containers/dot-containers.service.ts index db1472664f68..f40540382c1b 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-containers/dot-containers.service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-containers/dot-containers.service.ts @@ -1,14 +1,14 @@ import { Observable } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map, pluck, take } from 'rxjs/operators'; +import { catchError, map, take } from 'rxjs/operators'; import { DotHttpErrorManagerService } from '@dotcms/data-access'; -import { CoreWebService, DotRequestOptionsArgs } from '@dotcms/dotcms-js'; import { DotActionBulkResult, + DotCMSResponse, DotContainer, DotContainerEntity, DotContainerPayload @@ -24,7 +24,7 @@ export const CONTAINER_API_URL = '/api/v1/containers/'; */ @Injectable() export class DotContainersService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private httpErrorManagerService = inject(DotHttpErrorManagerService); /** @@ -33,7 +33,10 @@ export class DotContainersService { * @memberof DotContainersService */ get(): Observable { - return this.request({ url: CONTAINER_API_URL }); + return this.http.get>(CONTAINER_API_URL).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -53,9 +56,10 @@ export class DotContainersService { includeContentType ? `&includeContentType=${includeContentType}` : '' }`; - return this.request({ - url - }); + return this.http.get>(url).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -68,9 +72,10 @@ export class DotContainersService { getFiltered(filter: string): Observable { const url = `${CONTAINER_API_URL}?filter=${filter}`; - return this.request({ - url - }); + return this.http.get>(url).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -79,13 +84,11 @@ export class DotContainersService { * @returns Observable * @memberof DotContainersService */ - create(values: DotContainerPayload): Observable { - return this.request({ - method: 'POST', - url: CONTAINER_API_URL, - body: values - }); + return this.http.post>(CONTAINER_API_URL, values).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -96,11 +99,10 @@ export class DotContainersService { * @memberof DotContainersService */ update(values: DotContainerPayload): Observable { - return this.request({ - method: 'PUT', - url: CONTAINER_API_URL, - body: values - }); + return this.http.put>(CONTAINER_API_URL, values).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -110,11 +112,14 @@ export class DotContainersService { * @memberof DotContainersService */ saveAndPublish(values: DotContainerEntity): Observable { - return this.request({ - method: 'PUT', - url: `${CONTAINER_API_URL}_savepublish`, - body: values - }); + return this.http + .put>(`${CONTAINER_API_URL}_savepublish`, values) + .pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => + this.handleError(error) + ) + ); } /** @@ -124,11 +129,16 @@ export class DotContainersService { * @memberof DotContainersService */ delete(identifiers: string[]): Observable { - return this.request({ - method: 'DELETE', - url: `${CONTAINER_API_URL}_bulkdelete`, - body: identifiers - }); + return this.http + .delete>(`${CONTAINER_API_URL}_bulkdelete`, { + body: identifiers + }) + .pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => + this.handleError(error) + ) + ); } /** @@ -140,11 +150,10 @@ export class DotContainersService { unArchive(identifiers: string[]): Observable { const url = `${CONTAINER_API_URL}_bulkunarchive`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -156,15 +165,14 @@ export class DotContainersService { archive(identifiers: string[]): Observable { const url = `${CONTAINER_API_URL}_bulkarchive`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** - * Unpublish a container00 + * Unpublish a container * @param {string[]} identifiers * @returns Observable * @memberof DotContainersService @@ -172,11 +180,10 @@ export class DotContainersService { unPublish(identifiers: string[]): Observable { const url = `${CONTAINER_API_URL}_bulkunpublish`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -188,11 +195,10 @@ export class DotContainersService { publish(identifiers: string[]): Observable { const url = `${CONTAINER_API_URL}_bulkpublish`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -204,20 +210,16 @@ export class DotContainersService { copy(identifier: string): Observable { const url = `${CONTAINER_API_URL}${identifier}/_copy`; - return this.request({ method: 'PUT', url }); + return this.http.put>(url, {}).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } - private request(options: DotRequestOptionsArgs): Observable { - const response$ = this.coreWebService.requestView(options); - - return response$.pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) + private handleError(error: HttpErrorResponse): Observable { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) ); } } diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-menu.service.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-menu.service.ts index 48f94f067f7a..8315c6699f16 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-menu.service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-menu.service.ts @@ -14,8 +14,7 @@ import { refCount } from 'rxjs/operators'; -import { DotCMSResponse } from '@dotcms/dotcms-js'; -import { DotMenu, DotMenuItem } from '@dotcms/dotcms-models'; +import { DotMenu, DotMenuItem, DotCMSResponse } from '@dotcms/dotcms-models'; @Injectable() export class DotMenuService { diff --git a/core-web/apps/dotcms-ui/src/app/api/services/dot-templates/dot-templates.service.ts b/core-web/apps/dotcms-ui/src/app/api/services/dot-templates/dot-templates.service.ts index 7ab08c9c9da5..c7578908096e 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/dot-templates/dot-templates.service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/dot-templates/dot-templates.service.ts @@ -3,11 +3,10 @@ import { Observable } from 'rxjs'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map, pluck, take } from 'rxjs/operators'; +import { catchError, map, take } from 'rxjs/operators'; import { DotHttpErrorManagerService } from '@dotcms/data-access'; -import { CoreWebService, DotRequestOptionsArgs } from '@dotcms/dotcms-js'; -import { DotActionBulkResult, DotTemplate } from '@dotcms/dotcms-models'; +import { DotActionBulkResult, DotCMSResponse, DotTemplate } from '@dotcms/dotcms-models'; export const TEMPLATE_API_URL = '/api/v1/templates/'; @@ -18,9 +17,8 @@ export const TEMPLATE_API_URL = '/api/v1/templates/'; */ @Injectable() export class DotTemplatesService { - private coreWebService = inject(CoreWebService); - private httpErrorManagerService = inject(DotHttpErrorManagerService); private http = inject(HttpClient); + private httpErrorManagerService = inject(DotHttpErrorManagerService); /** * Return a list of templates. @@ -28,7 +26,10 @@ export class DotTemplatesService { * @memberof DotTemplatesService */ get(): Observable { - return this.request({ url: TEMPLATE_API_URL }); + return this.http.get>(TEMPLATE_API_URL).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -42,9 +43,10 @@ export class DotTemplatesService { getById(id: string, version = 'working'): Observable { const url = `${TEMPLATE_API_URL}${id}/${version}`; - return this.request({ - url - }); + return this.http.get>(url).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -57,9 +59,10 @@ export class DotTemplatesService { getFiltered(filter: string): Observable { const url = `${TEMPLATE_API_URL}?filter=${filter}`; - return this.request({ - url - }); + return this.http.get>(url).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -70,11 +73,10 @@ export class DotTemplatesService { * @memberof DotTemplatesService */ create(values: DotTemplate): Observable { - return this.request({ - method: 'POST', - url: TEMPLATE_API_URL, - body: values - }); + return this.http.post>(TEMPLATE_API_URL, values).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -83,11 +85,10 @@ export class DotTemplatesService { * @memberof DotTemplatesService */ update(values: DotTemplate): Observable { - return this.request({ - method: 'PUT', - url: TEMPLATE_API_URL, - body: values - }); + return this.http.put>(TEMPLATE_API_URL, values).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -98,10 +99,11 @@ export class DotTemplatesService { */ saveAndPublish(values: DotTemplate): Observable { return this.http - .put(`${TEMPLATE_API_URL}_savepublish`, { - ...values - }) - .pipe(pluck('entity')); + .put>(`${TEMPLATE_API_URL}_savepublish`, values) + .pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -111,11 +113,16 @@ export class DotTemplatesService { * @memberof DotTemplatesService */ delete(identifiers: string[]): Observable { - return this.request({ - method: 'DELETE', - url: TEMPLATE_API_URL, - body: identifiers - }); + return this.http + .delete>(TEMPLATE_API_URL, { + body: identifiers + }) + .pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => + this.handleError(error) + ) + ); } /** @@ -127,11 +134,10 @@ export class DotTemplatesService { unArchive(identifiers: string[]): Observable { const url = `${TEMPLATE_API_URL}_unarchive`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -143,15 +149,14 @@ export class DotTemplatesService { archive(identifiers: string[]): Observable { const url = `${TEMPLATE_API_URL}_archive`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** - * Unpublish a template00 + * Unpublish a template * @param {string[]} identifiers * @returns Observable * @memberof DotTemplatesService @@ -159,11 +164,10 @@ export class DotTemplatesService { unPublish(identifiers: string[]): Observable { const url = `${TEMPLATE_API_URL}_unpublish`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -175,11 +179,10 @@ export class DotTemplatesService { publish(identifiers: string[]): Observable { const url = `${TEMPLATE_API_URL}_publish`; - return this.request({ - method: 'PUT', - url, - body: identifiers - }); + return this.http.put>(url, identifiers).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } /** @@ -191,20 +194,16 @@ export class DotTemplatesService { copy(identifier: string): Observable { const url = `${TEMPLATE_API_URL}${identifier}/_copy`; - return this.request({ method: 'PUT', url }); + return this.http.put>(url, {}).pipe( + map((response) => response.entity), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } - private request(options: DotRequestOptionsArgs): Observable { - const response$ = this.coreWebService.requestView(options); - - return response$.pipe( - pluck('entity'), - catchError((error: HttpErrorResponse) => { - return this.httpErrorManagerService.handle(error).pipe( - take(1), - map(() => null) - ); - }) + private handleError(error: HttpErrorResponse): Observable { + return this.httpErrorManagerService.handle(error).pipe( + take(1), + map(() => null) ); } } diff --git a/core-web/apps/dotcms-ui/src/app/api/services/notifications-service.ts b/core-web/apps/dotcms-ui/src/app/api/services/notifications-service.ts index fecb3f5f748a..b9f963f78c54 100644 --- a/core-web/apps/dotcms-ui/src/app/api/services/notifications-service.ts +++ b/core-web/apps/dotcms-ui/src/app/api/services/notifications-service.ts @@ -1,10 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService, DotCMSResponse } from '@dotcms/dotcms-js'; +import { DotCMSResponse, DotCMSResponseJsonObject } from '@dotcms/dotcms-models'; import { DotNotificationResponse } from '../../shared/models/notifications/notification.model'; @@ -17,53 +18,50 @@ interface DotNotificationServiceUrls { @Injectable() export class NotificationsService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private urls: DotNotificationServiceUrls; constructor() { this.urls = { - dismissNotificationsUrl: 'v1/notification/delete', - getLastNotificationsUrl: 'v1/notification/getNotifications/offset/0/limit/24', - getNotificationsUrl: 'v1/notification/getNotifications/', - markAsReadNotificationsUrl: 'v1/notification/markAsRead' + dismissNotificationsUrl: '/api/v1/notification/delete', + getLastNotificationsUrl: '/api/v1/notification/getNotifications/offset/0/limit/24', + getNotificationsUrl: '/api/v1/notification/getNotifications/', + markAsReadNotificationsUrl: '/api/v1/notification/markAsRead' }; } getLastNotifications(): Observable> { - return this.coreWebService - .requestView({ - url: this.urls.getLastNotificationsUrl - }) - .pipe(pluck('bodyJsonObject')); + return this.http + .get< + DotCMSResponseJsonObject> + >(this.urls.getLastNotificationsUrl) + .pipe(map((response) => response.bodyJsonObject)); } getAllNotifications(): Observable> { - return this.coreWebService - .requestView({ - url: this.urls.getNotificationsUrl - }) - .pipe(pluck('bodyJsonObject')); + return this.http + .get< + DotCMSResponseJsonObject> + >(this.urls.getNotificationsUrl) + .pipe(map((response) => response.bodyJsonObject)); } dismissNotifications( items: Record ): Observable> { - return this.coreWebService - .requestView({ - body: items, - method: 'PUT', - url: this.urls.dismissNotificationsUrl - }) - .pipe(pluck('bodyJsonObject')); + return this.http + .put< + DotCMSResponseJsonObject> + >(this.urls.dismissNotificationsUrl, items) + .pipe(map((response) => response.bodyJsonObject)); } markAllAsRead(): Observable> { - return this.coreWebService - .request({ - method: 'PUT', - url: this.urls.markAsReadNotificationsUrl - }) - .pipe(pluck('bodyJsonObject')); + return this.http + .put< + DotCMSResponseJsonObject> + >(this.urls.markAsReadNotificationsUrl, {}) + .pipe(map((response) => response.bodyJsonObject)); } } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-properties/dot-container-properties.component.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-properties/dot-container-properties.component.spec.ts index e0592da4e176..1e3957093076 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-properties/dot-container-properties.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-containers/dot-container-create/dot-container-properties/dot-container-properties.component.spec.ts @@ -1,7 +1,8 @@ import { of } from 'rxjs'; import { CommonModule } from '@angular/common'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { Component, CUSTOM_ELEMENTS_SCHEMA, @@ -61,6 +62,7 @@ import { } from '@dotcms/ui'; import { CoreWebServiceMock, + createFakeContentType, DotFormatDateServiceMock, DotMessageDisplayServiceMock, MockDotMessageService @@ -181,11 +183,10 @@ const containerMockData = { }; const mockContentTypes: DotCMSContentType[] = [ - { + createFakeContentType({ baseType: 'CONTENT', clazz: 'com.dotcms.contenttype.model.type.ImmutableSimpleContentType', - defaultType: false, - description: 'Activities available at desitnations', + description: 'Activities available at destinations', detailPage: 'e5f131d2-1952-4596-bbbf-28fb28021b68', fixed: false, folder: 'SYSTEM_FOLDER', @@ -204,12 +205,11 @@ const mockContentTypes: DotCMSContentType[] = [ workflows: [], fields: [], layout: [] - }, - { + }), + createFakeContentType({ baseType: 'CONTENT', clazz: 'com.dotcms.contenttype.model.type.ImmutableSimpleContentType', - defaultType: false, - description: 'Activities available at desitnations', + description: 'Activities available at destinations', detailPage: 'e5f131d2-1952-4596-bbbf-28fb28021b68', fixed: false, folder: 'SYSTEM_FOLDER', @@ -228,14 +228,14 @@ const mockContentTypes: DotCMSContentType[] = [ workflows: [], fields: [], layout: [] - } + }) ]; describe('DotContainerPropertiesComponent', () => { let fixture: ComponentFixture; let comp: DotContainerPropertiesComponent; let de: DebugElement; - let coreWebService: CoreWebService; + let httpTesting: HttpTestingController; let dotDialogService: DotAlertConfirmService; let dotRouterService: DotRouterService; const messageServiceMock = new MockDotMessageService(messages); @@ -247,11 +247,27 @@ describe('DotContainerPropertiesComponent', () => { DotContainerPropertiesComponent, DotContentEditorComponent, DotLoopEditorComponent, - DotTextareaContentMockComponent + DotTextareaContentMockComponent, + CommonModule, + DotMessagePipe, + SharedModule, + CheckboxModule, + InplaceModule, + ReactiveFormsModule, + MenuModule, + ButtonModule, + DotActionButtonComponent, + DotActionMenuButtonComponent, + DotAddToBundleComponent, + DynamicDialogModule, + DotAutofocusDirective, + BrowserAnimationsModule ], providers: [ - { provide: DotMessageService, useValue: messageServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), { provide: CoreWebService, useClass: CoreWebServiceMock }, + { provide: DotMessageService, useValue: messageServiceMock }, { provide: DotEventsSocketURL, useFactory: dotEventSocketURLFactory }, { provide: ActivatedRoute, @@ -289,42 +305,34 @@ describe('DotContainerPropertiesComponent', () => { LoggerService, { provide: DotFormatDateService, useClass: DotFormatDateServiceMock } ], - imports: [ - CommonModule, - DotMessagePipe, - SharedModule, - CheckboxModule, - InplaceModule, - ReactiveFormsModule, - MenuModule, - ButtonModule, - DotActionButtonComponent, - DotActionMenuButtonComponent, - DotAddToBundleComponent, - HttpClientTestingModule, - DynamicDialogModule, - DotAutofocusDirective, - BrowserAnimationsModule - ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); fixture = TestBed.createComponent(DotContainerPropertiesComponent); comp = fixture.componentInstance; de = fixture.debugElement; - coreWebService = TestBed.inject(CoreWebService); + httpTesting = TestBed.inject(HttpTestingController); dotDialogService = TestBed.inject(DotAlertConfirmService); dotRouterService = TestBed.inject(DotRouterService); }); + afterEach(() => { + // Flush any remaining requests (e.g., appconfiguration from DotcmsConfigService) + httpTesting?.match(() => true).forEach((req) => req.flush({})); + }); + describe('with data', () => { beforeEach(() => { - jest.spyOn(coreWebService, 'requestView').mockReturnValue( - of({ - entity: mockContentTypes, - header: (type) => (type === 'Link' ? 'test;test=test' : '10') - }) - ); fixture.detectChanges(); + // Mock the content types request + const req = httpTesting.expectOne((request) => + request.url.includes('/api/v1/contenttype') + ); + expect(req.request.method).toBe('GET'); + req.flush({ entity: mockContentTypes }); + + // Mock the app configuration request from DotcmsConfigService + const configReq = httpTesting.match('/api/v1/appconfiguration'); + configReq.forEach((r) => r.flush({ entity: {} })); }); it('should focus on title field', async () => { @@ -436,6 +444,17 @@ describe('DotContainerPropertiesComponent', () => { fixture.detectChanges(); const saveBtn = de.query(By.css('[data-testId="saveBtn"]')); saveBtn.triggerEventHandler('click'); + + // Mock the update container request + const req = httpTesting.expectOne('/api/v1/containers/'); + expect(req.request.method).toBe('PUT'); + req.flush({ + entity: { + container: containerMockData.container.container, + contentTypes: containerMockData.container.contentTypes + } + }); + tick(200); fixture.detectChanges(); expect(de.query(By.css('[data-testId="saveBtn"]')).attributes.disabled).toBeDefined(); @@ -457,14 +476,26 @@ describe('DotContainerPropertiesComponent', () => { ).toEqual(false); })); - it('should redirect to containers list after save', () => { + it('should redirect to containers list after save', fakeAsync(() => { comp.form.get('title').setValue('Hello'); fixture.detectChanges(); const saveBtn = de.query(By.css('[data-testId="saveBtn"]')); saveBtn.triggerEventHandler('click'); + + // Mock the update container request + const req = httpTesting.expectOne('/api/v1/containers/'); + expect(req.request.method).toBe('PUT'); + req.flush({ + entity: { + container: containerMockData.container.container, + contentTypes: containerMockData.container.contentTypes + } + }); + + tick(); fixture.detectChanges(); expect(dotRouterService.goToURL).toHaveBeenCalledWith('/containers'); expect(dotRouterService.goToURL).toHaveBeenCalledTimes(1); - }); + })); }); }); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.spec.ts index 1ce23a4aa08a..c0d9f6ca33b0 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.spec.ts @@ -1,8 +1,8 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { DotSessionStorageService } from '@dotcms/data-access'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotCMSClazzes, DotCMSContentType, @@ -10,7 +10,7 @@ import { DotPageContainer, DotPageContent } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock, dotcmsContentTypeBasicMock } from '@dotcms/utils-testing'; +import { dotcmsContentTypeBasicMock } from '@dotcms/utils-testing'; import { DotContainerContentletService } from './dot-container-contentlet.service'; @@ -21,9 +21,9 @@ describe('DotContainerContentletService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), DotContainerContentletService, DotSessionStorageService ] @@ -33,7 +33,10 @@ describe('DotContainerContentletService', () => { dotSessionStorageService = TestBed.inject(DotSessionStorageService); }); - it('should do a request for get the contentlet html code', () => { + it('should do a request for get the contentlet html code without variant', () => { + // Mock the DotSessionStorageService to return undefined (no variant) + jest.spyOn(dotSessionStorageService, 'getVariationId').mockReturnValue(undefined); + const pageContainer: DotPageContainer = { identifier: '1', uuid: '3' @@ -69,7 +72,7 @@ describe('DotContainerContentletService', () => { dotContainerContentletService .getContentletToContainer(pageContainer, pageContent, dotPage) .subscribe(); - httpMock.expectOne(`v1/containers/content/2?containerId=1&pageInode=2&variantName=DEFAULT`); + httpMock.expectOne('/api/v1/containers/content/2?containerId=1&pageInode=2'); }); it('should do a request for get the form html code', () => { @@ -94,7 +97,7 @@ describe('DotContainerContentletService', () => { }; dotContainerContentletService.getFormToContainer(pageContainer, form.id).subscribe(); - httpMock.expectOne(`v1/containers/form/2?containerId=1`); + httpMock.expectOne('/api/v1/containers/form/2?containerId=1'); }); it('should do a request for get the contentlet html code in a specific variant', () => { @@ -136,7 +139,9 @@ describe('DotContainerContentletService', () => { dotContainerContentletService .getContentletToContainer(pageContainer, pageContent, dotPage) .subscribe(); - httpMock.expectOne(`v1/containers/content/2?containerId=1&pageInode=2&variantName=Testing`); + httpMock.expectOne( + '/api/v1/containers/content/2?containerId=1&pageInode=2&variantName=Testing' + ); }); afterEach(() => { diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.ts index 60430cde9a8f..69150d7340a3 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/content/services/dot-container-contentlet.service.ts @@ -1,16 +1,16 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { DotSessionStorageService } from '@dotcms/data-access'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotPage, DotPageContainer, DotPageContent } from '@dotcms/dotcms-models'; +import { DotCMSResponse, DotPage, DotPageContainer, DotPageContent } from '@dotcms/dotcms-models'; @Injectable() export class DotContainerContentletService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private dotSessionStorageService = inject(DotSessionStorageService); /** @@ -28,17 +28,15 @@ export class DotContainerContentletService { page: DotPage ): Observable { const currentVariantName = this.dotSessionStorageService.getVariationId(); - const defaultUrl = `v1/containers/content/${content.identifier}?containerId=${container.identifier}&pageInode=${page.inode}`; + const defaultUrl = `/api/v1/containers/content/${content.identifier}?containerId=${container.identifier}&pageInode=${page.inode}`; const url = !currentVariantName ? defaultUrl : `${defaultUrl}&variantName=${currentVariantName}`; - return this.coreWebService - .requestView({ - url: url - }) - .pipe(pluck('entity', 'render')); + return this.http + .get>(url) + .pipe(map((response) => response.entity.render)); } /** @@ -53,10 +51,10 @@ export class DotContainerContentletService { container: DotPageContainer, formId: string ): Observable<{ render: string; content: { [key: string]: string } }> { - return this.coreWebService - .requestView({ - url: `v1/containers/form/${formId}?containerId=${container.identifier}` - }) - .pipe(pluck('entity')); + return this.http + .get< + DotCMSResponse<{ render: string; content: { [key: string]: string } }> + >(`/api/v1/containers/form/${formId}?containerId=${container.identifier}`) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.spec.ts index dc14345ef701..ded32d65cd5c 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.spec.ts @@ -1,6 +1,7 @@ import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { Component, CUSTOM_ELEMENTS_SCHEMA, @@ -12,8 +13,7 @@ import { } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, provideRouter } from '@angular/router'; import { DialogService } from 'primeng/dynamicdialog'; @@ -68,15 +68,12 @@ describe('DotEditLayoutComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [MockTemplateBuilderComponent], - imports: [ - HttpClientTestingModule, - DotEditLayoutComponent, - DotShowHideFeatureDirective, - RouterTestingModule - ], + imports: [DotEditLayoutComponent, DotShowHideFeatureDirective], schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], providers: [ - RouterTestingModule, + provideHttpClient(), + provideHttpClientTesting(), + provideRouter([]), DotSessionStorageService, DotRouterService, DotEventsService, diff --git a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.ts b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.ts index 7e1716586ae0..475cf96538a7 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/layout/dot-edit-layout/dot-edit-layout.component.ts @@ -15,7 +15,6 @@ import { DotGlobalMessageService, DotPageStateService } from '@dotcms/data-access'; -import { ResponseView } from '@dotcms/dotcms-js'; import { DotContainer, DotContainerMap, @@ -136,7 +135,7 @@ export class DotEditLayoutComponent implements OnInit, OnDestroy { .pipe(take(1)) .subscribe( (updatedPage: DotPageRender) => this.handleSuccessSaveTemplate(updatedPage), - (err: ResponseView) => this.handleErrorSaveTemplate(err), + (err: HttpErrorResponse) => this.handleErrorSaveTemplate(err), () => this.dotRouterService.allowRouteDeactivation() ); } @@ -187,7 +186,7 @@ export class DotEditLayoutComponent implements OnInit, OnDestroy { ) .subscribe( (updatedPage: DotPageRender) => this.handleSuccessSaveTemplate(updatedPage), - (err: ResponseView) => this.handleErrorSaveTemplate(err) + (err: HttpErrorResponse) => this.handleErrorSaveTemplate(err) ); } @@ -212,12 +211,12 @@ export class DotEditLayoutComponent implements OnInit, OnDestroy { /** * * Handle Error on Save template - * @param {ResponseView} err + * @param {HttpErrorResponse} err * @memberof DotEditLayoutComponent */ - private handleErrorSaveTemplate(err: ResponseView) { - this.dotGlobalMessageService.error(err.response.statusText); - this.dotHttpErrorManagerService.handle(new HttpErrorResponse(err.response)).subscribe(); + private handleErrorSaveTemplate(err: HttpErrorResponse) { + this.dotGlobalMessageService.error(err.statusText); + this.dotHttpErrorManagerService.handle(err).subscribe(); } private getRemappedContainers(containers: { diff --git a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.spec.ts index c955d01480f1..0f0119c94e24 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.spec.ts @@ -1,11 +1,7 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - import { DotRelationshipService } from './dot-relationship.service'; const cardinalities = [ @@ -27,22 +23,18 @@ describe('DotRelationshipService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotRelationshipService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotRelationshipService] }); dotRelationshipService = TestBed.inject(DotRelationshipService); httpMock = TestBed.inject(HttpTestingController); }); it('should load cardinalities', () => { - dotRelationshipService.loadCardinalities().subscribe((res: any) => { + dotRelationshipService.loadCardinalities().subscribe((res) => { expect(res).toEqual(cardinalities); }); - const req = httpMock.expectOne('v1/relationships/cardinalities'); + const req = httpMock.expectOne('/api/v1/relationships/cardinalities'); expect(req.request.method).toBe('GET'); req.flush({ entity: cardinalities }); }); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.ts b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.ts index 5ba0552fa371..da66907937a1 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/field-properties/dot-relationships-property/services/dot-relationship.service.ts @@ -1,10 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck, take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; import { DotRelationshipCardinality } from '../model/dot-relationship-cardinality.model'; @@ -16,7 +17,7 @@ import { DotRelationshipCardinality } from '../model/dot-relationship-cardinalit */ @Injectable() export class DotRelationshipService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** *Return all the cardinalities options allow @@ -25,10 +26,10 @@ export class DotRelationshipService { * @memberof DotRelationshipService */ loadCardinalities(): Observable { - return this.coreWebService - .requestView({ - url: 'v1/relationships/cardinalities' - }) - .pipe(take(1), pluck('entity')); + return this.http + .get< + DotCMSResponse + >('/api/v1/relationships/cardinalities') + .pipe(map((response) => response.entity)); } } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.spec.ts index ca102e6811a1..7408bcd654b5 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.spec.ts @@ -1,28 +1,24 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotCMSContentTypeField, DotFieldVariable } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock, dotcmsContentTypeFieldBasicMock } from '@dotcms/utils-testing'; +import { dotcmsContentTypeFieldBasicMock } from '@dotcms/utils-testing'; import { DotFieldVariablesService } from './dot-field-variables.service'; describe('DotFieldVariablesService', () => { let dotFieldVariablesService: DotFieldVariablesService; - let httpMock: HttpTestingController; + let httpTesting: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotFieldVariablesService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotFieldVariablesService] }); dotFieldVariablesService = TestBed.inject(DotFieldVariablesService); - httpMock = TestBed.inject(HttpTestingController); + httpTesting = TestBed.inject(HttpTestingController); }); it('should load field variables', () => { @@ -55,8 +51,8 @@ describe('DotFieldVariablesService', () => { expect(variables).toEqual(mockResponse.entity); }); - const req = httpMock.expectOne( - `v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables` + const req = httpTesting.expectOne( + `/api/v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables` ); expect(req.request.method).toBe('GET'); req.flush({ entity: mockResponse.entity }); @@ -88,10 +84,16 @@ describe('DotFieldVariablesService', () => { expect(variables).toEqual(mockResponse.entity); }); - const req = httpMock.expectOne( - `v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables` + const req = httpTesting.expectOne( + `/api/v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables` ); expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual({ + key: variable.key, + value: variable.value, + clazz: 'com.dotcms.contenttype.model.field.FieldVariable', + fieldId: field.id + }); req.flush(mockResponse); }); @@ -113,17 +115,17 @@ describe('DotFieldVariablesService', () => { }; dotFieldVariablesService.delete(field, variable).subscribe((variables: any) => { - expect(variables).toEqual(mockResponse); + expect(variables).toEqual(mockResponse.entity); }); - const req = httpMock.expectOne( - `v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables/id/${variable.id}` + const req = httpTesting.expectOne( + `/api/v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables/id/${variable.id}` ); expect(req.request.method).toBe('DELETE'); - req.flush({ entity: mockResponse }); + req.flush(mockResponse); }); afterEach(() => { - httpMock.verify(); + httpTesting.verify(); }); }); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.ts b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.ts index 4b8cbf0dd27d..c5f52eea6b57 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/dot-content-type-fields-variables/services/dot-field-variables.service.ts @@ -1,18 +1,18 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotCMSContentTypeField, DotFieldVariable } from '@dotcms/dotcms-models'; +import { DotCMSContentTypeField, DotCMSResponse, DotFieldVariable } from '@dotcms/dotcms-models'; /** * Provide method to handle with the Field Variables */ @Injectable() export class DotFieldVariablesService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Load Field Variables. @@ -21,11 +21,11 @@ export class DotFieldVariablesService { * @memberof FieldVariablesService */ load(field: DotCMSContentTypeField): Observable { - return this.coreWebService - .requestView({ - url: `v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables` - }) - .pipe(pluck('entity')); + return this.http + .get< + DotCMSResponse + >(`/api/v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables`) + .pipe(map((response) => response.entity)); } /** @@ -37,18 +37,17 @@ export class DotFieldVariablesService { * @memberof DotFieldVariablesService */ save(field: DotCMSContentTypeField, variable: DotFieldVariable): Observable { - return this.coreWebService - .requestView({ - body: { + return this.http + .post>( + `/api/v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables`, + { key: variable.key, value: variable.value, clazz: 'com.dotcms.contenttype.model.field.FieldVariable', fieldId: field.id - }, - method: 'POST', - url: `v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables` - }) - .pipe(pluck('entity')); + } + ) + .pipe(map((response) => response.entity)); } /** @@ -63,11 +62,10 @@ export class DotFieldVariablesService { field: DotCMSContentTypeField, variable: DotFieldVariable ): Observable { - return this.coreWebService - .requestView({ - method: 'DELETE', - url: `v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables/id/${variable.id}` - }) - .pipe(pluck('entity')); + return this.http + .delete< + DotCMSResponse + >(`/api/v1/contenttype/${field.contentTypeId}/fields/id/${field.id}/variables/id/${variable.id}`) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.spec.ts b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.spec.ts index 14daecc3c67b..a34157630f0c 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.spec.ts @@ -1,9 +1,9 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotCMSContentTypeField, DotCMSContentTypeLayoutRow } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock, dotcmsContentTypeFieldBasicMock } from '@dotcms/utils-testing'; +import { dotcmsContentTypeFieldBasicMock } from '@dotcms/utils-testing'; import { FieldService } from '.'; @@ -19,15 +19,14 @@ export const mockFieldType: FieldType = { describe('FieldService', () => { let fieldService: FieldService; - let httpMock: HttpTestingController; + let httpTesting: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [{ provide: CoreWebService, useClass: CoreWebServiceMock }, FieldService] + providers: [provideHttpClient(), provideHttpClientTesting(), FieldService] }); fieldService = TestBed.inject(FieldService); - httpMock = TestBed.inject(HttpTestingController); + httpTesting = TestBed.inject(HttpTestingController); }); it('should load field types', () => { @@ -37,7 +36,7 @@ describe('FieldService', () => { expect(res).toEqual(mockResponse); }); - const req = httpMock.expectOne('v1/fieldTypes'); + const req = httpTesting.expectOne('/api/v1/fieldTypes'); expect(req.request.method).toBe('GET'); req.flush({ entity: mockResponse }); }); @@ -67,7 +66,7 @@ describe('FieldService', () => { expect(res).toEqual(mockData); }); - const req = httpMock.expectOne(`v3/contenttype/${contentTypeId}/fields/move`); + const req = httpTesting.expectOne(`/api/v3/contenttype/${contentTypeId}/fields/move`); expect(req.request.method).toBe('PUT'); req.flush({ entity: mockData }); }); @@ -96,7 +95,7 @@ describe('FieldService', () => { } ); - const req = httpMock.expectOne(`v3/contenttype/${contentTypeId}/fields`); + const req = httpTesting.expectOne(`/api/v3/contenttype/${contentTypeId}/fields`); expect(req.request.method).toBe('DELETE'); req.flush({ entity: { deletedIds: ['1', '2'], fields: mockData } }); }); @@ -123,13 +122,13 @@ describe('FieldService', () => { expect(res[0]).toEqual(mockResponse); }); - const req = httpMock.expectOne(`v3/contenttype/${contentTypeId}/fields/1`); + const req = httpTesting.expectOne(`/api/v3/contenttype/${contentTypeId}/fields/1`); expect(req.request.method).toBe('PUT'); req.flush({ entity: [mockResponse] }); }); }); afterEach(() => { - httpMock.verify(); + httpTesting.verify(); }); }); diff --git a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.ts b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.ts index 3df0a072a446..7611d5b44888 100644 --- a/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.ts +++ b/core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/service/field.service.ts @@ -1,11 +1,15 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotCMSContentTypeField, DotCMSContentTypeLayoutRow } from '@dotcms/dotcms-models'; +import { + DotCMSContentTypeField, + DotCMSContentTypeLayoutRow, + DotCMSResponse +} from '@dotcms/dotcms-models'; import { FIELD_ICONS } from '../content-types-fields-list/content-types-fields-icon-map'; import { FieldType } from '../models'; @@ -15,7 +19,7 @@ import { FieldType } from '../models'; */ @Injectable() export class FieldService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Get the field types @@ -24,11 +28,9 @@ export class FieldService { * @memberof FieldService */ loadFieldTypes(): Observable { - return this.coreWebService - .requestView({ - url: 'v1/fieldTypes' - }) - .pipe(pluck('entity')); + return this.http + .get>('/api/v1/fieldTypes') + .pipe(map((response) => response.entity)); } /** @@ -42,15 +44,11 @@ export class FieldService { contentTypeId: string, fields: DotCMSContentTypeLayoutRow[] ): Observable { - return this.coreWebService - .requestView({ - body: { - layout: fields - }, - method: 'PUT', - url: `v3/contenttype/${contentTypeId}/fields/move` - }) - .pipe(pluck('entity')); + return this.http + .put< + DotCMSResponse + >(`/api/v3/contenttype/${contentTypeId}/fields/move`, { layout: fields }) + .pipe(map((response) => response.entity)); } /** @@ -65,15 +63,16 @@ export class FieldService { contentTypeId: string, fields: DotCMSContentTypeField[] ): Observable<{ fields: DotCMSContentTypeLayoutRow[]; deletedIds: string[] }> { - return this.coreWebService - .requestView({ - body: { - fieldsID: fields.map((field: DotCMSContentTypeField) => field.id) - }, - method: 'DELETE', - url: `v3/contenttype/${contentTypeId}/fields` - }) - .pipe(pluck('entity')); + return this.http + .delete>( + `/api/v3/contenttype/${contentTypeId}/fields`, + { + body: { + fieldsID: fields.map((field: DotCMSContentTypeField) => field.id) + } + } + ) + .pipe(map((response) => response.entity)); } /** @@ -99,14 +98,10 @@ export class FieldService { contentTypeId: string, field: DotCMSContentTypeField ): Observable { - return this.coreWebService - .requestView({ - body: { - field: field - }, - method: 'PUT', - url: `v3/contenttype/${contentTypeId}/fields/${field.id}` - }) - .pipe(pluck('entity')); + return this.http + .put< + DotCMSResponse + >(`/api/v3/contenttype/${contentTypeId}/fields/${field.id}`, { field: field }) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/apps/dotcms-ui/src/app/providers.ts b/core-web/apps/dotcms-ui/src/app/providers.ts index f54b189241a4..8101ce379c0a 100644 --- a/core-web/apps/dotcms-ui/src/app/providers.ts +++ b/core-web/apps/dotcms-ui/src/app/providers.ts @@ -35,7 +35,6 @@ import { import { ApiRoot, BrowserUtil, - CoreWebService, DotcmsConfigService, DotcmsEventsService, DotEventsSocket, @@ -121,7 +120,6 @@ const PROVIDERS: Provider[] = [ // Infrastructure services from SharedModule.forRoot() ApiRoot, BrowserUtil, - CoreWebService, DotEventsService, DotNavigationService, DotcmsConfigService, diff --git a/core-web/apps/dotcms-ui/src/app/shared/shared.module.ts b/core-web/apps/dotcms-ui/src/app/shared/shared.module.ts index af7b5eecc7af..be1809586053 100644 --- a/core-web/apps/dotcms-ui/src/app/shared/shared.module.ts +++ b/core-web/apps/dotcms-ui/src/app/shared/shared.module.ts @@ -6,7 +6,6 @@ import { DotEventsService } from '@dotcms/data-access'; import { ApiRoot, BrowserUtil, - CoreWebService, DotcmsConfigService, DotcmsEventsService, DotEventsSocket, @@ -43,7 +42,6 @@ export class SharedModule { providers: [ ApiRoot, BrowserUtil, - CoreWebService, DotEventsService, DotNavigationService, DotcmsConfigService, diff --git a/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.spec.ts b/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.spec.ts index 98c1f8a6050d..6fb4db4c0e04 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.spec.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService, Site } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; +import { Site } from '@dotcms/dotcms-js'; import { DotPageAsset, DotPageSelectorService } from './dot-page-selector.service'; @@ -121,11 +121,7 @@ describe('DotPageSelectorService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotPageSelectorService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotPageSelectorService] }); dotPageSelectorService = TestBed.inject(DotPageSelectorService); httpMock = TestBed.inject(HttpTestingController); @@ -161,7 +157,7 @@ describe('DotPageSelectorService', () => { }); const req = httpMock.expectOne( - `v1/page/search?path=about-us&onlyLiveSites=true&live=false` + `/api/v1/page/search?path=about-us&onlyLiveSites=true&live=false` ); req.flush(mockGetPagedResponse); diff --git a/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.ts b/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.ts index 0503f8c4197c..3a7fddf8dfce 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-page-selector/service/dot-page-selector.service.ts @@ -1,10 +1,12 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { flatMap, map, pluck } from 'rxjs/operators'; +import { mergeMap, map } from 'rxjs/operators'; -import { CoreWebService, Site } from '@dotcms/dotcms-js'; +import { Site } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; import { DotFolder, DotPageSelectorItem } from '../models/dot-page-selector.models'; @@ -34,12 +36,17 @@ export interface DotPageAsset { url?: string; } +// Response type for ES search endpoints that return contentlets +interface DotESSearchResponse { + contentlets: T; +} + const PAGE_BASE_TYPE_QUERY = '+basetype:5'; const MAX_RESULTS_SIZE = 20; @Injectable() export class DotPageSelectorService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Get page asset by identifier @@ -49,28 +56,24 @@ export class DotPageSelectorService { * @memberof DotPageSelectorService */ getPageById(identifier: string): Observable { - return this.coreWebService - .requestView({ - body: this.getRequestBodyQuery( - `${PAGE_BASE_TYPE_QUERY} +identifier:*${identifier}*` - ), - method: 'POST', - url: '/api/es/search' - }) + return this.http + .post< + DotESSearchResponse + >('/api/es/search', this.getRequestBodyQuery(`${PAGE_BASE_TYPE_QUERY} +identifier:*${identifier}*`)) .pipe( - pluck('contentlets'), - flatMap((pages: DotPageAsset[]) => pages), + map((response) => response.contentlets), + mergeMap((pages: DotPageAsset[]) => pages), map((page: DotPageAsset) => this.getPageSelectorItem(page)) ); } getPages(path: string): Observable { - return this.coreWebService - .requestView({ - url: `v1/page/search?path=${path}&onlyLiveSites=true&live=false` - }) + return this.http + .get< + DotCMSResponse + >(`/api/v1/page/search?path=${path}&onlyLiveSites=true&live=false`) .pipe( - pluck('entity'), + map((response) => response.entity), map((pages: DotPageAsset[]) => { return pages.map((page: DotPageAsset) => this.getPageSelectorItem(page)); }) @@ -78,14 +81,10 @@ export class DotPageSelectorService { } getFolders(path: string): Observable { - return this.coreWebService - .requestView({ - url: `/api/v1/folder/byPath`, - body: { path: path }, - method: 'POST' - }) + return this.http + .post>('/api/v1/folder/byPath', { path: path }) .pipe( - pluck('entity'), + map((response) => response.entity), map((folder: DotFolder[]) => { return folder.map((folder: DotFolder) => this.getPageSelectorItem(folder)); }) @@ -96,16 +95,12 @@ export class DotPageSelectorService { let query = '+contenttype:Host -identifier:SYSTEM_HOST +host.hostName:'; query += specific ? this.getSiteName(param) : `*${this.getSiteName(param)}*`; - return this.coreWebService - .requestView({ - body: param - ? this.getRequestBodyQuery(query) - : this.getRequestBodyQuery(query, MAX_RESULTS_SIZE), - method: 'POST', - url: '/api/es/search' - }) + return this.http + .post< + DotESSearchResponse + >('/api/es/search', param ? this.getRequestBodyQuery(query) : this.getRequestBodyQuery(query, MAX_RESULTS_SIZE)) .pipe( - pluck('contentlets'), + map((response) => response.contentlets), map((sites: Site[]) => { return sites.map((site) => { return { payload: site, label: `//${site.hostname}/` }; diff --git a/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-site-selector/dot-site-selector.component.spec.ts b/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-site-selector/dot-site-selector.component.spec.ts index 53bfa5a67621..5deb1b122427 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-site-selector/dot-site-selector.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/_common/dot-site-selector/dot-site-selector.component.spec.ts @@ -3,7 +3,8 @@ import { Observable, of as observableOf } from 'rxjs'; import { CommonModule } from '@angular/common'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { Component, DebugElement, Input } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; @@ -13,17 +14,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DotEventsService, DotMessageService, + DotSiteService, DotSystemConfigService, PaginatorService } from '@dotcms/data-access'; -import { CoreWebService, Site, SiteService } from '@dotcms/dotcms-js'; -import { DotSystemConfig } from '@dotcms/dotcms-models'; -import { - CoreWebServiceMock, - MockDotMessageService, - mockSites, - SiteServiceMock -} from '@dotcms/utils-testing'; +import { Site, SiteService } from '@dotcms/dotcms-js'; +import { MockDotMessageService, mockSites, SiteServiceMock } from '@dotcms/utils-testing'; import { DotSiteSelectorComponent } from './dot-site-selector.component'; @@ -51,44 +47,6 @@ const sites: Site[] = [ } ]; -const mockSystemConfig: DotSystemConfig = { - logos: { - loginScreen: '', - navBar: '' - }, - colors: { - primary: '#54428e', - secondary: '#3a3847', - background: '#BB30E1' - }, - releaseInfo: { - buildDate: 'June 24, 2019', - version: '5.0.0' - }, - systemTimezone: { - id: 'America/Costa_Rica', - label: 'Costa Rica', - offset: 360 - }, - languages: [], - license: { - level: 100, - displayServerId: '19fc0e44', - levelName: 'COMMUNITY EDITION', - isCommunity: true - }, - cluster: { - clusterId: 'test-cluster', - companyKeyDigest: 'test-digest' - } -}; - -class MockDotSystemConfigService { - getSystemConfig(): Observable { - return observableOf(mockSystemConfig); - } -} - @Component({ selector: 'dot-test-host-component', template: ` @@ -121,15 +79,26 @@ describe('SiteSelectorComponent', () => { DotSiteSelectorComponent, SearchableDropdownComponent, BrowserAnimationsModule, - HttpClientTestingModule, CommonModule, FormsModule ], providers: [ + provideHttpClient(), + provideHttpClientTesting(), { provide: DotMessageService, useValue: messageServiceMock }, { provide: SiteService, useValue: siteServiceMock }, - { provide: CoreWebService, useClass: CoreWebServiceMock }, - { provide: DotSystemConfigService, useClass: MockDotSystemConfigService }, + { + provide: DotSystemConfigService, + useValue: { + getSystemConfig: () => observableOf({}) + } + }, + { + provide: DotSiteService, + useValue: { + getCurrentSite: () => observableOf(mockSites[0]) + } + }, IframeOverlayService, PaginatorService, DotEventsService diff --git a/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.spec.ts b/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.spec.ts index 9a04264a24ba..17992bd135fd 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.spec.ts @@ -1,91 +1,58 @@ -import { mockProvider } from '@ngneat/spectator/jest'; import { of as observableOf } from 'rxjs'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { ConfirmationService } from 'primeng/api'; - -import { - DotAlertConfirmService, - DotFormatDateService, - DotHttpErrorManagerService, - DotMessageDisplayService, - DotMessageService, - DotRouterService -} from '@dotcms/data-access'; -import { CoreWebService, LoginService } from '@dotcms/dotcms-js'; +import { DotHttpErrorManagerService, DotRouterService } from '@dotcms/data-access'; import { DotCMSContentlet, DotCMSContentType } from '@dotcms/dotcms-models'; -import { - CoreWebServiceMock, - DotMessageDisplayServiceMock, - LoginServiceMock, - MockDotRouterService -} from '@dotcms/utils-testing'; +import { MockDotHttpErrorManagerService, MockDotRouterService } from '@dotcms/utils-testing'; import { DotContentletEditorService } from './dot-contentlet-editor.service'; import { DotMenuService } from '../../../../api/services/dot-menu.service'; describe('DotContentletEditorService', () => { - const load = () => { - // - }; - - const keyDown = () => { - // - }; - let service: DotContentletEditorService; let dotMenuService: DotMenuService; let dotRouterService: DotRouterService; - let httpMock: HttpTestingController; - let injector; + let httpTesting: HttpTestingController; beforeEach(() => { - injector = TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + TestBed.configureTestingModule({ providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotContentletEditorService, DotMenuService, - DotHttpErrorManagerService, - DotAlertConfirmService, - ConfirmationService, - DotFormatDateService, - { provide: CoreWebService, useClass: CoreWebServiceMock }, - { provide: DotRouterService, useClass: MockDotRouterService }, - { - provide: DotMessageDisplayService, - useClass: DotMessageDisplayServiceMock - }, - { - provide: LoginService, - useClass: LoginServiceMock - }, - mockProvider(DotMessageService) + { provide: DotHttpErrorManagerService, useClass: MockDotHttpErrorManagerService }, + { provide: DotRouterService, useClass: MockDotRouterService } ] }); - service = injector.inject(DotContentletEditorService); - dotMenuService = injector.inject(DotMenuService); - dotRouterService = injector.inject(DotRouterService); - httpMock = injector.inject(HttpTestingController); + service = TestBed.inject(DotContentletEditorService); + dotMenuService = TestBed.inject(DotMenuService); + dotRouterService = TestBed.inject(DotRouterService); + httpTesting = TestBed.inject(HttpTestingController); jest.spyOn(dotMenuService, 'getDotMenuId').mockReturnValue(observableOf('456')); }); + afterEach(() => { + httpTesting.verify(); + }); + it('should get action url', () => { - const url = `v1/portlet/_actionurl/test`; + const url = '/api/v1/portlet/_actionurl/test'; service.getActionUrl('test').subscribe((urlString: string) => { expect(urlString).toEqual('testString'); }); - const req = httpMock.expectOne(url); + const req = httpTesting.expectOne(url); expect(req.request.method).toBe('GET'); req.flush({ entity: 'testString' }); - httpMock.verify(); }); it('should set data to add', () => { @@ -114,8 +81,12 @@ describe('DotContentletEditorService', () => { container: '123' }, events: { - load: load, - keyDown: keyDown + load: () => { + // + }, + keyDown: () => { + // + } } }); }); diff --git a/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.ts b/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.ts index 2c89908499da..b2538704bf68 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/dot-contentlet-editor/services/dot-contentlet-editor.service.ts @@ -1,13 +1,12 @@ import { Observable, of, Subject } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, filter, map, mergeMap, pluck, take } from 'rxjs/operators'; +import { catchError, filter, map, mergeMap, take } from 'rxjs/operators'; import { DotHttpErrorManagerService } from '@dotcms/data-access'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotCMSContentlet, DotCMSContentType } from '@dotcms/dotcms-models'; +import { DotCMSContentlet, DotCMSContentType, DotCMSResponse } from '@dotcms/dotcms-models'; interface DotAddEditEvents { load?: ($event: Event) => void; @@ -32,7 +31,7 @@ export interface DotEditorAction { providedIn: 'root' }) export class DotContentletEditorService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private httpErrorManagerService = inject(DotHttpErrorManagerService); close$: Subject = new Subject(); @@ -151,12 +150,10 @@ export class DotContentletEditorService { * @memberof DotContentletEditorService */ getActionUrl(contentTypeVariable: string): Observable { - return this.coreWebService - .requestView({ - url: `v1/portlet/_actionurl/${contentTypeVariable}` - }) + return this.http + .get>(`/api/v1/portlet/_actionurl/${contentTypeVariable}`) .pipe( - pluck('entity'), + map((response) => response.entity), catchError((error: HttpErrorResponse) => { return this.httpErrorManagerService.handle(error).pipe( take(1), diff --git a/core-web/apps/dotcms-ui/src/app/view/components/dot-listing-data-table/dot-listing-data-table.component.spec.ts b/core-web/apps/dotcms-ui/src/app/view/components/dot-listing-data-table/dot-listing-data-table.component.spec.ts index b44b5d37c1b6..22a79d5b355c 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/dot-listing-data-table/dot-listing-data-table.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/dot-listing-data-table/dot-listing-data-table.component.spec.ts @@ -3,7 +3,8 @@ import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpHeaders, provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { Component, DebugElement, Input } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; @@ -18,15 +19,9 @@ import { TableModule } from 'primeng/table'; import { TooltipModule } from 'primeng/tooltip'; import { DotAlertConfirmService, DotMessageService } from '@dotcms/data-access'; -import { - CoreWebService, - DotcmsConfigService, - LoggerService, - LoginService, - StringUtils -} from '@dotcms/dotcms-js'; +import { DotcmsConfigService, LoggerService, LoginService, StringUtils } from '@dotcms/dotcms-js'; import { DotActionMenuItem } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock, MockDotMessageService } from '@dotcms/utils-testing'; +import { MockDotMessageService } from '@dotcms/utils-testing'; import { DotListingDataTableComponent } from './dot-listing-data-table.component'; @@ -69,7 +64,7 @@ class EmptyMockComponent {} }) class TestHostComponent { @Input() columns: DataTableColumn[]; - @Input() url: '/api/data'; + @Input() url = '/api/data'; @Input() actionHeaderOptions: ActionHeaderOptions; @Input() buttonActions: ButtonAction[] = []; @Input() sortOrder: string; @@ -108,7 +103,7 @@ describe('DotListingDataTableComponent', () => { let items; let enabledItems; let disabledItems; - let coreWebService: CoreWebService; + let httpMock: HttpTestingController; const favoritePagesItem = { field1: 'item7-value1', field2: 'item7-value2', @@ -121,7 +116,8 @@ describe('DotListingDataTableComponent', () => { beforeEach(() => { const messageServiceMock = new MockDotMessageService({ 'global-search': 'Global Serach', - 'No-Results-Found': 'No Results Found' + 'No-Results-Found': 'No Results Found', + 'Type-to-filter': 'Type to filter' }); TestBed.configureTestingModule({ @@ -134,14 +130,14 @@ describe('DotListingDataTableComponent', () => { { path: 'test', component: DotListingDataTableComponent } ]), MenuModule, - HttpClientTestingModule, FormsModule, ContextMenuModule, ButtonModule, TooltipModule ], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), { provide: DotMessageService, useValue: messageServiceMock }, LoggerService, DotAlertConfirmService, @@ -184,7 +180,7 @@ describe('DotListingDataTableComponent', () => { } } ]; - coreWebService = TestBed.inject(CoreWebService); + httpMock = TestBed.inject(HttpTestingController); comp = hostFixture.debugElement.query(By.css('dot-listing-data-table')).componentInstance; de = hostFixture.debugElement.query(By.css('p-table')); el = de.nativeElement; @@ -255,26 +251,37 @@ describe('DotListingDataTableComponent', () => { items = [...enabledItems, ...disabledItems]; }); - it('should have default attributes', () => { + afterEach(() => { + httpMock.verify(); + }); + + it('should have default attributes', fakeAsync(() => { + hostFixture.detectChanges(); + flushHttpRequest([]); + tick(1); hostFixture.detectChanges(); + expect(de.componentInstance.responsiveLayout).toBe('scroll'); expect(de.componentInstance.lazy).toBe(true); expect(de.componentInstance.paginator).toBe(true); - }); + })); - it('should set active element the global search on load', () => { + it('should set active element the global search on load', fakeAsync(() => { const actionHeader = hostFixture.debugElement.query(By.css('dot-action-header')); const globalSearch = actionHeader.query(By.css('input')); hostFixture.detectChanges(); + flushHttpRequest([]); + tick(1); + hostFixture.detectChanges(); expect(globalSearch.nativeElement).toBe(document.activeElement); - }); + })); it('renderer basic datatable component', fakeAsync(() => { - setRequestSpy(items); hostComponent.multipleSelection = true; hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); const rows = el.querySelectorAll('[data-testclass="testTableRow"]'); @@ -323,11 +330,11 @@ describe('DotListingDataTableComponent', () => { return item; }); - setRequestSpy(itemsWithFormat); hostComponent.columns[2].format = 'date'; hostComponent.multipleSelection = true; hostFixture.detectChanges(); + flushHttpRequest(itemsWithFormat); tick(1); hostFixture.detectChanges(); @@ -370,8 +377,8 @@ describe('DotListingDataTableComponent', () => { })); it('should renderer table without checkbox', fakeAsync(() => { - setRequestSpy(items); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); @@ -396,8 +403,8 @@ describe('DotListingDataTableComponent', () => { } ]; hostComponent.actions = fakeActions; - setRequestSpy(items); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); const rows = el.querySelectorAll('tr'); @@ -416,11 +423,11 @@ describe('DotListingDataTableComponent', () => { } } ]; - setRequestSpy(items); hostComponent.actions = fakeActions; hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); const actionButton = de.query(By.css('dot-action-menu-button')); @@ -434,22 +441,24 @@ describe('DotListingDataTableComponent', () => { it('should show the loading indicator while the data is received', fakeAsync(() => { expect(comp.loading).toEqual(true); - setRequestSpy(items); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); comp.loadCurrentPage(); + flushHttpRequest(items); tick(1); expect(comp.loading).toEqual(false); })); it('should load first page of results and set pagination to 1', fakeAsync(() => { - setRequestSpy(items); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); comp.dataTable.first = 3; comp.loadFirstPage(); + flushHttpRequest(items); tick(1); expect(comp.dataTable.first).toBe(1); expect(comp.items.length).toBe(items.length); @@ -457,9 +466,8 @@ describe('DotListingDataTableComponent', () => { it('should focus first row on arrowDown in Global Search Input', fakeAsync(() => { jest.spyOn(comp, 'focusFirstRow'); - setRequestSpy(items); - comp.loadFirstPage(); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); comp.globalSearch.nativeElement.dispatchEvent( @@ -468,29 +476,30 @@ describe('DotListingDataTableComponent', () => { expect(comp.dataTable.tableViewChild.nativeElement.rows[1]).toBe(document.activeElement); })); - it('should set the pagination size in the Table', () => { - setRequestSpy(items); + it('should set the pagination size in the Table', fakeAsync(() => { hostComponent.paginationPerPage = 5; - comp.loadFirstPage(); + hostFixture.detectChanges(); + flushHttpRequest(items); + tick(1); hostFixture.detectChanges(); expect(comp.dataTable.rows).toBe(5); - }); + })); - it('should set pagination extra parameters', () => { - setRequestSpy(items); + it('should set pagination extra parameters', fakeAsync(() => { hostComponent.paginatorExtraParams = { type: 'FORM', name: 'DotCMS' }; hostFixture.detectChanges(); + flushHttpRequest(items); + tick(1); expect(comp.paginatorService.extraParams.get('type')).toEqual('FORM'); expect(comp.paginatorService.extraParams.get('name')).toEqual('DotCMS'); - }); + })); it('should emit when a row is clicked or enter', fakeAsync(() => { - setRequestSpy(items); jest.spyOn(comp.rowWasClicked, 'emit'); - comp.loadFirstPage(); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); const firstRow: DebugElement = de.queryAll(By.css('tr'))[1]; @@ -501,12 +510,9 @@ describe('DotListingDataTableComponent', () => { })); it('should never emit when a SYSTEM TEMPLATE row is clicked or enter', fakeAsync(() => { - setRequestSpy(items); jest.spyOn(comp.rowWasClicked, 'emit'); - - comp.loadFirstPage(); - hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); @@ -518,10 +524,9 @@ describe('DotListingDataTableComponent', () => { })); it('should set pContextMenuRowDisabled correctly', fakeAsync(() => { - setRequestSpy(items); jest.spyOn(comp.rowWasClicked, 'emit'); - comp.loadFirstPage(); hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); const enabledRow = document.querySelectorAll('[data-testclass="testTableRow"]')[0]; @@ -545,10 +550,10 @@ describe('DotListingDataTableComponent', () => { let bodyCheckboxes: DebugElement[]; beforeEach(fakeAsync(() => { - setRequestSpy(items); hostComponent.checkbox = true; hostFixture.detectChanges(); + flushHttpRequest(items); tick(1); hostFixture.detectChanges(); bodyCheckboxes = de.queryAll(By.css('p-tablecheckbox')); @@ -562,8 +567,8 @@ describe('DotListingDataTableComponent', () => { }); it('should renders the dot empty state component if items array is empty', fakeAsync(() => { - setRequestSpy([]); hostFixture.detectChanges(); + flushHttpRequest([]); tick(1); hostFixture.detectChanges(); const emptyState = de.query(By.css('dot-empty-state')); @@ -571,20 +576,22 @@ describe('DotListingDataTableComponent', () => { })); it('should show no results message if filtered content is empty', fakeAsync(() => { - setRequestSpy([]); hostFixture.detectChanges(); + flushHttpRequest([]); tick(1); comp.globalSearch.nativeElement.value = 'test'; comp.globalSearch.nativeElement.dispatchEvent(new Event('input')); tick(de.componentInstance.filterDelay + 1); + flushHttpRequest([]); // Flush the HTTP request triggered by the filter + tick(1); hostFixture.detectChanges(); const noResults = de.query(By.css('[data-testid="listing-datatable__empty"]')); expect(noResults.nativeElement.textContent.trim()).toEqual('No Results Found'); })); it('should hide entries for system content types', fakeAsync(() => { - setRequestSpy([...items, favoritePagesItem]); hostFixture.detectChanges(); + flushHttpRequest([...items, favoritePagesItem]); tick(1); hostFixture.detectChanges(); const row = de.query(By.css('[data-testId="row-dotFavoritePage"]')); @@ -592,12 +599,15 @@ describe('DotListingDataTableComponent', () => { expect(entriesColumn.nativeElement.textContent).toBeFalsy(); })); - function setRequestSpy(response: any): void { - jest.spyOn(coreWebService, 'requestView').mockReturnValue( - of({ - entity: response, - header: (type: any) => (type === 'Link' ? 'test;test=test' : '10') - }) as any - ); + function flushHttpRequest(response: any): void { + let headers = new HttpHeaders(); + headers = headers.set('Link', 'test;test=test'); + headers = headers.set('X-Pagination-Current-Page', '1'); + headers = headers.set('X-Pagination-Link-Pages', '5'); + headers = headers.set('X-Pagination-Per-Page', '40'); + headers = headers.set('X-Pagination-Total-Entries', '10'); + + const req = httpMock.expectOne(() => true); + req.flush({ entity: response }, { headers }); } }); diff --git a/core-web/apps/dotcms-ui/src/app/view/components/dot-persona-selector/dot-persona-selector.component.spec.ts b/core-web/apps/dotcms-ui/src/app/view/components/dot-persona-selector/dot-persona-selector.component.spec.ts index ea81e8b6409d..4eb12f945dfd 100644 --- a/core-web/apps/dotcms-ui/src/app/view/components/dot-persona-selector/dot-persona-selector.component.spec.ts +++ b/core-web/apps/dotcms-ui/src/app/view/components/dot-persona-selector/dot-persona-selector.component.spec.ts @@ -1,12 +1,8 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; import { of } from 'rxjs'; import { provideHttpClient } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { Component, Input } from '@angular/core'; import { provideAnimations } from '@angular/platform-browser/animations'; import { ConfirmationService } from 'primeng/api'; @@ -23,11 +19,10 @@ import { DotWorkflowActionsFireService, PaginatorService } from '@dotcms/data-access'; -import { CoreWebService, LoginService, SiteService } from '@dotcms/dotcms-js'; +import { LoginService, SiteService } from '@dotcms/dotcms-js'; import { DotPersona, DotSystemConfig } from '@dotcms/dotcms-models'; import { cleanUpDialog, - CoreWebServiceMock, DotMessageDisplayServiceMock, LoginServiceMock, MockDotMessageService, @@ -41,24 +36,6 @@ import { DotPersonaSelectorComponent } from './dot-persona-selector.component'; import { IframeOverlayService } from '../_common/iframe/service/iframe-overlay.service'; import { DotAddPersonaDialogComponent } from '../dot-add-persona-dialog/dot-add-persona-dialog.component'; -@Component({ - selector: 'dot-host-component', - template: ` - - `, - standalone: false -}) -class HostTestComponent { - @Input() disabled: boolean; - - selectedPersonaHandler(_$event) {} - - deletePersonaHandler(_$event) {} -} - class TestPaginatorService { filter: string; url: string; @@ -71,8 +48,7 @@ class TestPaginatorService { } describe('DotPersonaSelectorComponent', () => { - let spectator: Spectator; - let component: DotPersonaSelectorComponent; + let spectator: SpectatorHost; let paginatorService: PaginatorService; const defaultPersona: DotPersona = mockDotPersona; const messageServiceMock = new MockDotMessageService({ @@ -120,9 +96,8 @@ describe('DotPersonaSelectorComponent', () => { } } - const createComponent = createComponentFactory({ - component: HostTestComponent, - imports: [DotPersonaSelectorComponent], + const createHost = createHostFactory({ + component: DotPersonaSelectorComponent, componentProviders: [ { provide: PaginatorService, useClass: TestPaginatorService }, { provide: IframeOverlayService, useClass: IframeOverlayService } @@ -142,7 +117,6 @@ describe('DotPersonaSelectorComponent', () => { }, { provide: LoginService, useClass: LoginServiceMock }, { provide: SiteService, useValue: siteServiceMock }, - { provide: CoreWebService, useClass: CoreWebServiceMock }, { provide: DotRouterService, useClass: MockDotRouterService }, { provide: DotSystemConfigService, useClass: MockDotSystemConfigService }, DotHttpErrorManagerService, @@ -155,24 +129,30 @@ describe('DotPersonaSelectorComponent', () => { }); const openOverlay = () => { - component.disabled = false; + spectator.component.disabled = false; const personaSelectedItem = spectator.query('dot-persona-selected-item'); personaSelectedItem.dispatchEvent(new MouseEvent('click')); spectator.detectChanges(); }; beforeEach(() => { - spectator = createComponent(); - component = spectator.query(DotPersonaSelectorComponent); - paginatorService = component.paginationService; + spectator = createHost( + ``, + { + hostProps: { + disabled: false + } + } + ); + paginatorService = spectator.component.paginationService; spectator.detectChanges(); }); it('should emit the selected persona', () => { - jest.spyOn(component.selected, 'emit'); + jest.spyOn(spectator.component.selected, 'emit'); spectator.triggerEventHandler('dot-searchable-dropdown', 'switch', defaultPersona); - expect(component.selected.emit).toHaveBeenCalledWith(defaultPersona); - expect(component.selected.emit).toHaveBeenCalledTimes(1); + expect(spectator.component.selected.emit).toHaveBeenCalledWith(defaultPersona); + expect(spectator.component.selected.emit).toHaveBeenCalledTimes(1); }); it('should call filter change with keyword', () => { @@ -193,41 +173,37 @@ describe('DotPersonaSelectorComponent', () => { it('should set dot-searchable-dropdown with right attributes', () => { // Initialize totalRecords - component.personas = [mockDotPersona]; - component.totalRecords = component.personas.length; + spectator.component.personas = [mockDotPersona]; + spectator.component.totalRecords = spectator.component.personas.length; spectator.detectChanges(); - expect(component.searchableDropdown.labelPropertyName).toBe('name'); - expect(component.searchableDropdown.width).toBe('448px'); - expect(component.searchableDropdown.overlayWidth).toBe('300px'); - expect(component.searchableDropdown.rows).toBe(10); - expect(component.totalRecords).toBe(1); + expect(spectator.component.searchableDropdown.labelPropertyName).toBe('name'); + expect(spectator.component.searchableDropdown.width).toBe('448px'); + expect(spectator.component.searchableDropdown.overlayWidth).toBe('300px'); + expect(spectator.component.searchableDropdown.rows).toBe(10); + expect(spectator.component.totalRecords).toBe(1); }); it('should set dot-persona-selected-item with right attributes', () => { const personaSelectedItem = spectator.query('dot-persona-selected-item'); expect(personaSelectedItem.getAttribute('appendTo')).toBe('target'); - // In Angular 20, ng-reflect-* attributes are not available - // Verify tooltip position attribute (passed as input to the component) expect(personaSelectedItem.getAttribute('tooltipPosition')).toBe('bottom'); - // Verify the displayed content (persona name or 'Default Visitor') const nameSpan = spectator.query('dot-persona-selected-item .dot-persona-selector__name'); expect(nameSpan?.textContent?.trim()).toBe('Default Visitor'); }); it('should call toggle when selected dot-persona-selected-item', async () => { - jest.spyOn(component.searchableDropdown, 'toggleOverlayPanel'); + jest.spyOn(spectator.component.searchableDropdown, 'toggleOverlayPanel'); await spectator.fixture.whenStable(); const selectedItem = spectator.query('dot-persona-selected-item'); spectator.click(selectedItem); - expect(component.searchableDropdown.toggleOverlayPanel).toHaveBeenCalled(); + expect(spectator.component.searchableDropdown.toggleOverlayPanel).toHaveBeenCalled(); }); it('should have highlighted persona option once the dropdown in loaded', async () => { - // Setup personas data - component.personas = [mockDotPersona]; - component.totalRecords = 1; + spectator.component.personas = [mockDotPersona]; + spectator.component.totalRecords = 1; spectator.detectChanges(); await spectator.fixture.whenStable(); @@ -240,11 +216,9 @@ describe('DotPersonaSelectorComponent', () => { expect(personaOption.classList.contains('highlight')).toEqual(true); }); - // TODO: this test fails ramdomly when all tests are ran, a fix needs to be done it('should dot-persona-selector-option template with right params', async () => { - // Setup personas data - component.personas = [mockDotPersona]; - component.totalRecords = 1; + spectator.component.personas = [mockDotPersona]; + spectator.component.totalRecords = 1; spectator.detectChanges(); await spectator.fixture.whenStable(); @@ -255,7 +229,6 @@ describe('DotPersonaSelectorComponent', () => { const mockPersonaData = { ...mockDotPersona, label: 'Global Investor' }; const personaOption = spectator.query('dot-persona-selector-option'); expect(personaOption).toBeTruthy(); - // Access component instance through debugElement const personaComponent = spectator.debugElement.query( (el) => el.name === 'dot-persona-selector-option' ); @@ -265,13 +238,12 @@ describe('DotPersonaSelectorComponent', () => { }); it('should execute "change" event from dot-persona-selector-option', async () => { - // Setup personas data - component.personas = [mockDotPersona]; - component.totalRecords = 1; + spectator.component.personas = [mockDotPersona]; + spectator.component.totalRecords = 1; spectator.detectChanges(); await spectator.fixture.whenStable(); - jest.spyOn(component.selected, 'emit'); + jest.spyOn(spectator.component.selected, 'emit'); openOverlay(); await spectator.fixture.whenStable(); spectator.detectChanges(); @@ -281,17 +253,17 @@ describe('DotPersonaSelectorComponent', () => { ); expect(personaOptionDebugElement).toBeTruthy(); personaOptionDebugElement.triggerEventHandler('switch', defaultPersona); - expect(component.selected.emit).toHaveBeenCalledWith(defaultPersona); - expect(component.selected.emit).toHaveBeenCalledTimes(1); + expect(spectator.component.selected.emit).toHaveBeenCalledWith(defaultPersona); + expect(spectator.component.selected.emit).toHaveBeenCalledTimes(1); }); - xit('should execute "delete" event from dot-persona-selector-option', async () => { + it('should execute "delete" event from dot-persona-selector-option', async () => { await spectator.fixture.whenStable(); - jest.spyOn(component.delete, 'emit'); + jest.spyOn(spectator.component.delete, 'emit'); openOverlay(); spectator.triggerEventHandler('dot-persona-selector-option', 'delete', defaultPersona); - expect(component.delete.emit).toHaveBeenCalledWith({ + expect(spectator.component.delete.emit).toHaveBeenCalledWith({ ...defaultPersona, label: 'Global Investor' }); @@ -301,19 +273,19 @@ describe('DotPersonaSelectorComponent', () => { let personaDialog: DotAddPersonaDialogComponent; beforeEach(() => { - personaDialog = component.personaDialog; + personaDialog = spectator.component.personaDialog; }); it('should toggle Overlay Panel, pass the search as name if present and open add form', () => { openOverlay(); const addPersonaIcon = spectator.query('p-button'); - jest.spyOn(component.searchableDropdown, 'toggleOverlayPanel'); + jest.spyOn(spectator.component.searchableDropdown, 'toggleOverlayPanel'); spectator.triggerEventHandler('dot-searchable-dropdown', 'filterChange', 'Bill'); spectator.click(addPersonaIcon); spectator.detectChanges(); - expect(component.searchableDropdown.toggleOverlayPanel).toHaveBeenCalled(); + expect(spectator.component.searchableDropdown.toggleOverlayPanel).toHaveBeenCalled(); expect(personaDialog.visible).toBe(true); expect(personaDialog.personaName).toBe('Bill'); personaDialog.visible = false; @@ -321,9 +293,9 @@ describe('DotPersonaSelectorComponent', () => { }); it('should emit persona and refresh the list on Add new persona', () => { - jest.spyOn(component.selected, 'emit'); + jest.spyOn(spectator.component.selected, 'emit'); jest.spyOn(paginatorService, 'getWithOffset').mockReturnValue(of([mockDotPersona])); - jest.spyOn(component.searchableDropdown, 'resetPanelMinHeight'); + jest.spyOn(spectator.component.searchableDropdown, 'resetPanelMinHeight'); spectator.triggerEventHandler( 'dot-add-persona-dialog', @@ -331,12 +303,12 @@ describe('DotPersonaSelectorComponent', () => { defaultPersona ); - expect(component.selected.emit).toHaveBeenCalledWith(defaultPersona); - expect(component.selected.emit).toHaveBeenCalledTimes(1); + expect(spectator.component.selected.emit).toHaveBeenCalledWith(defaultPersona); + expect(spectator.component.selected.emit).toHaveBeenCalledTimes(1); expect(paginatorService.filter).toEqual(''); expect(paginatorService.getWithOffset).toHaveBeenCalledWith(0); expect(paginatorService.getWithOffset).toHaveBeenCalledTimes(1); - expect(component.searchableDropdown.resetPanelMinHeight).toHaveBeenCalled(); + expect(spectator.component.searchableDropdown.resetPanelMinHeight).toHaveBeenCalled(); }); }); @@ -344,7 +316,7 @@ describe('DotPersonaSelectorComponent', () => { let iframeOverlayService: IframeOverlayService; beforeEach(() => { - iframeOverlayService = component.iframeOverlayService; + iframeOverlayService = spectator.component.iframeOverlayService; }); it('should call hide event on hide persona list', () => { diff --git a/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.spec.ts b/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.spec.ts index 0ac47b4d7b1d..e35f26de4624 100644 --- a/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.spec.ts +++ b/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.spec.ts @@ -1,113 +1,112 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + createHttpFactory, + HttpMethod, + mockProvider, + SpectatorHttp, + SpyObject +} from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; - -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; - -import { ApiRoot, UserModel, LoggerService, StringUtils, CoreWebService } from '@dotcms/dotcms-js'; -import { DotAjaxActionResponseView, DotCurrentUser } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; +import { DotAjaxActionResponseView, DotBundle, DotCurrentUser } from '@dotcms/dotcms-models'; import { AddToBundleService } from './add-to-bundle.service'; import { DotCurrentUserService } from '../dot-current-user/dot-current-user.service'; +const MOCK_USER_ID = '1234'; + +const mockCurrentUser: DotCurrentUser = { + userId: MOCK_USER_ID, + givenName: 'Test', + surname: 'User', + roleId: 'admin', + email: 'test@test.com', + admin: true +}; + +const mockBundleItems: DotBundle[] = [ + { + name: 'My bundle', + id: '1234' + }, + { + name: 'My bundle 2', + id: '1sdf5-23fs-dsf2-sf3oj23p4p42d' + } +]; + +const mockBundleResponse = { + bodyJsonObject: { + items: mockBundleItems + } +}; + +const mockAddToBundleResponse: DotAjaxActionResponseView = { + errorMessages: [], + total: 1, + bundleId: '1234-id-7890-entifier', + errors: 0, + _body: {} +}; + describe('AddToBundleService', () => { - let addToBundleService: AddToBundleService; - let dotCurrentUserService: DotCurrentUserService; - let httpMock: HttpTestingController; + let spectator: SpectatorHttp; + let dotCurrentUserService: SpyObject; + + const createHttp = createHttpFactory({ + service: AddToBundleService, + providers: [mockProvider(DotCurrentUserService)] + }); beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - ApiRoot, - UserModel, - LoggerService, - StringUtils, - AddToBundleService, - DotCurrentUserService - ] - }); - addToBundleService = TestBed.inject(AddToBundleService); - dotCurrentUserService = TestBed.inject(DotCurrentUserService); - httpMock = TestBed.inject(HttpTestingController); + spectator = createHttp(); + dotCurrentUserService = spectator.inject(DotCurrentUserService); }); - it('should get bundle list', (done) => { - jest.spyOn(dotCurrentUserService, 'getCurrentUser').mockReturnValue( - of({ - userId: '1234' - }) - ); - - const mockBundleItems = [ - { - name: 'My bundle', - id: '1234' - }, - { - name: 'My bundle 2', - id: '1sdf5-23fs-dsf2-sf3oj23p4p42d' - } - ]; - - const mockResponse = { - idenitier: 'id', - items: mockBundleItems, - label: 'name', - numRows: 2 - }; - - addToBundleService.getBundles().subscribe((items: any) => { - expect(items).toBe(mockResponse.items); - done(); + describe('getBundles', () => { + beforeEach(() => { + const { of } = jest.requireActual('rxjs'); + dotCurrentUserService.getCurrentUser.mockReturnValue(of(mockCurrentUser)); }); - const req = httpMock.expectOne('api/bundle/getunsendbundles/userid/1234'); - expect(req.request.method).toBe('GET'); - req.flush(mockResponse); + it('should get bundle list for current user', () => { + spectator.service.getBundles().subscribe((items) => { + expect(items).toEqual(mockBundleItems); + }); + + const req = spectator.expectOne( + `/api/bundle/getunsendbundles/userid/${MOCK_USER_ID}`, + HttpMethod.GET + ); + + req.flush(mockBundleResponse); + }); }); - it('should do a post request and add to bundle', (done) => { - const mockResponse = { - errorMessages: [], - total: 1, - bundleId: '1234-id-7890-entifier', - errors: 0, - _body: {} - }; - - const mockBundleData = { - id: '1234', - name: 'my bundle' - }; - - const assetIdentifier = '1234567890'; - - addToBundleService - .addToBundle(assetIdentifier, mockBundleData) - .subscribe((action: DotAjaxActionResponseView) => { - expect(action).toEqual(mockResponse); - done(); + describe('addToBundle', () => { + it('should do a post request and add to bundle', () => { + const mockBundleData: DotBundle = { + id: '1234', + name: 'my bundle' + }; + const assetIdentifier = '1234567890'; + + spectator.service.addToBundle(assetIdentifier, mockBundleData).subscribe((response) => { + expect(response).toEqual(mockAddToBundleResponse); }); - const req = httpMock.expectOne((_req) => true); - expect( - req.request.url.indexOf( - 'DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/addToBundle' - ) - ).toBeGreaterThan(-1); - expect(req.request.method).toEqual('POST'); - expect(req.request.body).toEqual( - `assetIdentifier=${assetIdentifier}&bundleName=${mockBundleData.name}&bundleSelect=${mockBundleData.id}` - ); - req.flush(mockResponse); - }); + const req = spectator.expectOne( + '/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/addToBundle', + HttpMethod.POST + ); + + expect(req.request.body).toEqual( + `assetIdentifier=${assetIdentifier}&bundleName=${mockBundleData.name}&bundleSelect=${mockBundleData.id}` + ); + expect(req.request.headers.get('Content-Type')).toBe( + 'application/x-www-form-urlencoded' + ); - afterEach(() => { - httpMock.verify(); + req.flush(mockAddToBundleResponse); + }); }); }); diff --git a/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.ts b/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.ts index 11ee6ab37f43..42029bbe07af 100644 --- a/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.ts +++ b/core-web/libs/data-access/src/lib/add-to-bundle/add-to-bundle.service.ts @@ -1,26 +1,27 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { map, mergeMap, pluck } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotAjaxActionResponseView, DotBundle, DotCurrentUser } from '@dotcms/dotcms-models'; +import { + DotAjaxActionResponseView, + DotBundle, + DotCurrentUser, + DotCMSResponseJsonObject +} from '@dotcms/dotcms-models'; import { DotCurrentUserService } from '../dot-current-user/dot-current-user.service'; @Injectable() export class AddToBundleService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private currentUser = inject(DotCurrentUserService); - private bundleUrl = `api/bundle/getunsendbundles/userid`; - - /* - TODO: I had to do this because this line concat 'api/' into the URL - https://github.com/dotCMS/dotcms-js/blob/master/src/core/core-web.service.ts#L169 -*/ - private addToBundleUrl = `/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/addToBundle`; + private bundleUrl = '/api/bundle/getunsendbundles/userid'; + private addToBundleUrl = + '/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/addToBundle'; /** * Get bundle items @@ -30,13 +31,13 @@ export class AddToBundleService { getBundles(): Observable { return this.currentUser.getCurrentUser().pipe( mergeMap((user: DotCurrentUser) => { - return this.coreWebService - .requestView({ - url: `${this.bundleUrl}/${user.userId}` - }) - .pipe(pluck('bodyJsonObject', 'items')); + return this.http + .get< + DotCMSResponseJsonObject<{ items: DotBundle[] }> + >(`${this.bundleUrl}/${user.userId}`) + .pipe(map((response) => response.bodyJsonObject.items)); }) - ) as Observable; + ); } /** @@ -50,15 +51,14 @@ export class AddToBundleService { assetIdentifier: string, bundleData: DotBundle ): Observable { - return this.coreWebService - .request({ - body: `assetIdentifier=${assetIdentifier}&bundleName=${bundleData.name}&bundleSelect=${bundleData.id}`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - method: 'POST', - url: this.addToBundleUrl - }) - .pipe(map((res) => res as DotAjaxActionResponseView)); + const headers = new HttpHeaders({ + 'Content-Type': 'application/x-www-form-urlencoded' + }); + + const body = `assetIdentifier=${assetIdentifier}&bundleName=${bundleData.name}&bundleSelect=${bundleData.id}`; + + return this.http + .post(this.addToBundleUrl, body, { headers }) + .pipe(map((res) => res)); } } diff --git a/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.spec.ts b/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.spec.ts index 11b701e5d863..e13cce08b92d 100644 --- a/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.spec.ts @@ -1,56 +1,57 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - -import { DotContentletLockerService } from './dot-contentlet-locker.service'; +import { + DotContentletLockerService, + DotContentletLockResponse +} from './dot-contentlet-locker.service'; describe('DotContentletLockerService', () => { - let dotContentletLockerService: DotContentletLockerService; + let service: DotContentletLockerService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotContentletLockerService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotContentletLockerService] }); - dotContentletLockerService = TestBed.inject(DotContentletLockerService); + service = TestBed.inject(DotContentletLockerService); httpMock = TestBed.inject(HttpTestingController); }); - it('should lock a content asset', () => { - const inode = '123'; - dotContentletLockerService.lock(inode).subscribe((lockInfo: any) => { - expect(lockInfo).toEqual({ message: 'locked' }); + afterEach(() => { + httpMock.verify(); + }); + + it('should lock a contentlet', () => { + const mockResponse: DotContentletLockResponse = { + id: 'test-id', + inode: 'test-inode', + message: 'Content locked' + }; + + service.lock('test-inode').subscribe((response: DotContentletLockResponse) => { + expect(response).toEqual(mockResponse); }); - const req = httpMock.expectOne(`/api/content/lock/inode/${inode}`); + const req = httpMock.expectOne('/api/content/lock/inode/test-inode'); expect(req.request.method).toBe('PUT'); - req.flush({ - message: 'locked' - }); + req.flush({ bodyJsonObject: mockResponse }); }); - it('should unlock a content asset', () => { - const inode = '123'; - dotContentletLockerService.unlock(inode).subscribe((lockInfo: any) => { - expect(lockInfo).toEqual({ message: 'locked' }); - }); + it('should unlock a contentlet', () => { + const mockResponse: DotContentletLockResponse = { + id: 'test-id', + inode: 'test-inode', + message: 'Content unlocked' + }; - const req = httpMock.expectOne(`/api/content/unlock/inode/${inode}`); - expect(req.request.method).toBe('PUT'); - req.flush({ - message: 'locked' + service.unlock('test-inode').subscribe((response: DotContentletLockResponse) => { + expect(response).toEqual(mockResponse); }); - }); - afterEach(() => { - httpMock.verify(); + const req = httpMock.expectOne('/api/content/unlock/inode/test-inode'); + expect(req.request.method).toBe('PUT'); + req.flush({ bodyJsonObject: mockResponse }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.ts b/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.ts index 1b8373f54cba..f37b4b804b81 100644 --- a/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.ts +++ b/core-web/libs/data-access/src/lib/dot-contentlet-locker/dot-contentlet-locker.service.ts @@ -1,10 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; +import { DotCMSResponseJsonObject } from '@dotcms/dotcms-models'; export interface DotContentletLockResponse { id: string; @@ -21,7 +22,7 @@ export interface DotContentletLockResponse { */ @Injectable() export class DotContentletLockerService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Lock a content asset @@ -31,12 +32,11 @@ export class DotContentletLockerService { * @memberof PageViewService */ lock(inode: string): Observable { - return this.coreWebService - .requestView({ - method: 'PUT', - url: `/api/content/lock/inode/${inode}` - }) - .pipe(pluck('bodyJsonObject')); + return this.http + .put< + DotCMSResponseJsonObject + >(`/api/content/lock/inode/${inode}`, {}) + .pipe(map((response) => response.bodyJsonObject)); } /** @@ -47,11 +47,10 @@ export class DotContentletLockerService { * @memberof PageViewService */ unlock(inode: string): Observable { - return this.coreWebService - .requestView({ - method: 'PUT', - url: `/api/content/unlock/inode/${inode}` - }) - .pipe(pluck('bodyJsonObject')); + return this.http + .put< + DotCMSResponseJsonObject + >(`/api/content/unlock/inode/${inode}`, {}) + .pipe(map((response) => response.bodyJsonObject)); } } diff --git a/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.spec.ts b/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.spec.ts index 4d70f4200b1f..bce14ac38b75 100644 --- a/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.spec.ts @@ -1,63 +1,74 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; +import { DotCrudService } from './dot-crud.service'; -import { DotCrudService } from '.'; - -describe('CrudService', () => { - let dotCrudService: DotCrudService; +describe('DotCrudService', () => { + let service: DotCrudService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [{ provide: CoreWebService, useClass: CoreWebServiceMock }, DotCrudService] + providers: [provideHttpClient(), provideHttpClientTesting(), DotCrudService] }); - dotCrudService = TestBed.inject(DotCrudService); + service = TestBed.inject(DotCrudService); httpMock = TestBed.inject(HttpTestingController); }); - it('should post data and return an entity', () => { - const body = { - clazz: 'com.dotcms.contenttype.model.type.ImmutableSimpleContentType', - defaultType: false, - description: 'This is the content type description', - fixed: false, - folder: 'SYSTEM_FOLDER', - host: '12345-host', - name: 'A content type', - owner: 'user.id.1', - system: false, - variable: 'aContentType' - }; - - const mockResponse = { - entity: [ - Object.assign({}, body, { - fields: [], - iDate: 1495670226000, - id: '1234-id-7890-entifier', - modDate: 1495670226000, - multilingualable: false, - system: false, - versionable: true - }) - ] - }; - - dotCrudService.postData('v1/urldemo', body).subscribe((result) => { - expect(result[0]).toEqual(mockResponse.entity[0]); + afterEach(() => { + httpMock.verify(); + }); + + it('should post data', () => { + const mockData = { name: 'test' }; + const mockResponse = { id: '123', name: 'test' }; + + service.postData('/api/v1/test', mockData).subscribe((response) => { + expect(response).toEqual(mockResponse); }); - const req = httpMock.expectOne('v1/urldemo'); + const req = httpMock.expectOne('/api/v1/test'); expect(req.request.method).toBe('POST'); - expect(req.request.body).toBe(body); - req.flush({ entity: mockResponse.entity }); + expect(req.request.body).toEqual(mockData); + req.flush({ entity: mockResponse }); }); - afterEach(() => { - httpMock.verify(); + it('should put data', () => { + const mockData = { name: 'updated' }; + const mockResponse = { id: '123', name: 'updated' }; + + service.putData('/api/v1/test', mockData).subscribe((response) => { + expect(response).toEqual(mockResponse); + }); + + const req = httpMock.expectOne('/api/v1/test'); + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(mockData); + req.flush({ entity: mockResponse }); + }); + + it('should get data by id', () => { + const mockResponse = { id: '123', name: 'test' }; + + service.getDataById('/api/v1/test', '123').subscribe((response) => { + expect(response).toEqual(mockResponse); + }); + + const req = httpMock.expectOne('/api/v1/test/id/123'); + expect(req.request.method).toBe('GET'); + req.flush({ entity: mockResponse }); + }); + + it('should delete data', () => { + const mockResponse = { success: true }; + + service.delete('/api/v1/test', '123').subscribe((response) => { + expect(response).toEqual(mockResponse); + }); + + const req = httpMock.expectOne('/api/v1/test/123'); + expect(req.request.method).toBe('DELETE'); + req.flush({ entity: mockResponse }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.ts b/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.ts index 2c9de7a724c5..0bbcf5373413 100644 --- a/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.ts +++ b/core-web/libs/data-access/src/lib/dot-crud/dot-crud.service.ts @@ -1,19 +1,22 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; /** - * Provides util listing methods - * @export - * @class CrudService - */ + /** + * @deprecated This service is deprecated. Use specific data-access services instead. + * Provides util listing methods. + * @export + * @class DotCrudService + */ @Injectable() export class DotCrudService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Will do a POST request and return the response to the url provide @@ -24,13 +27,12 @@ export class DotCrudService { * @memberof CrudService */ public postData(baseUrl: string, data: K): Observable { - return this.coreWebService - .requestView({ - body: data, - method: 'POST', - url: `${baseUrl}` - }) - .pipe(pluck('entity')); + // Ensure URL starts with /api/ if it doesn't already + const url = this.normalizeUrl(baseUrl); + + return this.http + .post>(url, data) + .pipe(map((response) => response.entity)); } /** @@ -42,13 +44,9 @@ export class DotCrudService { * @memberof CrudService */ public putData(baseUrl: string, data: unknown): Observable { - return this.coreWebService - .requestView({ - body: data, - method: 'PUT', - url: `${baseUrl}` - }) - .pipe(pluck('entity')); + const url = this.normalizeUrl(baseUrl); + + return this.http.put>(url, data).pipe(map((response) => response.entity)); } /** @@ -61,11 +59,9 @@ export class DotCrudService { * @memberof DotCrudService */ getDataById(baseUrl: string, id: string, pick = 'entity'): Observable { - return this.coreWebService - .requestView({ - url: `${baseUrl}/id/${id}` - }) - .pipe(pluck(pick)); + const url = this.normalizeUrl(`${baseUrl}/id/${id}`); + + return this.http.get>(url).pipe(map((response) => response[pick])); } /** @@ -77,11 +73,24 @@ export class DotCrudService { * @memberof CrudService */ delete(baseUrl: string, id: string): Observable { - return this.coreWebService - .requestView({ - method: 'DELETE', - url: `${baseUrl}/${id}` - }) - .pipe(pluck('entity')); + const url = this.normalizeUrl(`${baseUrl}/${id}`); + + return this.http.delete>(url).pipe(map((response) => response.entity)); + } + + /** + * Normalizes URL to ensure it starts with /api/ if needed + * @private + */ + private normalizeUrl(url: string): string { + if (url.startsWith('/api/') || url.startsWith('/')) { + return url; + } + + if (url.startsWith('v1/') || url.startsWith('v2/') || url.startsWith('v3/')) { + return `/api/${url}`; + } + + return url; } } diff --git a/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.spec.ts b/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.spec.ts index cbbadf939f2c..01c1ff3888a6 100644 --- a/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.spec.ts @@ -1,14 +1,13 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotCurrentUser, DotPermissionsType, UserPermissions, PermissionsType } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; import { DotCurrentUserService } from './dot-current-user.service'; @@ -18,11 +17,7 @@ describe('DotCurrentUserService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotCurrentUserService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotCurrentUserService] }); dotCurrentUserService = TestBed.inject(DotCurrentUserService); httpMock = TestBed.inject(HttpTestingController); @@ -40,7 +35,7 @@ describe('DotCurrentUserService', () => { expect(user).toEqual(mockCurrentUserResponse); }); - const req = httpMock.expectOne('v1/users/current/'); + const req = httpMock.expectOne('/api/v1/users/current/'); expect(req.request.method).toBe('GET'); req.flush(mockCurrentUserResponse); }); @@ -51,7 +46,7 @@ describe('DotCurrentUserService', () => { expect(hasAccess).toEqual(true); }); - const req = httpMock.expectOne(`v1/portlet/${portlet}/_doesuserhaveaccess`); + const req = httpMock.expectOne(`/api/v1/portlet/${portlet}/_doesuserhaveaccess`); expect(req.request.method).toBe('GET'); req.flush({ entity: { @@ -74,7 +69,7 @@ describe('DotCurrentUserService', () => { expect(permissions).toEqual(response); }); - const req = httpMock.expectOne(`v1/permissions/_bypermissiontype?userid=${userId}`); + const req = httpMock.expectOne(`/api/v1/permissions/_bypermissiontype?userid=${userId}`); expect(req.request.method).toBe('GET'); req.flush({ entity: response @@ -88,10 +83,10 @@ describe('DotCurrentUserService', () => { .subscribe(); const req = httpMock.expectOne( - `v1/permissions/_bypermissiontype?userid=${userId}&permission=${UserPermissions.WRITE}&permissiontype=${PermissionsType.HTMLPAGES}` + `/api/v1/permissions/_bypermissiontype?userid=${userId}&permission=${UserPermissions.WRITE}&permissiontype=${PermissionsType.HTMLPAGES}` ); expect(req.request.method).toBe('GET'); - req.flush({}); + req.flush({ entity: {} }); }); afterEach(() => { diff --git a/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.ts b/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.ts index 2f6e7f825706..3563876816cb 100644 --- a/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.ts +++ b/core-web/libs/data-access/src/lib/dot-current-user/dot-current-user.service.ts @@ -1,24 +1,26 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { map, pluck, take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { + DotCMSResponse, DotCurrentUser, DotPermissionsType, PermissionsType, UserPermissions } from '@dotcms/dotcms-models'; import { formatMessage } from '@dotcms/utils'; + @Injectable() export class DotCurrentUserService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); - private currentUsersUrl = 'v1/users/current/'; - private userPermissionsUrl = 'v1/permissions/_bypermissiontype?userid={0}'; - private porletAccessUrl = 'v1/portlet/{0}/_doesuserhaveaccess'; + private currentUsersUrl = '/api/v1/users/current/'; + private userPermissionsUrl = '/api/v1/permissions/_bypermissiontype?userid={0}'; + private porletAccessUrl = '/api/v1/portlet/{0}/_doesuserhaveaccess'; // TODO: We need to update the LoginService to get the userId in the User object /** @@ -27,11 +29,7 @@ export class DotCurrentUserService { * @memberof DotCurrentUserService */ getCurrentUser(): Observable { - return this.coreWebService - .request({ - url: this.currentUsersUrl - }) - .pipe(map((res: DotCurrentUser) => res)); + return this.http.get(this.currentUsersUrl); } /** @@ -58,11 +56,9 @@ export class DotCurrentUserService { permissionsType.join(',') ]); - return this.coreWebService - .requestView({ - url: permissionsUrl - }) - .pipe(take(1), pluck('entity')); + return this.http + .get>(permissionsUrl) + .pipe(map((response) => response.entity)); } /** @@ -72,10 +68,10 @@ export class DotCurrentUserService { * @memberof DotCurrentUserService */ hasAccessToPortlet(portletid: string): Observable { - return this.coreWebService - .requestView({ - url: this.porletAccessUrl.replace('{0}', portletid) - }) - .pipe(take(1), pluck('entity', 'response')); + return this.http + .get< + DotCMSResponse<{ response: boolean }> + >(this.porletAccessUrl.replace('{0}', portletid)) + .pipe(map((response) => response.entity.response)); } } diff --git a/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.spec.ts b/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.spec.ts index c31de48704f1..c8614543bbc8 100644 --- a/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.spec.ts @@ -1,48 +1,47 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotDevice } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock, mockDotDevices } from '@dotcms/utils-testing'; import { DotDevicesService } from './dot-devices.service'; describe('DotDevicesService', () => { - let dotDevicesService: DotDevicesService; + let service: DotDevicesService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotDevicesService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotDevicesService] }); - dotDevicesService = TestBed.inject(DotDevicesService); + service = TestBed.inject(DotDevicesService); httpMock = TestBed.inject(HttpTestingController); }); - it('should get Devices', () => { - const url = [ - `api/`, - `content/respectFrontendRoles/false/render/false/query/+contentType:previewDevice `, - `+live:true `, - `+deleted:false `, - `+working:true`, - `/limit/40/orderby/title` - ].join(''); - - dotDevicesService.get().subscribe((devices: DotDevice[]) => { - expect(devices).toEqual(mockDotDevices); + afterEach(() => { + httpMock.verify(); + }); + + it('should get devices', () => { + const mockDevices: DotDevice[] = [ + { + cssHeight: '100', + cssWidth: '100', + inode: '123', + identifier: '123', + name: 'Test Device', + stInode: 'abc' + } + ]; + + service.get().subscribe((devices: DotDevice[]) => { + expect(devices).toEqual(mockDevices); }); - const req = httpMock.expectOne(url); + const expectedUrl = + '/api/content/respectFrontendRoles/false/render/false/query/+contentType:previewDevice +live:true +deleted:false +working:true/limit/40/orderby/title'; + const req = httpMock.expectOne(expectedUrl); expect(req.request.method).toBe('GET'); - req.flush({ contentlets: mockDotDevices }); - }); - - afterEach(() => { - httpMock.verify(); + req.flush({ contentlets: mockDevices }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.ts b/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.ts index 28e3f181f810..1785dfd6e6f8 100644 --- a/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.ts +++ b/core-web/libs/data-access/src/lib/dot-devices/dot-devices.service.ts @@ -1,12 +1,17 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotDevice } from '@dotcms/dotcms-models'; +// Response type for content search endpoints that return contentlets +interface DotContentSearchResponse { + contentlets: T; +} + /** * Provide util methods to get the Devices & dimensions. * @export @@ -14,7 +19,7 @@ import { DotDevice } from '@dotcms/dotcms-models'; */ @Injectable() export class DotDevicesService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Return available devices. @@ -22,17 +27,17 @@ export class DotDevicesService { * @memberof DotDevicesService */ get(): Observable { - return this.coreWebService - .requestView({ - url: [ - 'api', - 'content', - 'respectFrontendRoles/false', - 'render/false', - 'query/+contentType:previewDevice +live:true +deleted:false +working:true', - 'limit/40/orderby/title' - ].join('/') - }) - .pipe(pluck('contentlets')); + const url = [ + '/api', + 'content', + 'respectFrontendRoles/false', + 'render/false', + 'query/+contentType:previewDevice +live:true +deleted:false +working:true', + 'limit/40/orderby/title' + ].join('/'); + + return this.http + .get>(url) + .pipe(map((response) => response.contentlets)); } } diff --git a/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.spec.ts b/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.spec.ts index dcf32d89443f..71b1db460aee 100644 --- a/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.spec.ts @@ -1,9 +1,8 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotPageContainer } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; import { DotEditPageService } from './dot-edit-page.service'; @@ -15,9 +14,9 @@ describe('DotEditPageService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), DotEditPageService, DotSessionStorageService ] @@ -43,7 +42,7 @@ describe('DotEditPageService', () => { dotEditPageService.save(pageId, model).subscribe(); - const req = httpMock.expectOne(`v1/page/${pageId}/content?variantName=DEFAULT`); + const req = httpMock.expectOne(`/api/v1/page/${pageId}/content?variantName=DEFAULT`); expect(req.request.method).toBe('POST'); expect(req.request.body).toBe(model); req.flush({}); @@ -55,7 +54,9 @@ describe('DotEditPageService', () => { dotEditPageService.whatChange(pageId, languageId).subscribe(); - const req = httpMock.expectOne(`v1/page/${pageId}/render/versions?langId=${languageId}`); + const req = httpMock.expectOne( + `/api/v1/page/${pageId}/render/versions?langId=${languageId}` + ); expect(req.request.method).toBe('GET'); req.flush({}); }); @@ -84,7 +85,7 @@ describe('DotEditPageService', () => { dotEditPageService.save(pageId, model).subscribe(); - const req = httpMock.expectOne(`v1/page/${pageId}/content?variantName=Testing`); + const req = httpMock.expectOne(`/api/v1/page/${pageId}/content?variantName=Testing`); expect(req.request.method).toBe('POST'); expect(req.request.body).toBe(model); req.flush({}); diff --git a/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.ts b/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.ts index 310611b957df..77d4a3a34bfe 100644 --- a/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.ts +++ b/core-web/libs/data-access/src/lib/dot-edit-page/dot-edit-page.service.ts @@ -1,17 +1,17 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService, DotRequestOptionsArgs } from '@dotcms/dotcms-js'; -import { DotPageContainer, DotWhatChanged } from '@dotcms/dotcms-models'; +import { DotCMSResponse, DotPageContainer, DotWhatChanged } from '@dotcms/dotcms-models'; import { DotSessionStorageService } from '../dot-session-storage/dot-session-storage.service'; @Injectable() export class DotEditPageService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private readonly dotSessionStorageService = inject(DotSessionStorageService); /** @@ -23,21 +23,17 @@ export class DotEditPageService { * @memberof DotEditPageService */ save(pageId: string, content: DotPageContainer[]): Observable { - const requestOptions: DotRequestOptionsArgs = { - method: 'POST', - body: content, - url: `v1/page/${pageId}/content` - }; - + const url = `/api/v1/page/${pageId}/content`; const currentVariantName = this.dotSessionStorageService.getVariationId(); + let params = new HttpParams(); if (currentVariantName) { - requestOptions.params = { - variantName: currentVariantName - }; + params = params.set('variantName', currentVariantName); } - return this.coreWebService.requestView(requestOptions).pipe(pluck('entity')); + return this.http + .post>(url, content, { params }) + .pipe(map((response) => response.entity)); } /** @@ -49,10 +45,10 @@ export class DotEditPageService { * @memberof DotEditPageService */ whatChange(pageId: string, languageId: string): Observable { - return this.coreWebService - .requestView({ - url: `v1/page/${pageId}/render/versions?langId=${languageId}` - }) - .pipe(pluck('entity')); + return this.http + .get< + DotCMSResponse + >(`/api/v1/page/${pageId}/render/versions?langId=${languageId}`) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.spec.ts b/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.spec.ts index bff0fd3e4398..5f6e99df8f5f 100644 --- a/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.spec.ts @@ -1,9 +1,7 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed, getTestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - import { DotESContentService, ESOrderDirection } from './dot-es-content.service'; describe('DotESContentService', () => { @@ -20,11 +18,7 @@ describe('DotESContentService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotESContentService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotESContentService] }); injector = getTestBed(); dotESContentService = injector.inject(DotESContentService); diff --git a/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.ts b/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.ts index df04c92b6811..72beb452e531 100644 --- a/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.ts +++ b/core-web/libs/data-access/src/lib/dot-es-content/dot-es-content.service.ts @@ -1,11 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { take, pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { ESContent } from '@dotcms/dotcms-models'; +import { DotCMSResponse, ESContent } from '@dotcms/dotcms-models'; export enum ESOrderDirection { ASC = 'ASC', @@ -27,7 +27,7 @@ export interface queryEsParams { */ @Injectable() export class DotESContentService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private _paginationPerPage = 40; private _offset = '0'; @@ -48,13 +48,9 @@ export class DotESContentService { const queryParams = this.getESQuery(params, this.getObjectFromMap(this._extraParams)); - return this.coreWebService - .requestView({ - body: JSON.stringify(queryParams), - method: 'POST', - url: this._url - }) - .pipe(pluck('entity'), take(1)); + return this.http + .post>(this._url, JSON.stringify(queryParams)) + .pipe(map((response) => response.entity)); } private setExtraParams(name: string, value?: string | number): void { diff --git a/core-web/libs/data-access/src/lib/dot-experiments/dot-experiments.service.ts b/core-web/libs/data-access/src/lib/dot-experiments/dot-experiments.service.ts index 49b6b70ee2c3..0f1f30075053 100644 --- a/core-web/libs/data-access/src/lib/dot-experiments/dot-experiments.service.ts +++ b/core-web/libs/data-access/src/lib/dot-experiments/dot-experiments.service.ts @@ -5,7 +5,6 @@ import { Injectable, inject } from '@angular/core'; import { catchError, pluck } from 'rxjs/operators'; -import { DotCMSResponse } from '@dotcms/dotcms-js'; import { DotExperiment, DotExperimentResults, @@ -14,7 +13,8 @@ import { GoalsLevels, HealthStatusTypes, RangeOfDateAndTime, - TrafficProportion + TrafficProportion, + DotCMSResponse } from '@dotcms/dotcms-models'; const API_ENDPOINT = '/api/v1/experiments'; diff --git a/core-web/libs/data-access/src/lib/dot-favorite-page/dot-favorite-page.service.spec.ts b/core-web/libs/data-access/src/lib/dot-favorite-page/dot-favorite-page.service.spec.ts index f3a7ba4db744..87711c20222e 100644 --- a/core-web/libs/data-access/src/lib/dot-favorite-page/dot-favorite-page.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-favorite-page/dot-favorite-page.service.spec.ts @@ -1,31 +1,28 @@ import { describe, expect, it } from '@jest/globals'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { getTestBed, TestBed } from '@angular/core/testing'; - -import { CoreWebService, CoreWebServiceMock } from '@dotcms/dotcms-js'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; import { DotFavoritePageService } from './dot-favorite-page.service'; import { DotESContentService, ESOrderDirection } from '../dot-es-content/dot-es-content.service'; describe('DotFavoritePageService', () => { - let injector: TestBed; let dotESContentService: DotESContentService; let dotFavoritePageService: DotFavoritePageService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), DotESContentService, DotFavoritePageService ] }); - injector = getTestBed(); - dotESContentService = injector.inject(DotESContentService); - dotFavoritePageService = injector.inject(DotFavoritePageService); + dotESContentService = TestBed.inject(DotESContentService); + dotFavoritePageService = TestBed.inject(DotFavoritePageService); jest.spyOn(dotESContentService, 'get'); }); diff --git a/core-web/libs/data-access/src/lib/dot-languages/dot-languages.service.ts b/core-web/libs/data-access/src/lib/dot-languages/dot-languages.service.ts index 8f9fdc13b7cc..3a671b0951f0 100644 --- a/core-web/libs/data-access/src/lib/dot-languages/dot-languages.service.ts +++ b/core-web/libs/data-access/src/lib/dot-languages/dot-languages.service.ts @@ -5,8 +5,12 @@ import { inject, Injectable } from '@angular/core'; import { pluck } from 'rxjs/operators'; -import { DotCMSResponse } from '@dotcms/dotcms-js'; -import { DotAddLanguage, DotLanguage, DotLanguagesISO } from '@dotcms/dotcms-models'; +import { + DotAddLanguage, + DotLanguage, + DotLanguagesISO, + DotCMSResponse +} from '@dotcms/dotcms-models'; export const LANGUAGE_API_URL = '/api/v2/languages'; export const LANGUAGE_API_URL_WITH_VARS = '/api/v2/languages?countLangVars=true'; diff --git a/core-web/libs/data-access/src/lib/dot-license/dot-license.service.spec.ts b/core-web/libs/data-access/src/lib/dot-license/dot-license.service.spec.ts index a53b7924dde9..80425df976ec 100644 --- a/core-web/libs/data-access/src/lib/dot-license/dot-license.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-license/dot-license.service.spec.ts @@ -1,6 +1,7 @@ import { of } from 'rxjs'; -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { DotLicenseService } from './dot-license.service'; @@ -11,8 +12,7 @@ describe('DotLicenseService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [DotLicenseService] + providers: [provideHttpClient(), provideHttpClientTesting(), DotLicenseService] }); dotLicenseService = TestBed.inject(DotLicenseService); httpMock = TestBed.inject(HttpTestingController); diff --git a/core-web/libs/data-access/src/lib/dot-license/dot-license.service.ts b/core-web/libs/data-access/src/lib/dot-license/dot-license.service.ts index a4d13b9f16f8..f20eb3064c97 100644 --- a/core-web/libs/data-access/src/lib/dot-license/dot-license.service.ts +++ b/core-web/libs/data-access/src/lib/dot-license/dot-license.service.ts @@ -3,10 +3,15 @@ import { Observable, Subject, BehaviorSubject, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck, map, take, tap } from 'rxjs/operators'; +import { map, take, tap } from 'rxjs/operators'; -import { ResponseView } from '@dotcms/dotcms-js'; -import { DotLicense } from '@dotcms/dotcms-models'; +import { DotCMSResponse, DotLicense } from '@dotcms/dotcms-models'; + +interface DotLicenseConfigResponse { + config: { + license: DotLicense; + }; +} export interface DotUnlicensedPortletData { icon: string; @@ -136,8 +141,8 @@ export class DotLicenseService { private getLicense(): Observable { if (this.license) return of(this.license); - return this.http.get(this.licenseURL).pipe( - pluck('entity', 'config', 'license'), + return this.http.get>(this.licenseURL).pipe( + map((response) => response.entity.config.license), tap((license) => { this.setLicense(license); }) @@ -151,8 +156,8 @@ export class DotLicenseService { */ updateLicense(): void { this.http - .get(this.licenseURL) - .pipe(pluck('entity', 'config', 'license')) + .get>(this.licenseURL) + .pipe(map((response) => response.entity.config.license)) .subscribe((license) => { this.setLicense(license); }); diff --git a/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.spec.ts b/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.spec.ts index d876879662dd..daf1430834e2 100644 --- a/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.spec.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock, mockDotLayout } from '@dotcms/utils-testing'; +import { mockDotLayout } from '@dotcms/utils-testing'; import { DotPageLayoutService } from './dot-page-layout.service'; @@ -16,9 +16,9 @@ describe('DotPageLayoutService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), DotPageLayoutService, DotSessionStorageService ] @@ -45,7 +45,7 @@ describe('DotPageLayoutService', () => { }); const req = httpMock.expectOne( - 'v1/page/test38923-82393842-23823/layout?variantName=DEFAULT' + '/api/v1/page/test38923-82393842-23823/layout?variantName=DEFAULT' ); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(mockDotLayout()); @@ -72,7 +72,7 @@ describe('DotPageLayoutService', () => { }); const req = httpMock.expectOne( - 'v1/page/test38923-82393842-23823/layout?variantName=variantTesting' + '/api/v1/page/test38923-82393842-23823/layout?variantName=variantTesting' ); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(mockDotLayout()); diff --git a/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.ts b/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.ts index 5e2dc8b31183..17bdda2e9dae 100644 --- a/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.ts +++ b/core-web/libs/data-access/src/lib/dot-page-layout/dot-page-layout.service.ts @@ -1,11 +1,16 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck, map } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService, DotRequestOptionsArgs } from '@dotcms/dotcms-js'; -import { DotPageRender, DotPageRenderParameters, DotTemplateDesigner } from '@dotcms/dotcms-models'; +import { + DotCMSResponse, + DotPageRender, + DotPageRenderParameters, + DotTemplateDesigner +} from '@dotcms/dotcms-models'; import { DotSessionStorageService } from '../dot-session-storage/dot-session-storage.service'; @@ -17,7 +22,7 @@ import { DotSessionStorageService } from '../dot-session-storage/dot-session-sto */ @Injectable() export class DotPageLayoutService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private readonly dotSessionStorageService = inject(DotSessionStorageService); /** @@ -29,26 +34,22 @@ export class DotPageLayoutService { * @memberof DotPageLayoutService */ save(pageIdentifier: string, dotLayout: DotTemplateDesigner): Observable { - const requestOptions: DotRequestOptionsArgs = { - body: dotLayout, - method: 'POST', - url: `v1/page/${pageIdentifier}/layout` - }; - + const url = `/api/v1/page/${pageIdentifier}/layout`; const currentVariantName = this.dotSessionStorageService.getVariationId(); + let params = new HttpParams(); if (currentVariantName) { - requestOptions.params = { - variantName: currentVariantName - }; + params = params.set('variantName', currentVariantName); } - return this.coreWebService.requestView(requestOptions).pipe( - pluck('entity'), - map( - (dotPageRenderResponse: DotPageRenderParameters) => - new DotPageRender(dotPageRenderResponse) - ) - ); + return this.http + .post>(url, dotLayout, { params }) + .pipe( + map((response) => response.entity), + map( + (dotPageRenderResponse: DotPageRenderParameters) => + new DotPageRender(dotPageRenderResponse) + ) + ); } } diff --git a/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.spec.ts b/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.spec.ts index 0ed42fbed783..6bbc504bde46 100644 --- a/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.spec.ts @@ -1,7 +1,8 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { LoginService, CoreWebService, CoreWebServiceMock } from '@dotcms/dotcms-js'; +import { LoginService } from '@dotcms/dotcms-js'; import { DotPageRenderParameters, DotPageMode } from '@dotcms/dotcms-models'; import { LoginServiceMock, @@ -21,10 +22,10 @@ describe('DotPageRenderService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotSessionStorageService, - { provide: CoreWebService, useClass: CoreWebServiceMock }, { provide: LoginService, useClass: LoginServiceMock @@ -38,7 +39,7 @@ describe('DotPageRenderService', () => { it('should check page permissions based on url', () => { dotPageRenderService.checkPermission({ url, language_id: '1' }).subscribe(); - const req = httpMock.expectOne(`v1/page/_check-permission`); + const req = httpMock.expectOne(`/api/v1/page/_check-permission`); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual({ url, language_id: '1' }); req.flush({ @@ -52,7 +53,7 @@ describe('DotPageRenderService', () => { }); const req = httpMock.expectOne( - 'v1/page/render/about-us?mode=PREVIEW_MODE&variantName=DEFAULT' + '/api/v1/page/render/about-us?mode=PREVIEW_MODE&variantName=DEFAULT' ); expect(req.request.method).toBe('GET'); req.flush({ entity: mockDotRenderedPage() }); @@ -60,12 +61,12 @@ describe('DotPageRenderService', () => { it('should get a page with just the url', () => { dotPageRenderService.get({ url }).subscribe(); - httpMock.expectOne('v1/page/render/about-us?mode=PREVIEW_MODE&variantName=DEFAULT'); + httpMock.expectOne('/api/v1/page/render/about-us?mode=PREVIEW_MODE&variantName=DEFAULT'); }); it('should get a page with just the mode', () => { dotPageRenderService.get({ url, mode: DotPageMode.LIVE }).subscribe(); - httpMock.expectOne(`v1/page/render/${url}?mode=ADMIN_MODE&variantName=DEFAULT`); + httpMock.expectOne(`/api/v1/page/render/${url}?mode=ADMIN_MODE&variantName=DEFAULT`); }); describe('view as', () => { @@ -79,7 +80,7 @@ describe('DotPageRenderService', () => { }) .subscribe(); httpMock.expectOne( - 'v1/page/render/about-us?language_id=3&mode=PREVIEW_MODE&variantName=DEFAULT' + '/api/v1/page/render/about-us?language_id=3&mode=PREVIEW_MODE&variantName=DEFAULT' ); }); @@ -96,7 +97,7 @@ describe('DotPageRenderService', () => { }) .subscribe(); httpMock.expectOne( - 'v1/page/render/about-us?device_inode=1234&mode=PREVIEW_MODE&variantName=DEFAULT' + '/api/v1/page/render/about-us?device_inode=1234&mode=PREVIEW_MODE&variantName=DEFAULT' ); }); @@ -113,7 +114,7 @@ describe('DotPageRenderService', () => { }) .subscribe(); httpMock.expectOne( - 'v1/page/render/about-us?com.dotmarketing.persona.id=6789&mode=PREVIEW_MODE&variantName=DEFAULT' + '/api/v1/page/render/about-us?com.dotmarketing.persona.id=6789&mode=PREVIEW_MODE&variantName=DEFAULT' ); }); @@ -136,7 +137,7 @@ describe('DotPageRenderService', () => { .subscribe(); httpMock.expectOne( - 'v1/page/render/about-us?com.dotmarketing.persona.id=6789&device_inode=1234&language_id=3&mode=PREVIEW_MODE&variantName=DEFAULT' + '/api/v1/page/render/about-us?com.dotmarketing.persona.id=6789&device_inode=1234&language_id=3&mode=PREVIEW_MODE&variantName=DEFAULT' ); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.ts b/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.ts index 59f155db9a94..2b50528cb049 100644 --- a/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.ts +++ b/core-web/libs/data-access/src/lib/dot-page-render/dot-page-render.service.ts @@ -1,12 +1,13 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { Params } from '@angular/router'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { + DotCMSResponse, DotPageMode, DotPersona, DotDevice, @@ -26,7 +27,7 @@ import { DotSessionStorageService } from '../dot-session-storage/dot-session-sto */ @Injectable() export class DotPageRenderService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private readonly dotSessionStorageService = inject(DotSessionStorageService); /** @@ -37,15 +38,11 @@ export class DotPageRenderService { * @memberof DotPageRenderService */ checkPermission(queryParams: Params): Observable { - return this.coreWebService - .requestView({ - body: { - ...queryParams - }, - method: 'POST', - url: `v1/page/_check-permission` + return this.http + .post>('/api/v1/page/_check-permission', { + ...queryParams }) - .pipe(pluck('entity')); + .pipe(map((response) => response.entity)); } /** @@ -61,12 +58,11 @@ export class DotPageRenderService { ): Observable { const params: DotPageRenderRequestParams = this.getOptionalViewAsParams(viewAs, mode); - return this.coreWebService - .requestView({ - url: `v1/page/render/${url?.replace(/^\//, '')}`, - params: { ...extraParams, ...params } - }) - .pipe(pluck('entity')); + return this.http + .get< + DotCMSResponse + >(`/api/v1/page/render/${url?.replace(/^\//, '')}`, { params: { ...extraParams, ...params } as Record }) + .pipe(map((response) => response.entity)); } private getOptionalViewAsParams( diff --git a/core-web/libs/data-access/src/lib/dot-page-state/dot-page-state.service.spec.ts b/core-web/libs/data-access/src/lib/dot-page-state/dot-page-state.service.spec.ts index c2db650cda92..7898f26dcf3e 100644 --- a/core-web/libs/data-access/src/lib/dot-page-state/dot-page-state.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-page-state/dot-page-state.service.spec.ts @@ -1,15 +1,17 @@ import { describe, expect, it } from '@jest/globals'; import { of, throwError } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { ConfirmationService } from 'primeng/api'; -import { CoreWebService, HttpCode, LoginService } from '@dotcms/dotcms-js'; +import { HttpCode, LoginService } from '@dotcms/dotcms-js'; import { DotCMSContentlet, DotDevice, + DotExperiment, DotExperimentStatus, DotPageMode, DotPageRenderState, @@ -17,7 +19,6 @@ import { PageModelChangeEventType } from '@dotcms/dotcms-models'; import { - CoreWebServiceMock, dotcmsContentletMock, DotLicenseServiceMock, DotMessageDisplayServiceMock, @@ -47,7 +48,10 @@ import { DotRouterService } from '../dot-router/dot-router.service'; import { DotSessionStorageService } from '../dot-session-storage/dot-session-storage.service'; const EXPERIMENT_MOCK = getExperimentMock(0); -const getDotPageRenderStateMock = (favoritePage?: DotCMSContentlet, runningExperiment = null) => { +const getDotPageRenderStateMock = ( + favoritePage?: DotCMSContentlet, + runningExperiment?: DotExperiment +) => { return new DotPageRenderState( mockUser(), mockDotRenderedPage(), @@ -70,8 +74,9 @@ describe('DotPageStateService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotSessionStorageService, DotContentletLockerService, DotHttpErrorManagerService, @@ -87,7 +92,6 @@ describe('DotPageStateService', () => { provide: DotMessageDisplayService, useClass: DotMessageDisplayServiceMock }, - { provide: CoreWebService, useClass: CoreWebServiceMock }, { provide: DotRouterService, useValue: new MockDotRouterJestService(jest) }, { provide: LoginService, @@ -168,7 +172,7 @@ describe('DotPageStateService', () => { }); it('should get with url from queryParams with a Failing fetch from ES Search (favorite page)', () => { - const error500 = mockResponseView(500, '/test', null, { + const error500 = mockResponseView(500, '/test', undefined, { message: 'error' }); dotFavoritePageService.get = jest.fn().mockReturnValue(throwError(error500)); @@ -189,7 +193,7 @@ describe('DotPageStateService', () => { describe('Get Running Experiment', () => { it('should get running experiment', () => { - const mock = getDotPageRenderStateMock(null, EXPERIMENT_MOCK); + const mock = getDotPageRenderStateMock(undefined, EXPERIMENT_MOCK); dotExperimentsService.getByStatus = jest.fn().mockReturnValue(of([EXPERIMENT_MOCK])); service.get(); @@ -215,7 +219,7 @@ describe('DotPageStateService', () => { }); it('should set running experiment to null if endpoint error', () => { - const error500 = mockResponseView(500, '/test', null, { + const error500 = mockResponseView(500, '/test', undefined, { message: 'experiments.error.fetching.data' }); const mock = getDotPageRenderStateMock(); @@ -360,7 +364,7 @@ describe('DotPageStateService', () => { service.state$.subscribe(({ state }: DotPageRenderState) => { expect(state.favoritePage).toBe(null); }); - service.setFavoritePageHighlight(null); + service.setFavoritePageHighlight(null as unknown as DotCMSContentlet); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.spec.ts b/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.spec.ts index 472e7ff50c51..1cd9afbb1a91 100644 --- a/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.spec.ts @@ -1,8 +1,8 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock, dotcmsContentTypeBasicMock } from '@dotcms/utils-testing'; +import { dotcmsContentTypeBasicMock } from '@dotcms/utils-testing'; import { DotPageTypesService } from './dot-page-types.service'; @@ -16,11 +16,7 @@ describe('DotPageTypesService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotPageTypesService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotPageTypesService] }); service = TestBed.inject(DotPageTypesService); httpMock = TestBed.inject(HttpTestingController); diff --git a/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.ts b/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.ts index a7e520504807..3cca682c67e4 100644 --- a/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.ts +++ b/core-web/libs/data-access/src/lib/dot-page-types/dot-page-types.service.ts @@ -1,15 +1,15 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck, take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotCMSContentType } from '@dotcms/dotcms-models'; +import { DotCMSContentType, DotCMSResponse } from '@dotcms/dotcms-models'; @Injectable() export class DotPageTypesService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Returns Content Type data of type page and urlMap @@ -19,10 +19,8 @@ export class DotPageTypesService { * @memberof DotPageTypesService */ getPages(keyword = ''): Observable { - return this.coreWebService - .requestView({ - url: `/api/v1/page/types?filter=${keyword}` - }) - .pipe(take(1), pluck('entity')); + return this.http + .get>(`/api/v1/page/types?filter=${keyword}`) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.spec.ts b/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.spec.ts index 8829432fd7b2..48ee7754a8fd 100644 --- a/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.spec.ts @@ -1,50 +1,117 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; +import { + createHttpFactory, + HttpMethod, + mockProvider, + SpectatorHttp, + SpyObject +} from '@ngneat/spectator/jest'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; +import { DotCMSPersonalizedItem } from '@dotcms/dotcms-models'; import { DotPersonalizeService } from './dot-personalize.service'; import { DotSessionStorageService } from '../dot-session-storage/dot-session-storage.service'; describe('DotPersonalizeService', () => { - let dotPersonalizeService: DotPersonalizeService; - let httpMock: HttpTestingController; + let spectator: SpectatorHttp; + let sessionStorageService: SpyObject; + + const createHttp = createHttpFactory({ + service: DotPersonalizeService, + providers: [mockProvider(DotSessionStorageService)] + }); beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - DotSessionStorageService, - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotPersonalizeService - ] - }); - dotPersonalizeService = TestBed.inject(DotPersonalizeService); - httpMock = TestBed.inject(HttpTestingController); + spectator = createHttp(); + sessionStorageService = spectator.inject(DotSessionStorageService); + sessionStorageService.getVariationId.mockReturnValue(''); }); - it('should set Personalized', () => { - dotPersonalizeService.personalized('a', 'b').subscribe(); + describe('personalized', () => { + it('should personalize a page without variant name', () => { + const mockResponse: DotCMSPersonalizedItem[] = [ + { + relationType: 'relation-type', + treeOrder: 1, + personalization: 'persona-tag', + container: 'container-id', + contentlet: 'contentlet-id', + htmlPage: 'page-id' + } + ]; - const req = httpMock.expectOne('/api/v1/personalization/pagepersonas?variantName=DEFAULT'); - expect(req.request.method).toBe('POST'); - expect(req.request.body).toEqual({ pageId: 'a', personaTag: 'b' }); - }); + spectator.service.personalized('page-id', 'persona-tag').subscribe((response) => { + expect(response).toEqual(mockResponse); + }); - it('should despersonalized', () => { - const pageId = 'a'; - const personaTag = 'b'; - dotPersonalizeService.despersonalized(pageId, personaTag).subscribe(); + const req = spectator.expectOne( + '/api/v1/personalization/pagepersonas', + HttpMethod.POST + ); + expect(req.request.body).toEqual({ pageId: 'page-id', personaTag: 'persona-tag' }); + expect(req.request.params.has('variantName')).toBe(false); + req.flush({ entity: mockResponse }); + }); - const req = httpMock.expectOne( - `/api/v1/personalization/pagepersonas/page/${pageId}/personalization/${personaTag}?variantName=DEFAULT` - ); - expect(req.request.method).toBe('DELETE'); + it('should personalize a page with variant name when available', () => { + sessionStorageService.getVariationId.mockReturnValue('test-variant'); + + const mockResponse: DotCMSPersonalizedItem[] = [ + { + relationType: 'relation-type', + treeOrder: 1, + personalization: 'persona-tag', + container: 'container-id', + contentlet: 'contentlet-id', + htmlPage: 'page-id' + } + ]; + + spectator.service.personalized('page-id', 'persona-tag').subscribe((response) => { + expect(response).toEqual(mockResponse); + }); + + const req = spectator.expectOne( + '/api/v1/personalization/pagepersonas?variantName=test-variant', + HttpMethod.POST + ); + expect(req.request.body).toEqual({ pageId: 'page-id', personaTag: 'persona-tag' }); + expect(req.request.params.get('variantName')).toBe('test-variant'); + req.flush({ entity: mockResponse }); + }); }); - afterEach(() => { - httpMock.verify(); + describe('despersonalized', () => { + it('should despersonalize a page without variant name', () => { + const mockResponse = 'success'; + + spectator.service.despersonalized('page-id', 'persona-tag').subscribe((response) => { + expect(response).toEqual(mockResponse); + }); + + const req = spectator.expectOne( + '/api/v1/personalization/pagepersonas/page/page-id/personalization/persona-tag', + HttpMethod.DELETE + ); + expect(req.request.params.has('variantName')).toBe(false); + req.flush({ entity: mockResponse }); + }); + + it('should despersonalize a page with variant name when available', () => { + sessionStorageService.getVariationId.mockReturnValue('test-variant'); + + const mockResponse = 'success'; + + spectator.service.despersonalized('page-id', 'persona-tag').subscribe((response) => { + expect(response).toEqual(mockResponse); + }); + + const req = spectator.expectOne( + '/api/v1/personalization/pagepersonas/page/page-id/personalization/persona-tag?variantName=test-variant', + HttpMethod.DELETE + ); + expect(req.request.params.get('variantName')).toBe('test-variant'); + req.flush({ entity: mockResponse }); + }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.ts b/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.ts index 24018b7e26f9..1ac7f3b33046 100644 --- a/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.ts +++ b/core-web/libs/data-access/src/lib/dot-personalize/dot-personalize.service.ts @@ -1,17 +1,17 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotCMSPersonalizedItem } from '@dotcms/dotcms-models'; +import { DotCMSPersonalizedItem, DotCMSResponse } from '@dotcms/dotcms-models'; import { DotSessionStorageService } from '../dot-session-storage/dot-session-storage.service'; @Injectable() export class DotPersonalizeService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private dotSessionStorageService = inject(DotSessionStorageService); /** @@ -25,19 +25,16 @@ export class DotPersonalizeService { personalized(pageId: string, personaTag: string): Observable { const currentVariantName = this.dotSessionStorageService.getVariationId(); - return this.coreWebService - .requestView({ - method: 'POST', - url: `/api/v1/personalization/pagepersonas`, - params: { - variantName: currentVariantName - }, - body: { - pageId, - personaTag - } - }) - .pipe(pluck('entity')); + let params = new HttpParams(); + if (currentVariantName) { + params = params.set('variantName', currentVariantName); + } + + return this.http + .post< + DotCMSResponse + >('/api/v1/personalization/pagepersonas', { pageId, personaTag }, { params }) + .pipe(map((response) => response.entity)); } /** @@ -51,14 +48,15 @@ export class DotPersonalizeService { despersonalized(pageId: string, personaTag: string): Observable { const currentVariantName = this.dotSessionStorageService.getVariationId(); - return this.coreWebService - .requestView({ - method: 'DELETE', - url: `/api/v1/personalization/pagepersonas/page/${pageId}/personalization/${personaTag}`, - params: { - variantName: currentVariantName - } - }) - .pipe(pluck('entity')); + let params = new HttpParams(); + if (currentVariantName) { + params = params.set('variantName', currentVariantName); + } + + return this.http + .delete< + DotCMSResponse + >(`/api/v1/personalization/pagepersonas/page/${pageId}/personalization/${personaTag}`, { params }) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.spec.ts b/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.spec.ts index be814b39c768..97c39aeb7afa 100644 --- a/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.spec.ts @@ -1,45 +1,45 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock, mockDotPersona } from '@dotcms/utils-testing'; +import { DotPersona } from '@dotcms/dotcms-models'; import { DotPersonasService } from './dot-personas.service'; describe('DotPersonasService', () => { - let dotPersonasService: DotPersonasService; + let service: DotPersonasService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotPersonasService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotPersonasService] }); - dotPersonasService = TestBed.inject(DotPersonasService); + service = TestBed.inject(DotPersonasService); httpMock = TestBed.inject(HttpTestingController); }); - it('should get Personas', () => { - const url = [ - `content/respectFrontendRoles/false/render/false/query/+contentType:persona `, - `+live:true `, - `+deleted:false `, - `+working:true` - ].join(''); + afterEach(() => { + httpMock.verify(); + }); - dotPersonasService.get().subscribe((result) => { - expect(result).toEqual(Array.of(mockDotPersona)); + it('should get personas', () => { + const mockPersonas: DotPersona[] = [ + { + name: 'Test Persona', + keyTag: 'test', + identifier: 'test-id', + personalized: false + } + ]; + + service.get().subscribe((personas: DotPersona[]) => { + expect(personas).toEqual(mockPersonas); }); - const req = httpMock.expectOne(url); + const expectedUrl = + '/api/content/respectFrontendRoles/false/render/false/query/+contentType:persona +live:true +deleted:false +working:true'; + const req = httpMock.expectOne(expectedUrl); expect(req.request.method).toBe('GET'); - req.flush({ contentlets: [mockDotPersona] }); - }); - - afterEach(() => { - httpMock.verify(); + req.flush({ contentlets: mockPersonas }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.ts b/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.ts index 4865a6e7aea5..ef1cf97dc498 100644 --- a/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.ts +++ b/core-web/libs/data-access/src/lib/dot-personas/dot-personas.service.ts @@ -1,12 +1,17 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotPersona } from '@dotcms/dotcms-models'; +// Response type for content search endpoints that return contentlets +interface DotContentSearchResponse { + contentlets: T; +} + /** * Provide util methods to get Personas. * @export @@ -14,7 +19,7 @@ import { DotPersona } from '@dotcms/dotcms-models'; */ @Injectable() export class DotPersonasService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Return Personas. @@ -22,10 +27,10 @@ export class DotPersonasService { * @memberof DotPersonasService */ get(): Observable { - return this.coreWebService - .requestView({ - url: 'content/respectFrontendRoles/false/render/false/query/+contentType:persona +live:true +deleted:false +working:true' - }) - .pipe(pluck('contentlets')); + return this.http + .get< + DotContentSearchResponse + >('/api/content/respectFrontendRoles/false/render/false/query/+contentType:persona +live:true +deleted:false +working:true') + .pipe(map((response) => response.contentlets)); } } diff --git a/core-web/libs/data-access/src/lib/dot-properties/dot-properties.service.spec.ts b/core-web/libs/data-access/src/lib/dot-properties/dot-properties.service.spec.ts index 4b97e2652abb..5e631f3ff039 100644 --- a/core-web/libs/data-access/src/lib/dot-properties/dot-properties.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-properties/dot-properties.service.spec.ts @@ -1,9 +1,8 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { FEATURE_FLAG_NOT_FOUND, FeaturedFlags } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; import { DotPropertiesService } from './dot-properties.service'; @@ -17,18 +16,18 @@ const fakeResponse = { describe('DotPropertiesService', () => { let service: DotPropertiesService; - let httpMock: HttpTestingController; + let httpTesting: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotPropertiesService - ] + providers: [provideHttpClient(), provideHttpClientTesting(), DotPropertiesService] }); service = TestBed.inject(DotPropertiesService); - httpMock = TestBed.inject(HttpTestingController); + httpTesting = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTesting.verify(); }); it('should get key', (done) => { @@ -39,12 +38,12 @@ describe('DotPropertiesService', () => { expect(response).toEqual(fakeResponse.entity.key1); done(); }); - const req = httpMock.expectOne(`/api/v1/configuration/config?keys=${key}`); + const req = httpTesting.expectOne(`/api/v1/configuration/config?keys=${key}`); expect(req.request.method).toBe('GET'); req.flush(fakeResponse); }); - it('should get ky as a list', (done) => { + it('should get key as a list', (done) => { const key = 'list'; expect(service).toBeTruthy(); @@ -52,7 +51,7 @@ describe('DotPropertiesService', () => { expect(response).toEqual(fakeResponse.entity.list); done(); }); - const req = httpMock.expectOne(`/api/v1/configuration/config?keys=list:${key}`); + const req = httpTesting.expectOne(`/api/v1/configuration/config?keys=list:${key}`); expect(req.request.method).toBe('GET'); req.flush(fakeResponse); }); @@ -70,7 +69,7 @@ describe('DotPropertiesService', () => { expect(response).toEqual(apiResponse.entity); done(); }); - const req = httpMock.expectOne(`/api/v1/configuration/config?keys=${keys.join()}`); + const req = httpTesting.expectOne(`/api/v1/configuration/config?keys=${keys.join()}`); expect(req.request.method).toBe('GET'); req.flush(apiResponse); }); @@ -83,7 +82,7 @@ describe('DotPropertiesService', () => { expect(response).toEqual(true); done(); }); - const req = httpMock.expectOne(`/api/v1/configuration/config?keys=${featureFlag}`); + const req = httpTesting.expectOne(`/api/v1/configuration/config?keys=${featureFlag}`); expect(req.request.method).toBe('GET'); req.flush(fakeResponse); }); @@ -107,7 +106,9 @@ describe('DotPropertiesService', () => { ); done(); }); - const req = httpMock.expectOne(`/api/v1/configuration/config?keys=${featureFlags.join()}`); + const req = httpTesting.expectOne( + `/api/v1/configuration/config?keys=${featureFlags.join()}` + ); expect(req.request.method).toBe('GET'); req.flush(apiResponse); }); @@ -124,12 +125,8 @@ describe('DotPropertiesService', () => { expect(response).toEqual(true); done(); }); - const req = httpMock.expectOne(`/api/v1/configuration/config?keys=${featureFlag}`); + const req = httpTesting.expectOne(`/api/v1/configuration/config?keys=${featureFlag}`); expect(req.request.method).toBe('GET'); req.flush(apiResponse); }); - - afterEach(() => { - httpMock.verify(); - }); }); diff --git a/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.spec.ts b/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.spec.ts index 61cb89b3c4e4..493d219fb0e5 100644 --- a/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.spec.ts @@ -1,62 +1,47 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - import { DotPushPublishFiltersService, DotPushPublishFilter } from './dot-push-publish-filters.service'; describe('DotPushPublishFiltersService', () => { - let dotPushPublishFiltersService: DotPushPublishFiltersService; + let service: DotPushPublishFiltersService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), DotPushPublishFiltersService ] }); - dotPushPublishFiltersService = TestBed.inject(DotPushPublishFiltersService); + service = TestBed.inject(DotPushPublishFiltersService); httpMock = TestBed.inject(HttpTestingController); }); - it('should get hit pp filters url', () => { - dotPushPublishFiltersService.get().subscribe(); - - const req = httpMock.expectOne('/api/v1/pushpublish/filters/'); - expect(req.request.method).toBe('GET'); + afterEach(() => { + httpMock.verify(); }); - it('should return entity', () => { - dotPushPublishFiltersService.get().subscribe((res: DotPushPublishFilter[]) => { - expect(res).toEqual([ - { - defaultFilter: true, - key: 'some.yml', - title: 'Hello World' - } - ]); + it('should get push publish filters', () => { + const mockFilters: DotPushPublishFilter[] = [ + { + defaultFilter: true, + key: 'test-key', + title: 'Test Filter' + } + ]; + + service.get().subscribe((filters: DotPushPublishFilter[]) => { + expect(filters).toEqual(mockFilters); }); const req = httpMock.expectOne('/api/v1/pushpublish/filters/'); expect(req.request.method).toBe('GET'); - req.flush({ - entity: [ - { - defaultFilter: true, - key: 'some.yml', - title: 'Hello World' - } - ] - }); - }); - - afterEach(() => { - httpMock.verify(); + req.flush({ entity: mockFilters }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.ts b/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.ts index abc71f84594e..ccdd7f719a59 100644 --- a/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.ts +++ b/core-web/libs/data-access/src/lib/dot-push-publish-filters/dot-push-publish-filters.service.ts @@ -1,27 +1,27 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; export interface DotPushPublishFilter { defaultFilter: boolean; key: string; title: string; } + @Injectable({ providedIn: 'root' }) export class DotPushPublishFiltersService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); get(): Observable { - return this.coreWebService - .requestView({ - url: '/api/v1/pushpublish/filters/' - }) - .pipe(pluck('entity')); + return this.http + .get>('/api/v1/pushpublish/filters/') + .pipe(map((response) => response.entity)); } } diff --git a/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.spec.ts b/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.spec.ts index f17ff0d8a8de..da91d4af0c47 100644 --- a/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.spec.ts @@ -1,13 +1,9 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotRole } from '@dotcms/dotcms-models'; -import { - CoreWebServiceMock, - MockDotMessageService, - mockProcessedRoles -} from '@dotcms/utils-testing'; +import { MockDotMessageService, mockProcessedRoles } from '@dotcms/utils-testing'; import { DotRolesService } from './dot-roles.service'; @@ -35,10 +31,10 @@ describe('DotRolesService', () => { }); TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotRolesService, - { provide: CoreWebService, useClass: CoreWebServiceMock }, { provide: DotMessageService, useValue: messageServiceMock } ] }); diff --git a/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.ts b/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.ts index 828c22944e56..e8cd3a8eed7d 100644 --- a/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.ts +++ b/core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.ts @@ -1,11 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { map, pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotRole } from '@dotcms/dotcms-models'; +import { DotCMSResponse, DotRole } from '@dotcms/dotcms-models'; import { DotMessageService } from '../dot-messages/dot-messages.service'; @@ -16,7 +16,7 @@ const CURRENT_USER_KEY = 'CMS Anonymous'; }) export class DotRolesService { private dotMessageService = inject(DotMessageService); - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Return list of roles associated to specific role . @@ -25,11 +25,14 @@ export class DotRolesService { * @memberof DotRolesService */ get(roleId: string, roleHierarchy: boolean): Observable { - return this.coreWebService - .requestView({ - url: `/api/v1/roles/${roleId}/rolehierarchyanduserroles?roleHierarchyForAssign=${roleHierarchy}` - }) - .pipe(pluck('entity'), map(this.processRolesResponse.bind(this))); + return this.http + .get< + DotCMSResponse + >(`/api/v1/roles/${roleId}/rolehierarchyanduserroles?roleHierarchyForAssign=${roleHierarchy}`) + .pipe( + map((response) => response.entity), + map(this.processRolesResponse.bind(this)) + ); } /** @@ -38,11 +41,10 @@ export class DotRolesService { * @memberof DotRolesService */ search(): Observable { - return this.coreWebService - .requestView({ - url: `/api/v1/roles/_search` - }) - .pipe(pluck('entity'), map(this.processRolesResponse.bind(this))); + return this.http.get>('/api/v1/roles/_search').pipe( + map((response) => response.entity), + map(this.processRolesResponse.bind(this)) + ); } private processRolesResponse(roles: DotRole[]): DotRole[] { diff --git a/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.spec.ts b/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.spec.ts index 4c0802a558e0..74049dcef2a4 100644 --- a/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.spec.ts @@ -1,36 +1,19 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { getTestBed, TestBed } from '@angular/core/testing'; - -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; +import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator'; import { DotSiteBrowserService } from './dot-site-browser.service'; describe('DotSiteBrowserService', () => { - let injector: TestBed; - let httpMock: HttpTestingController; - let dotSiteBrowserService: DotSiteBrowserService; + let spectator: SpectatorHttp; + const createHttp = createHttpFactory(DotSiteBrowserService); - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotSiteBrowserService - ] - }); - injector = getTestBed(); - dotSiteBrowserService = injector.inject(DotSiteBrowserService); - httpMock = injector.inject(HttpTestingController); - }); + beforeEach(() => (spectator = createHttp())); it('should set Site Browser Selected folder', () => { - dotSiteBrowserService.setSelectedFolder('/test').subscribe(); + spectator.service.setSelectedFolder('/test').subscribe(); - const req = httpMock.expectOne('/api/v1/browser/selectedfolder'); - expect(req.request.method).toEqual('PUT'); + const req = spectator.expectOne('/api/v1/browser/selectedfolder', HttpMethod.PUT); expect(req.request.body).toEqual({ path: '/test' }); - req.flush({}); + req.flush({ entity: {} }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.ts b/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.ts index baf10b9caff1..ff692cd802e1 100644 --- a/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.ts +++ b/core-web/libs/data-access/src/lib/dot-site-browser/dot-site-browser.service.ts @@ -1,10 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { take } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService, ResponseView } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; /** * Provide util methods of backend Site Browser. @@ -13,22 +14,18 @@ import { CoreWebService, ResponseView } from '@dotcms/dotcms-js'; */ @Injectable() export class DotSiteBrowserService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Set the selected folder in the Site Browser portlet. - * @returns Observable<{}> + * @returns Observable> * @memberof DotSiteBrowserService */ - setSelectedFolder(path: string): Observable>> { - return this.coreWebService - .requestView>({ - body: { - path: path - }, - method: 'PUT', - url: '/api/v1/browser/selectedfolder' + setSelectedFolder(path: string): Observable> { + return this.http + .put>>('/api/v1/browser/selectedfolder', { + path }) - .pipe(take(1)); + .pipe(map((response) => response.entity)); } } diff --git a/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.spec.ts b/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.spec.ts index ecf9eee5d3f0..d142174fc716 100644 --- a/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.spec.ts @@ -1,9 +1,7 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - import { DotTagsService } from './dot-tags.service'; describe('DotTagsService', () => { @@ -17,8 +15,7 @@ describe('DotTagsService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [{ provide: CoreWebService, useClass: CoreWebServiceMock }, DotTagsService] + providers: [provideHttpClient(), provideHttpClientTesting(), DotTagsService] }); dotTagsService = TestBed.inject(DotTagsService); httpMock = TestBed.inject(HttpTestingController); @@ -29,7 +26,7 @@ describe('DotTagsService', () => { expect(res).toEqual([mockResponse.test, mockResponse.united]); }); - const req = httpMock.expectOne('v1/tags'); + const req = httpMock.expectOne('/api/v1/tags'); expect(req.request.method).toBe('GET'); req.flush(mockResponse); }); @@ -39,7 +36,7 @@ describe('DotTagsService', () => { expect(res).toEqual([mockResponse.test, mockResponse.united]); }); - const req = httpMock.expectOne('v1/tags?name=test'); + const req = httpMock.expectOne('/api/v1/tags?name=test'); expect(req.request.method).toBe('GET'); req.flush(mockResponse); }); diff --git a/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.ts b/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.ts index 63f7b584ac99..2c9a9063419d 100644 --- a/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.ts +++ b/core-web/libs/data-access/src/lib/dot-tags/dot-tags.service.ts @@ -1,11 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { map, pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotTag } from '@dotcms/dotcms-models'; +import { DotTag, DotCMSResponseJsonObject } from '@dotcms/dotcms-models'; /** * Provide util methods to get Tags available in the system. @@ -16,7 +16,7 @@ import { DotTag } from '@dotcms/dotcms-models'; providedIn: 'root' }) export class DotTagsService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Get tags suggestions @@ -24,12 +24,12 @@ export class DotTagsService { * @memberof DotTagDotTagsServicesService */ getSuggestions(name?: string): Observable { - return this.coreWebService - .requestView({ - url: `v1/tags${name ? `?name=${name}` : ''}` - }) + const httpOptions = name ? { params: new HttpParams().set('name', name) } : {}; + + return this.http + .get>('/api/v1/tags', httpOptions) .pipe( - pluck('bodyJsonObject'), + map((response) => response.bodyJsonObject), map((tags: { [key: string]: DotTag }) => { return Object.entries(tags).map(([_key, value]) => value); }) diff --git a/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.spec.ts b/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.spec.ts index f53f5a114ab0..14fab07baac5 100644 --- a/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.spec.ts @@ -1,13 +1,10 @@ import { expect, it, describe } from '@jest/globals'; import { of } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpErrorResponse, provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - import { DotTempFileUploadService } from './dot-temp-file-upload.service'; import { DotHttpErrorManagerService } from '../dot-http-error-manager/dot-http-error-manager.service'; @@ -19,10 +16,10 @@ describe('DotTempFileUploadService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotTempFileUploadService, - { provide: CoreWebService, useClass: CoreWebServiceMock }, { provide: DotHttpErrorManagerService, useValue: { diff --git a/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.ts b/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.ts index bb00283531d5..bd400b75684b 100644 --- a/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.ts +++ b/core-web/libs/data-access/src/lib/dot-temp-file-upload/dot-temp-file-upload.service.ts @@ -1,18 +1,22 @@ import { Observable } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map, pluck, take } from 'rxjs/operators'; +import { catchError, map, take } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotCMSTempFile } from '@dotcms/dotcms-models'; import { DotHttpErrorManagerService } from '../dot-http-error-manager/dot-http-error-manager.service'; +// Response type for temp file endpoints +interface DotTempFileResponse { + tempFiles: DotCMSTempFile[]; +} + @Injectable() export class DotTempFileUploadService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private dotHttpErrorManagerService = inject(DotHttpErrorManagerService); /** @@ -34,33 +38,29 @@ export class DotTempFileUploadService { const formData = new FormData(); formData.append('file', file); - return this.coreWebService - .requestView({ - url: `/api/v1/temp`, - body: formData, - headers: { 'Content-Type': 'multipart/form-data' }, - method: 'POST' - }) - .pipe( - pluck('tempFiles'), - catchError((error: HttpErrorResponse) => this.handleError(error)) - ); + const headers = new HttpHeaders({ + 'Content-Type': 'multipart/form-data' + }); + return this.http.post('/api/v1/temp', formData, { headers }).pipe( + map((response) => response.tempFiles), + catchError((error: HttpErrorResponse) => this.handleError(error)) + ); } private uploadByUrl(file: string): Observable { - return this.coreWebService - .requestView({ - url: `/api/v1/temp/byUrl`, - body: { + const headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + return this.http + .post( + '/api/v1/temp/byUrl', + { remoteUrl: file }, - headers: { - 'Content-Type': 'application/json' - }, - method: 'POST' - }) + { headers } + ) .pipe( - pluck('tempFiles'), + map((response) => response.tempFiles), catchError((error: HttpErrorResponse) => this.handleError(error)) ); } diff --git a/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.spec.ts b/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.spec.ts index d5b97702d01a..036959684c75 100644 --- a/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.spec.ts @@ -1,36 +1,41 @@ -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { DotTheme } from '@dotcms/dotcms-models'; -import { mockDotThemes, CoreWebServiceMock } from '@dotcms/utils-testing'; import { DotThemesService } from './dot-themes.service'; describe('DotThemesService', () => { - let dotThemesService: DotThemesService; + let service: DotThemesService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [{ provide: CoreWebService, useClass: CoreWebServiceMock }, DotThemesService] + providers: [provideHttpClient(), provideHttpClientTesting(), DotThemesService] }); - dotThemesService = TestBed.inject(DotThemesService); + service = TestBed.inject(DotThemesService); httpMock = TestBed.inject(HttpTestingController); }); - it('should get Themes', () => { - dotThemesService.get('inode').subscribe((themes: DotTheme) => { - expect(themes).toEqual(mockDotThemes[0]); + afterEach(() => { + httpMock.verify(); + }); + + it('should get theme by inode', () => { + const mockTheme: DotTheme = { + inode: 'test-inode', + name: 'Test Theme', + identifier: 'test-id', + hostId: 'test-host' + }; + + service.get('test-inode').subscribe((theme: DotTheme) => { + expect(theme).toEqual(mockTheme); }); - const req = httpMock.expectOne(`v1/themes/id/inode`); + const req = httpMock.expectOne('/api/v1/themes/id/test-inode'); expect(req.request.method).toBe('GET'); - req.flush({ entity: mockDotThemes[0] }); - }); - - afterEach(() => { - httpMock.verify(); + req.flush({ entity: mockTheme }); }); }); diff --git a/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.ts b/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.ts index 1a642f0bc784..478f6b829aa5 100644 --- a/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.ts +++ b/core-web/libs/data-access/src/lib/dot-themes/dot-themes.service.ts @@ -1,11 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { pluck } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { DotTheme } from '@dotcms/dotcms-models'; +import { DotCMSResponse, DotTheme } from '@dotcms/dotcms-models'; /** * Provide util methods to get themes information. @@ -14,7 +14,7 @@ import { DotTheme } from '@dotcms/dotcms-models'; */ @Injectable() export class DotThemesService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); /** * Get Theme information based on the inode. @@ -24,10 +24,8 @@ export class DotThemesService { * @memberof DotThemesService */ get(inode: string): Observable { - return this.coreWebService - .requestView({ - url: 'v1/themes/id/' + inode - }) - .pipe(pluck('entity')); + return this.http + .get>('/api/v1/themes/id/' + inode) + .pipe(map((response) => response.entity)); } } diff --git a/core-web/libs/data-access/src/lib/dot-workflows-actions/dot-workflows-actions.service.ts b/core-web/libs/data-access/src/lib/dot-workflows-actions/dot-workflows-actions.service.ts index 4a18ad470011..e18b2daa4130 100644 --- a/core-web/libs/data-access/src/lib/dot-workflows-actions/dot-workflows-actions.service.ts +++ b/core-web/libs/data-access/src/lib/dot-workflows-actions/dot-workflows-actions.service.ts @@ -5,11 +5,11 @@ import { Injectable, inject } from '@angular/core'; import { map, pluck } from 'rxjs/operators'; -import { DotCMSResponse } from '@dotcms/dotcms-js'; import { DotCMSContentletWorkflowActions, DotCMSWorkflow, - DotCMSWorkflowAction + DotCMSWorkflowAction, + DotCMSResponse } from '@dotcms/dotcms-models'; export enum DotRenderMode { diff --git a/core-web/libs/data-access/src/lib/paginator/paginator.service.spec.ts b/core-web/libs/data-access/src/lib/paginator/paginator.service.spec.ts index b73337ccf3b2..3d09bc67f1e5 100644 --- a/core-web/libs/data-access/src/lib/paginator/paginator.service.spec.ts +++ b/core-web/libs/data-access/src/lib/paginator/paginator.service.spec.ts @@ -1,10 +1,7 @@ -import { HttpHeaders } from '@angular/common/http'; -import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpHeaders, provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { CoreWebService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock } from '@dotcms/utils-testing'; - import { OrderDirection, PaginatorService } from './paginator.service'; describe('PaginatorService', () => { @@ -13,19 +10,18 @@ describe('PaginatorService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [{ provide: CoreWebService, useClass: CoreWebServiceMock }, PaginatorService] + providers: [provideHttpClient(), provideHttpClientTesting(), PaginatorService] }); paginatorService = TestBed.inject(PaginatorService); httpMock = TestBed.inject(HttpTestingController); - paginatorService.url = 'v1/urldemo'; + paginatorService.url = '/api/v1/urldemo'; }); it('should do a request with basic params', () => { paginatorService.get().subscribe(); const req = httpMock.expectOne(() => true); expect(req.request.method).toBe('GET'); - expect(req.request.url).toBe('v1/urldemo'); + expect(req.request.url).toBe('/api/v1/urldemo'); }); it('should do a request with basic pagination params', () => { @@ -33,7 +29,7 @@ describe('PaginatorService', () => { paginatorService.sortField = 'name'; paginatorService.sortOrder = OrderDirection.DESC; paginatorService.get().subscribe(); - httpMock.expectOne('v1/urldemo?filter=test&orderby=name&direction=DESC&per_page=40'); + httpMock.expectOne('/api/v1/urldemo?filter=test&orderby=name&direction=DESC&per_page=40'); }); it('should do a request with extra params', () => { @@ -41,7 +37,7 @@ describe('PaginatorService', () => { paginatorService.setExtraParams('system', 'true'); paginatorService.setExtraParams('live', null); paginatorService.get().subscribe(); - httpMock.expectOne('v1/urldemo?per_page=40&archive=false&system=true'); + httpMock.expectOne('/api/v1/urldemo?per_page=40&archive=false&system=true'); }); it('should remove extra parameters', () => { @@ -75,12 +71,11 @@ describe('PaginatorService getting', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [{ provide: CoreWebService, useClass: CoreWebServiceMock }, PaginatorService] + providers: [provideHttpClient(), provideHttpClientTesting(), PaginatorService] }); paginatorService = TestBed.inject(PaginatorService); httpMock = TestBed.inject(HttpTestingController); - paginatorService.url = 'v1/urldemo'; + paginatorService.url = '/api/v1/urldemo'; headerLink = `/baseURL?filter=filter&page=1>;rel="first", /baseURL?filter=filter&page=5>;rel="last", @@ -169,7 +164,7 @@ describe('PaginatorService getting', () => { it('should remove duplicated query params', () => { let headers = new HttpHeaders(); - paginatorService.url = 'v1/urldemo'; + paginatorService.url = '/api/v1/urldemo'; headerLink = `/baseURL?filter=filter&page=1&system=true>;rel="first", /baseURL?filter=filter&page=2&system=true>;rel="prev"`; diff --git a/core-web/libs/data-access/src/lib/paginator/paginator.service.ts b/core-web/libs/data-access/src/lib/paginator/paginator.service.ts index ca1d19a2bd64..84210ce74c25 100644 --- a/core-web/libs/data-access/src/lib/paginator/paginator.service.ts +++ b/core-web/libs/data-access/src/lib/paginator/paginator.service.ts @@ -1,10 +1,11 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { take, map } from 'rxjs/operators'; -import { CoreWebService, ResponseView } from '@dotcms/dotcms-js'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; export enum OrderDirection { ASC = 1, @@ -34,7 +35,7 @@ interface PaginatiorServiceParams { */ @Injectable() export class PaginatorService { - private readonly coreWebService = inject(CoreWebService); + private readonly http = inject(HttpClient); public static readonly LINK_HEADER_NAME = 'Link'; public static readonly PAGINATION_PER_PAGE_HEADER_NAME = 'X-Pagination-Per-Page'; @@ -158,39 +159,42 @@ export class PaginatorService { */ // tslint:disable-next-line:cyclomatic-complexity public get(url?: string): Observable { - const params: Map = { + const params: Record = { ...this.getParams(), ...this.getObjectFromMap(this.extraParams) }; const cleanURL = this.sanitizeQueryParams(url, params); + const requestUrl = cleanURL || this.url; - return this.coreWebService - .requestView({ - params, - url: cleanURL || this.url + return this.http + .get>(requestUrl, { + params: params as Record, + observe: 'response' }) .pipe( - map((response: ResponseView) => { - this.setLinks(response.header(PaginatorService.LINK_HEADER_NAME)); + map((response: HttpResponse>) => { + this.setLinks(response.headers.get(PaginatorService.LINK_HEADER_NAME)); this.paginationPerPage = parseInt( - response.header(PaginatorService.PAGINATION_PER_PAGE_HEADER_NAME), + response.headers.get(PaginatorService.PAGINATION_PER_PAGE_HEADER_NAME), 10 ); this.currentPage = parseInt( - response.header(PaginatorService.PAGINATION_CURRENT_PAGE_HEADER_NAME), + response.headers.get(PaginatorService.PAGINATION_CURRENT_PAGE_HEADER_NAME), 10 ); this.maxLinksPage = parseInt( - response.header(PaginatorService.PAGINATION_MAX_LINK_PAGES_HEADER_NAME), + response.headers.get( + PaginatorService.PAGINATION_MAX_LINK_PAGES_HEADER_NAME + ), 10 ); this.totalRecords = parseInt( - response.header(PaginatorService.PAGINATION_TOTAL_ENTRIES_HEADER_NAME), + response.headers.get(PaginatorService.PAGINATION_TOTAL_ENTRIES_HEADER_NAME), 10 ); - return response.entity; + return response.body.entity; }), take(1) ); @@ -325,11 +329,11 @@ export class PaginatorService { * Use to remove repeated query params in the url * @private * @param {string} [url=''] - * @param {Map} params + * @param {Record} params * @return {*} {string} * @memberof PaginatorService */ - private sanitizeQueryParams(url = '', params: Map): string { + private sanitizeQueryParams(url = '', params: Record): string { const urlArr = url?.split('?'); const baseUrl = urlArr[0]; const queryParams = urlArr[1]; diff --git a/core-web/libs/data-access/src/lib/push-publish/push-publish.service.spec.ts b/core-web/libs/data-access/src/lib/push-publish/push-publish.service.spec.ts index 36ee07af1bc1..423b014ed905 100644 --- a/core-web/libs/data-access/src/lib/push-publish/push-publish.service.spec.ts +++ b/core-web/libs/data-access/src/lib/push-publish/push-publish.service.spec.ts @@ -2,15 +2,16 @@ import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/ import { format } from 'date-fns'; import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { ApiRoot, CoreWebService, LoggerService, StringUtils, UserModel } from '@dotcms/dotcms-js'; +import { ApiRoot, LoggerService, StringUtils, UserModel } from '@dotcms/dotcms-js'; import { DotAjaxActionResponseView, DotCurrentUser, DotPushPublishData } from '@dotcms/dotcms-models'; -import { CoreWebServiceMock, DotFormatDateServiceMock } from '@dotcms/utils-testing'; +import { DotFormatDateServiceMock } from '@dotcms/utils-testing'; import { PushPublishService } from './push-publish.service'; @@ -39,10 +40,10 @@ describe('PushPublishService', () => { let dotCurrentUserService: DotCurrentUserService; const createHttp = createHttpFactory({ service: PushPublishService, - imports: [HttpClientTestingModule], mocks: [DotCurrentUserService], providers: [ - { provide: CoreWebService, useClass: CoreWebServiceMock }, + provideHttpClient(), + provideHttpClientTesting(), { provide: DotFormatDateService, useClass: DotFormatDateServiceMock }, ApiRoot, LoggerService, diff --git a/core-web/libs/data-access/src/lib/push-publish/push-publish.service.ts b/core-web/libs/data-access/src/lib/push-publish/push-publish.service.ts index cc717ed44a96..81aa56b79187 100644 --- a/core-web/libs/data-access/src/lib/push-publish/push-publish.service.ts +++ b/core-web/libs/data-access/src/lib/push-publish/push-publish.service.ts @@ -1,15 +1,17 @@ import { Observable } from 'rxjs'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { filter, map, mergeMap, pluck, toArray } from 'rxjs/operators'; +import { filter, map, mergeMap, toArray } from 'rxjs/operators'; -import { ApiRoot, CoreWebService, ResponseView } from '@dotcms/dotcms-js'; +import { ApiRoot } from '@dotcms/dotcms-js'; import { DotAjaxActionResponseView, DotCurrentUser, DotEnvironment, - DotPushPublishData + DotPushPublishData, + DotCMSResponseJsonObject } from '@dotcms/dotcms-models'; import { DotCurrentUserService } from '../dot-current-user/dot-current-user.service'; @@ -23,17 +25,15 @@ import { DotFormatDateService } from '../dot-format-date/dot-format-date.service @Injectable() export class PushPublishService { _apiRoot = inject(ApiRoot); - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private currentUser = inject(DotCurrentUserService); private dotFormatDateService = inject(DotFormatDateService); private pushEnvironementsUrl = '/api/environment/loadenvironments/roleId'; - /* - TODO: I had to do this because this line concat'api/' into the URL - https://github.com/dotCMS/dotcms-js/blob/master/src/core/core-web.service.ts#L169 - */ - private publishUrl = `/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/publish`; - private publishBundleURL = `/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/pushBundle`; + private publishUrl = + '/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/publish'; + private publishBundleURL = + '/DotAjaxDirector/com.dotcms.publisher.ajax.RemotePublishAjaxAction/cmd/pushBundle'; private _lastEnvironmentPushed!: string[]; @@ -49,11 +49,12 @@ export class PushPublishService { getEnvironments(): Observable { return this.currentUser.getCurrentUser().pipe( mergeMap((user: DotCurrentUser) => { - return this.coreWebService.requestView({ - url: `${this.pushEnvironementsUrl}/${user.roleId}` - }); + return this.http + .get< + DotCMSResponseJsonObject + >(`${this.pushEnvironementsUrl}/${user.roleId}`) + .pipe(map((response) => response.bodyJsonObject)); }), - pluck, DotEnvironment[]>('bodyJsonObject'), mergeMap((environments: DotEnvironment[]) => environments), filter((environment: DotEnvironment) => environment.name !== ''), toArray() @@ -74,16 +75,16 @@ export class PushPublishService { ): Observable { this._lastEnvironmentPushed = pushPublishData.environment; - return this.coreWebService - .request({ - body: this.getPublishEnvironmentData(assetIdentifier, pushPublishData), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - method: 'POST', - url: isBundle ? this.publishBundleURL : this.publishUrl - }) - .pipe(map((res) => res as DotAjaxActionResponseView)); + const headers = new HttpHeaders({ + 'Content-Type': 'application/x-www-form-urlencoded' + }); + + const body = this.getPublishEnvironmentData(assetIdentifier, pushPublishData); + const url = isBundle ? this.publishBundleURL : this.publishUrl; + + return this.http + .post(url, body, { headers }) + .pipe(map((res) => res)); } private getPublishEnvironmentData( diff --git a/core-web/libs/dot-rules/src/lib/components/restdropdown/RestDropdown.ts b/core-web/libs/dot-rules/src/lib/components/restdropdown/RestDropdown.ts index 5e557321a0ee..0f07e1a2fb41 100644 --- a/core-web/libs/dot-rules/src/lib/components/restdropdown/RestDropdown.ts +++ b/core-web/libs/dot-rules/src/lib/components/restdropdown/RestDropdown.ts @@ -1,5 +1,6 @@ import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Component, EventEmitter, @@ -14,7 +15,6 @@ import { NgControl, ControlValueAccessor } from '@angular/forms'; import { map } from 'rxjs/operators'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { isEmpty } from '@dotcms/utils'; import { Verify } from '../../services/validation/Verify'; @@ -36,7 +36,7 @@ import { Verify } from '../../services/validation/Verify'; standalone: false }) export class RestDropdown implements AfterViewInit, OnChanges, ControlValueAccessor { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); control = inject(NgControl, { optional: true }); @Input() placeholder: string; @@ -117,10 +117,8 @@ export class RestDropdown implements AfterViewInit, OnChanges, ControlValueAcces ngOnChanges(change): void { if (change.optionUrl) { - this._options = this.coreWebService - .request({ - url: change.optionUrl.currentValue - }) + this._options = this.http + .get(change.optionUrl.currentValue) .pipe(map((res: any) => this.jsonEntriesToOptions(res))); } diff --git a/core-web/libs/dot-rules/src/lib/rule-engine.module.ts b/core-web/libs/dot-rules/src/lib/rule-engine.module.ts index cb410b895310..9699dfbf680f 100644 --- a/core-web/libs/dot-rules/src/lib/rule-engine.module.ts +++ b/core-web/libs/dot-rules/src/lib/rule-engine.module.ts @@ -19,7 +19,6 @@ import { TooltipModule } from 'primeng/tooltip'; import { ApiRoot, - CoreWebService, DotcmsConfigService, DotcmsEventsService, LoggerService, @@ -43,20 +42,20 @@ import { AreaPickerDialogComponent } from './google-map/area-picker-dialog.compo import { ModalDialogComponent } from './modal-dialog/dialog-component'; import { AddToBundleDialogComponent } from './push-publish/add-to-bundle-dialog-component'; import { AddToBundleDialogContainer } from './push-publish/add-to-bundle-dialog-container'; +import { RuleActionComponent } from './rule-action-component'; +import { RuleComponent } from './rule-component'; import { ConditionComponent } from './rule-condition-component'; +import { ConditionGroupComponent } from './rule-condition-group-component'; +import { RuleEngineComponent } from './rule-engine'; +import { RuleEngineContainer } from './rule-engine.container'; import { ActionService } from './services/Action'; import { BundleService } from './services/bundle-service'; -import { ConditionGroupComponent } from './rule-condition-group-component'; import { ConditionService } from './services/Condition'; import { ConditionGroupService } from './services/ConditionGroup'; import { RuleViewService } from './services/dot-view-rule-service'; import { GoogleMapService } from './services/GoogleMapService'; import { RuleService } from './services/Rule'; import { I18nService } from './services/system/locale/I18n'; -import { RuleActionComponent } from './rule-action-component'; -import { RuleComponent } from './rule-component'; -import { RuleEngineComponent } from './rule-engine'; -import { RuleEngineContainer } from './rule-engine.container'; @NgModule({ imports: [ @@ -103,7 +102,6 @@ import { RuleEngineContainer } from './rule-engine.container'; providers: [ ApiRoot, BrowserUtil, - CoreWebService, DotcmsConfigService, DotcmsEventsService, LoggerService, diff --git a/core-web/libs/dot-rules/src/lib/services/Action.ts b/core-web/libs/dot-rules/src/lib/services/Action.ts index 4d40705824f0..373ac9a7c3e3 100644 --- a/core-web/libs/dot-rules/src/lib/services/Action.ts +++ b/core-web/libs/dot-rules/src/lib/services/Action.ts @@ -1,29 +1,27 @@ -import { from as observableFrom, empty as observableEmpty, Subject } from 'rxjs'; -import { Observable } from 'rxjs'; +import { from as observableFrom, empty as observableEmpty, Subject, Observable } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { mergeMap, reduce, catchError, map } from 'rxjs/operators'; -import { ApiRoot } from '@dotcms/dotcms-js'; -import { CoreWebService } from '@dotcms/dotcms-js'; import { + ApiRoot, UNKNOWN_RESPONSE_ERROR, CwError, SERVER_RESPONSE_ERROR, NETWORK_CONNECTION_ERROR, - CLIENTS_ONLY_MESSAGES + CLIENTS_ONLY_MESSAGES, + LoggerService, + HttpCode } from '@dotcms/dotcms-js'; -import { LoggerService } from '@dotcms/dotcms-js'; -import { HttpCode } from '@dotcms/dotcms-js'; import { ActionModel } from './Rule'; import { ServerSideTypeModel } from './ServerSideFieldModel'; @Injectable() export class ActionService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private loggerService = inject(LoggerService); private _typeName = 'Action'; @@ -67,30 +65,26 @@ export class ActionService { path = `${path}${childPath}`; } - return this.coreWebService - .request({ - url: path - }) - .pipe( - catchError((err: any, _source: Observable) => { - if (err && err.status === HttpCode.NOT_FOUND) { - this.loggerService.error( - 'Could not retrieve ' + this._typeName + ' : 404 path not valid.', - path - ); - } else if (err) { - this.loggerService.debug( - 'Could not retrieve' + this._typeName + ': Response status code: ', - err.status, - 'error:', - err, - path - ); - } + return this.http.get(path).pipe( + catchError((err: HttpErrorResponse, _source: Observable) => { + if (err && err.status === HttpCode.NOT_FOUND) { + this.loggerService.error( + 'Could not retrieve ' + this._typeName + ' : 404 path not valid.', + path + ); + } else if (err) { + this.loggerService.debug( + 'Could not retrieve' + this._typeName + ': Response status code: ', + err.status, + 'error:', + err, + path + ); + } - return observableEmpty(); - }) - ); + return observableEmpty(); + }) + ); } allAsArray( @@ -145,20 +139,13 @@ and should provide the info needed to make the user aware of the fix.`); json.owningRule = ruleId; const path = this._getPath(ruleId); - const add = this.coreWebService - .request({ - method: 'POST', - body: json, - url: path - }) - .pipe( - map((res: HttpResponse) => { - const json: any = res; - model.key = json.id; + const add = this.http.post(path, json).pipe( + map((res: any) => { + model.key = res.id; - return model; - }) - ); + return model; + }) + ); return add.pipe(catchError(this._catchRequestError('add'))); } @@ -175,33 +162,22 @@ and should provide the info needed to make the user aware of the fix.`); } else { const json = ActionService.toJson(model); json.owningRule = ruleId; - const save = this.coreWebService - .request({ - method: 'PUT', - body: json, - url: this._getPath(ruleId, model.key) + const save = this.http.put(this._getPath(ruleId, model.key), json).pipe( + map((_res: any) => { + return model; }) - .pipe( - map((_res: HttpResponse) => { - return model; - }) - ); + ); return save.pipe(catchError(this._catchRequestError('save'))); } } remove(ruleId, model: ActionModel): Observable { - const remove = this.coreWebService - .request({ - method: 'DELETE', - url: this._getPath(ruleId, model.key) + const remove = this.http.delete(this._getPath(ruleId, model.key)).pipe( + map((_res: any) => { + return model; }) - .pipe( - map((_res: HttpResponse) => { - return model; - }) - ); + ); return remove.pipe(catchError(this._catchRequestError('remove'))); } @@ -217,11 +193,14 @@ and should provide the info needed to make the user aware of the fix.`); private _catchRequestError( _operation - ): (response: HttpResponse, original: Observable) => Observable { - return (response: HttpResponse): Observable => { + ): (response: HttpErrorResponse, original: Observable) => Observable { + return (response: HttpErrorResponse): Observable => { if (response) { if (response.status === HttpCode.SERVER_ERROR) { - if (response.body && response.body.indexOf('ECONNREFUSED') >= 0) { + if ( + response.error && + JSON.stringify(response.error).indexOf('ECONNREFUSED') >= 0 + ) { throw new CwError( NETWORK_CONNECTION_ERROR, CLIENTS_ONLY_MESSAGES[NETWORK_CONNECTION_ERROR] @@ -247,7 +226,7 @@ and should provide the info needed to make the user aware of the fix.`); ); this._error.next( - response.body.error.replace('dotcms.api.error.forbidden: ', '') + response.error?.error?.replace('dotcms.api.error.forbidden: ', '') ?? '' ); throw new CwError( diff --git a/core-web/libs/dot-rules/src/lib/services/Condition.ts b/core-web/libs/dot-rules/src/lib/services/Condition.ts index 64425d1217d3..8ed697922cb2 100644 --- a/core-web/libs/dot-rules/src/lib/services/Condition.ts +++ b/core-web/libs/dot-rules/src/lib/services/Condition.ts @@ -1,14 +1,11 @@ -import { from as observableFrom, empty as observableEmpty, Subject } from 'rxjs'; -import { Observable } from 'rxjs'; +import { from as observableFrom, empty as observableEmpty, Subject, Observable } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { reduce, mergeMap, catchError, map } from 'rxjs/operators'; -import { ApiRoot } from '@dotcms/dotcms-js'; -import { HttpCode } from '@dotcms/dotcms-js'; -import { CoreWebService, LoggerService } from '@dotcms/dotcms-js'; +import { ApiRoot, HttpCode, LoggerService } from '@dotcms/dotcms-js'; import { ConditionGroupModel, ConditionModel, ICondition } from './Rule'; import { ServerSideTypeModel } from './ServerSideFieldModel'; @@ -18,16 +15,17 @@ import { ServerSideTypeModel } from './ServerSideFieldModel'; @Injectable() export class ConditionService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private loggerService = inject(LoggerService); - public get error(): Observable { - return this._error.asObservable(); - } private _baseUrl: string; private _error: Subject = new Subject(); + public get error(): Observable { + return this._error.asObservable(); + } + constructor() { const apiRoot = inject(ApiRoot); @@ -67,29 +65,25 @@ export class ConditionService { } makeRequest(childPath: string): Observable { - return this.coreWebService - .request({ - url: this._baseUrl + '/' + childPath + const path = `${this._baseUrl}/${childPath}`; + + return this.http.get(path).pipe( + catchError((err: HttpErrorResponse, _source: Observable) => { + if (err && err.status === HttpCode.NOT_FOUND) { + this.loggerService.info('Could not retrieve Condition Types: URL not valid.'); + } else if (err) { + this.loggerService.info( + 'Could not retrieve Condition Types.', + 'response status code: ', + err.status, + 'error:', + err + ); + } + + return observableEmpty(); }) - .pipe( - catchError((err: any, _source: Observable) => { - if (err && err.status === HttpCode.NOT_FOUND) { - this.loggerService.info( - 'Could not retrieve Condition Types: URL not valid.' - ); - } else if (err) { - this.loggerService.info( - 'Could not retrieve Condition Types.', - 'response status code: ', - err.status, - 'error:', - err - ); - } - - return observableEmpty(); - }) - ); + ); } listForGroup( @@ -134,20 +128,14 @@ export class ConditionService { const json = ConditionService.toJson(model); json.owningGroup = groupId; - const add = this.coreWebService - .request({ - method: 'POST', - body: json, - url: this._baseUrl + '/' - }) - .pipe( - map((res: HttpResponse) => { - const json: any = res; - model.key = json.id; - return model; - }) - ); + const add = this.http.post(`${this._baseUrl}/`, json).pipe( + map((res: any) => { + model.key = res.id; + + return model; + }) + ); return add.pipe(catchError(this._catchRequestError('add'))); } @@ -164,40 +152,31 @@ export class ConditionService { } else { const json = ConditionService.toJson(model); json.owningGroup = groupId; - const body = JSON.stringify(json); - const save = this.coreWebService - .request({ - method: 'PUT', - body: body, - url: this._baseUrl + '/' + model.key + + const save = this.http.put(`${this._baseUrl}/${model.key}`, json).pipe( + map((_res: any) => { + return model; }) - .pipe( - map((_res: HttpResponse) => { - return model; - }) - ); + ); return save.pipe(catchError(this._catchRequestError('save'))); } } remove(model: ConditionModel): Observable { - const remove = this.coreWebService - .request({ - method: 'DELETE', - url: this._baseUrl + '/' + model.key + const remove = this.http.delete(`${this._baseUrl}/${model.key}`).pipe( + map((_res: any) => { + return model; }) - .pipe( - map((_res: HttpResponse) => { - return model; - }) - ); + ); return remove.pipe(catchError(this._catchRequestError('remove'))); } - private _catchRequestError(operation): (any) => Observable { - return (err: any) => { + private _catchRequestError( + operation: string + ): (response: HttpErrorResponse, original: Observable) => Observable { + return (err: HttpErrorResponse): Observable => { if (err && err.status === HttpCode.NOT_FOUND) { this.loggerService.info('Could not ' + operation + ' Condition: URL not valid.'); } else if (err) { @@ -210,7 +189,7 @@ export class ConditionService { ); } - this._error.next(err.json().error.replace('dotcms.api.error.forbidden: ', '')); + this._error.next(err.error?.error?.replace('dotcms.api.error.forbidden: ', '') ?? ''); return observableEmpty(); }; diff --git a/core-web/libs/dot-rules/src/lib/services/ConditionGroup.ts b/core-web/libs/dot-rules/src/lib/services/ConditionGroup.ts index d6d7a3f9b7e7..b38743df3395 100644 --- a/core-web/libs/dot-rules/src/lib/services/ConditionGroup.ts +++ b/core-web/libs/dot-rules/src/lib/services/ConditionGroup.ts @@ -1,49 +1,59 @@ -import { from as observableFrom, empty as observableEmpty, Subject } from 'rxjs'; -import { Observable } from 'rxjs'; +import { from as observableFrom, empty as observableEmpty, Subject, Observable } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { reduce, mergeMap, catchError, map, tap } from 'rxjs/operators'; +import { reduce, mergeMap, catchError, map } from 'rxjs/operators'; -import { ApiRoot } from '@dotcms/dotcms-js'; -import { HttpCode } from '@dotcms/dotcms-js'; -import { CoreWebService, LoggerService } from '@dotcms/dotcms-js'; +import { ApiRoot, HttpCode, LoggerService } from '@dotcms/dotcms-js'; import { ConditionGroupModel, IConditionGroup } from './Rule'; +interface ConditionGroupJson { + id: string; + operator: string; + priority: number; + conditions: Record; +} + +interface ConditionGroupResponse { + id: string; +} + @Injectable() export class ConditionGroupService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private loggerService = inject(LoggerService); - public get error(): Observable { - return this._error.asObservable(); - } private _typeName = 'Condition Group'; private _baseUrl: string; private _error: Subject = new Subject(); + public get error(): Observable { + return this._error.asObservable(); + } + constructor() { const apiRoot = inject(ApiRoot); - this._baseUrl = '/api/v1/sites/' + apiRoot.siteId + '/ruleengine/rules'; + this._baseUrl = `/api/v1/sites/${apiRoot.siteId}/ruleengine/rules`; } - static toJson(conditionGroup: ConditionGroupModel): any { - const json: any = {}; - json.id = conditionGroup.key; - json.operator = conditionGroup.operator; - json.priority = conditionGroup.priority; - json.conditions = conditionGroup.conditions; - - return json; + static toJson(conditionGroup: ConditionGroupModel): ConditionGroupJson { + return { + id: conditionGroup.key, + operator: conditionGroup.operator, + priority: conditionGroup.priority, + conditions: conditionGroup.conditions + }; } - static toJsonList(models: { [key: string]: ConditionGroupModel }): any { - const list = {}; + static toJsonList(models: { + [key: string]: ConditionGroupModel; + }): Record { + const list: Record = {}; Object.keys(models).forEach((key) => { list[key] = ConditionGroupService.toJson(models[key]); }); @@ -51,37 +61,32 @@ export class ConditionGroupService { return list; } - makeRequest(path: string): Observable { - return this.coreWebService - .request({ - url: path + makeRequest(path: string): Observable { + return this.http.get(path).pipe( + map((res) => { + this.loggerService.info('ConditionGroupService', 'makeRequest-Response', res); + + return res; + }), + catchError((err: HttpErrorResponse, _source: Observable) => { + if (err && err.status === HttpCode.NOT_FOUND) { + this.loggerService.error( + 'Could not retrieve ' + this._typeName + ' : 404 path not valid.', + path + ); + } else if (err) { + this.loggerService.info( + 'Could not retrieve' + this._typeName + ': Response status code: ', + err.status, + 'error:', + err, + path + ); + } + + return observableEmpty(); }) - .pipe( - map((res: HttpResponse) => { - const json = res; - this.loggerService.info('ConditionGroupService', 'makeRequest-Response', json); - - return json; - }), - catchError((err: any, _source: Observable) => { - if (err && err.status === HttpCode.NOT_FOUND) { - this.loggerService.error( - 'Could not retrieve ' + this._typeName + ' : 404 path not valid.', - path - ); - } else if (err) { - this.loggerService.info( - 'Could not retrieve' + this._typeName + ': Response status code: ', - err.status, - 'error:', - err, - path - ); - } - - return observableEmpty(); - }) - ); + ); } all(ruleKey: string, keys: string[]): Observable { @@ -103,8 +108,7 @@ export class ConditionGroupService { } get(ruleKey: string, key: string): Observable { - let result: Observable; - result = this.makeRequest(this._getPath(ruleKey, key)).pipe( + return this.makeRequest(this._getPath(ruleKey, key)).pipe( map((json: IConditionGroup) => { json.id = key; this.loggerService.info( @@ -115,11 +119,12 @@ export class ConditionGroupService { return new ConditionGroupModel(json); }) ); - - return result; } - createConditionGroup(ruleId: string, model: ConditionGroupModel): Observable { + createConditionGroup( + ruleId: string, + model: ConditionGroupModel + ): Observable { this.loggerService.info('ConditionGroupService', 'add', model); if (!model.isValid()) { throw new Error(`This should be thrown from a checkValid function on the model, @@ -129,20 +134,13 @@ export class ConditionGroupService { const json = ConditionGroupService.toJson(model); const path = this._getPath(ruleId); - const add = this.coreWebService - .request({ - method: 'POST', - body: json, - url: path - }) - .pipe( - map((res: HttpResponse) => { - const json: any = res; - model.key = json.id; + const add = this.http.post(path, json).pipe( + map((res) => { + model.key = res.id; - return model; - }) - ); + return model; + }) + ); return add.pipe(catchError(this._catchRequestError('add'))); } @@ -161,33 +159,22 @@ export class ConditionGroupService { this.createConditionGroup(ruleId, model); } else { const json = ConditionGroupService.toJson(model); - const save = this.coreWebService - .request({ - method: 'PUT', - body: json, - url: this._getPath(ruleId, model.key) + const save = this.http.put(this._getPath(ruleId, model.key), json).pipe( + map(() => { + return model; }) - .pipe( - tap(() => { - return model; - }) - ); + ); return save.pipe(catchError(this._catchRequestError('save'))); } } remove(ruleId: string, model: ConditionGroupModel): Observable { - const remove = this.coreWebService - .request({ - method: 'DELETE', - url: this._getPath(ruleId, model.key) + const remove = this.http.delete(this._getPath(ruleId, model.key)).pipe( + map(() => { + return model; }) - .pipe( - tap(() => { - return model; - }) - ); + ); return remove.pipe(catchError(this._catchRequestError('remove'))); } @@ -201,8 +188,13 @@ export class ConditionGroupService { return p; } - private _catchRequestError(operation): Func { - return (err: any) => { + private _catchRequestError( + operation: string + ): ( + response: HttpErrorResponse, + original: Observable + ) => Observable { + return (err: HttpErrorResponse): Observable => { if (err && err.status === HttpCode.NOT_FOUND) { this.loggerService.info('Could not ' + operation + ' Condition: URL not valid.'); } else if (err) { @@ -215,11 +207,9 @@ export class ConditionGroupService { ); } - this._error.next(err.json().error.replace('dotcms.api.error.forbidden: ', '')); + this._error.next(err.error?.error?.replace('dotcms.api.error.forbidden: ', '') ?? ''); return observableEmpty(); }; } } - -type Func = (any) => Observable; diff --git a/core-web/libs/dot-rules/src/lib/services/Rule.ts b/core-web/libs/dot-rules/src/lib/services/Rule.ts index b0e992be3e67..bee44f5bb47a 100644 --- a/core-web/libs/dot-rules/src/lib/services/Rule.ts +++ b/core-web/libs/dot-rules/src/lib/services/Rule.ts @@ -1,12 +1,12 @@ import { from as observableFrom, Subject, Observable, BehaviorSubject } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpResponse } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { mergeMap, reduce, map, tap } from 'rxjs/operators'; // tslint:disable-next-line:max-file-line-count -import { CoreWebService, SiteService, CwError, ApiRoot } from '@dotcms/dotcms-js'; +import { ApiRoot, CwError, SiteService } from '@dotcms/dotcms-js'; import { ServerSideFieldModel, ServerSideTypeModel } from './ServerSideFieldModel'; import { I18nService } from './system/locale/I18n'; @@ -254,7 +254,7 @@ export class RuleService { _apiRoot = inject(ApiRoot); private _resources = inject(I18nService); private siteService = inject(SiteService); - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); get rules(): RuleModel[] { return this._rules; @@ -376,15 +376,16 @@ export class RuleService { createRule(body: RuleModel): Observable { const siteId = this.loadRulesSiteId(); - return this.coreWebService - .request({ - body: RuleService.fromClientRuleTransformFn(body), - method: 'POST', - url: `/api/v1/sites/${siteId}${this._rulesEndpointUrl}` - }) + return this.http + .post<{ + id: string; + }>( + `/api/v1/sites/${siteId}${this._rulesEndpointUrl}`, + RuleService.fromClientRuleTransformFn(body) + ) .pipe( - map((result: HttpResponse) => { - body.key = result['id']; // @todo:ggranum type the POST result correctly. + map((result) => { + body.key = result['id']; return ( (Object.assign({}, DEFAULT_RULE, body, result)) @@ -396,16 +397,11 @@ export class RuleService { deleteRule(ruleId: string): Observable<{ success: boolean } | CwError> { const siteId = this.loadRulesSiteId(); - return this.coreWebService - .request({ - method: 'DELETE', - url: `/api/v1/sites/${siteId}${this._rulesEndpointUrl}/${ruleId}` + return this.http.delete(`/api/v1/sites/${siteId}${this._rulesEndpointUrl}/${ruleId}`).pipe( + map((_result) => { + return { success: true }; }) - .pipe( - map((_result) => { - return { success: true }; - }) - ); + ); } loadRules(): Observable { @@ -427,17 +423,13 @@ export class RuleService { loadRule(id: string): Observable { const siteId = this.loadRulesSiteId(); - return this.coreWebService - .request({ - url: `/api/v1/sites/${siteId}${this._rulesEndpointUrl}/${id}` + return this.http.get(`/api/v1/sites/${siteId}${this._rulesEndpointUrl}/${id}`).pipe( + map((result) => { + return ( + (Object.assign({ key: id }, DEFAULT_RULE, result)) + ); }) - .pipe( - map((result) => { - return ( - (Object.assign({ key: id }, DEFAULT_RULE, result)) - ); - }) - ); + ); } updateRule(id: string, rule: RuleModel): Observable { @@ -446,12 +438,11 @@ export class RuleService { if (!id) { result = this.createRule(rule); } else { - result = this.coreWebService - .request({ - body: RuleService.fromClientRuleTransformFn(rule), - method: 'PUT', - url: `/api/v1/sites/${siteId}${this._rulesEndpointUrl}/${id}` - }) + result = this.http + .put( + `/api/v1/sites/${siteId}${this._rulesEndpointUrl}/${id}`, + RuleService.fromClientRuleTransformFn(rule) + ) .pipe( map((res) => { const r = Object.assign({}, DEFAULT_RULE, res); @@ -466,34 +457,26 @@ export class RuleService { } getConditionTypes(): Observable { - return this.coreWebService - .request({ - url: this._conditionTypesEndpointUrl - }) + return this.http + .get(this._conditionTypesEndpointUrl) .pipe(map(this.fromServerServersideTypesTransformFn)); } getRuleActionTypes(): Observable { - return this.coreWebService - .request({ - url: this._ruleActionTypesEndpointUrl - }) + return this.http + .get(this._ruleActionTypesEndpointUrl) .pipe(map(this.fromServerServersideTypesTransformFn)); } _doLoadRuleActionTypes(): Observable { - return this.coreWebService - .request({ - url: this._ruleActionTypesEndpointUrl - }) + return this.http + .get(this._ruleActionTypesEndpointUrl) .pipe(map(this.fromServerServersideTypesTransformFn)); } _doLoadConditionTypes(): Observable { - return this.coreWebService - .request({ - url: this._conditionTypesEndpointUrl - }) + return this.http + .get(this._conditionTypesEndpointUrl) .pipe(map(this.fromServerServersideTypesTransformFn)); } @@ -521,21 +504,17 @@ export class RuleService { } private sendLoadRulesRequest(siteId: string): void { - this.coreWebService - .request({ - url: `/api/v1/sites/${siteId}/ruleengine/rules` - }) - .subscribe( - (ruleMap) => { - this._rules = RuleService.fromServerRulesTransformFn(ruleMap); - this._rules$.next(this.rules); - - return RuleService.fromServerRulesTransformFn(ruleMap); - }, - (err) => { - this._errors$.next(err); - } - ); + this.http.get(`/api/v1/sites/${siteId}/ruleengine/rules`).subscribe( + (ruleMap) => { + this._rules = RuleService.fromServerRulesTransformFn(ruleMap); + this._rules$.next(this.rules); + + return RuleService.fromServerRulesTransformFn(ruleMap); + }, + (err) => { + this._errors$.next(err); + } + ); } private loadActionTypes(): Observable { diff --git a/core-web/libs/dot-rules/src/lib/services/bundle-service.ts b/core-web/libs/dot-rules/src/lib/services/bundle-service.ts index f3dd6f7804cf..3cc2f9c93c4f 100644 --- a/core-web/libs/dot-rules/src/lib/services/bundle-service.ts +++ b/core-web/libs/dot-rules/src/lib/services/bundle-service.ts @@ -1,13 +1,10 @@ import { of as observableOf, Observable, Subject } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { map, mergeMap } from 'rxjs/operators'; -import { ApiRoot } from '@dotcms/dotcms-js'; -import { CoreWebService } from '@dotcms/dotcms-js'; - export interface IUser { givenName?: string; surname?: string; @@ -27,8 +24,7 @@ export interface IPublishEnvironment { @Injectable() export class BundleService { - _apiRoot = inject(ApiRoot); - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); bundles$: Subject = new Subject(); @@ -66,11 +62,7 @@ export class BundleService { * @deprecated use getCurrentUser in LoginService */ getLoggedUser(): Observable { - return this.coreWebService - .request({ - url: this._loggedUserUrl - }) - .pipe(map((res: HttpResponse) => res)); + return this.http.get(this._loggedUserUrl); } loadBundleStores(): void { @@ -85,10 +77,8 @@ export class BundleService { _doLoadBundleStores(): Observable { return this.getLoggedUser().pipe( mergeMap((user: IUser) => { - return this.coreWebService - .request({ - url: `${this._bundleStoreUrl}/${user.userId}` - }) + return this.http + .get<{ items: IBundle[] }>(`${this._bundleStoreUrl}/${user.userId}`) .pipe(map(BundleService.fromServerBundleTransformFn)); }) ); @@ -114,10 +104,8 @@ export class BundleService { _doLoadPublishEnvironments(): Observable { return this.getLoggedUser().pipe( mergeMap((user: IUser) => { - return this.coreWebService - .request({ - url: `${this._pushEnvironementsUrl}/${user.roleId}/` - }) + return this.http + .get(`${this._pushEnvironementsUrl}/${user.roleId}/`) .pipe(map(BundleService.fromServerEnvironmentTransformFn)); }) ); @@ -127,48 +115,32 @@ export class BundleService { ruleId: string, bundle: IBundle ): Observable<{ errorMessages: string[]; total: number; errors: number }> { - return this.coreWebService - .request({ - body: `assetIdentifier=${ruleId}&bundleName=${bundle.name}&bundleSelect=${bundle.id}`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - method: 'POST', - url: this._addToBundleUrl - }) - .pipe( - map( - (res: HttpResponse) => - <{ errorMessages: string[]; total: number; errors: number }>(res) - ) - ); + const headers = new HttpHeaders({ + 'Content-Type': 'application/x-www-form-urlencoded' + }); + const body = `assetIdentifier=${ruleId}&bundleName=${bundle.name}&bundleSelect=${bundle.id}`; + + return this.http.post<{ errorMessages: string[]; total: number; errors: number }>( + this._addToBundleUrl, + body, + { headers } + ); } pushPublishRule( ruleId: string, environmentId: string ): Observable<{ errorMessages: string[]; total: number; bundleId: string; errors: number }> { - return this.coreWebService - .request({ - body: this.getPublishRuleData(ruleId, environmentId), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - method: 'POST', - url: this._pushRuleUrl - }) - .pipe( - map( - (res: HttpResponse) => < - { - errorMessages: string[]; - total: number; - bundleId: string; - errors: number; - } - >(res) - ) - ); + const headers = new HttpHeaders({ + 'Content-Type': 'application/x-www-form-urlencoded' + }); + + return this.http.post<{ + errorMessages: string[]; + total: number; + bundleId: string; + errors: number; + }>(this._pushRuleUrl, this.getPublishRuleData(ruleId, environmentId), { headers }); } private getFormattedDate(date: Date): string { diff --git a/core-web/libs/dot-rules/src/lib/services/system/locale/I18n.ts b/core-web/libs/dot-rules/src/lib/services/system/locale/I18n.ts index db608a919b97..db8d87766457 100644 --- a/core-web/libs/dot-rules/src/lib/services/system/locale/I18n.ts +++ b/core-web/libs/dot-rules/src/lib/services/system/locale/I18n.ts @@ -1,14 +1,11 @@ -import { defer as observableDefer, Observer } from 'rxjs'; -import { Observable } from 'rxjs'; +import { defer as observableDefer, Observer, Observable } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { catchError, map } from 'rxjs/operators'; +import { catchError } from 'rxjs/operators'; -import { ApiRoot } from '@dotcms/dotcms-js'; -import { LoggerService } from '@dotcms/dotcms-js'; -import { CoreWebService, HttpCode } from '@dotcms/dotcms-js'; +import { ApiRoot, LoggerService } from '@dotcms/dotcms-js'; import { Verify } from '../../validation/Verify'; @@ -98,7 +95,7 @@ export class TreeNode { @Injectable() export class I18nService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private loggerService = inject(LoggerService); root: TreeNode; @@ -113,16 +110,8 @@ export class I18nService { this.root = new TreeNode(null, 'root'); } - makeRequest(url): Observable> { - return this.coreWebService - .request({ - url: this._baseUrl + '/' + url - }) - .pipe( - map((res: HttpResponse) => { - return res; - }) - ); + makeRequest(url: string): Observable { + return this.http.get(`${this._baseUrl}/${url}`); } get( @@ -145,8 +134,8 @@ export class I18nService { const promise = new Promise((resolve, _reject) => { this.makeRequest(path.join('/')) .pipe( - catchError((err: any, _source: Observable) => { - if (err && err.status === HttpCode.NOT_FOUND) { + catchError((err: HttpErrorResponse, _source: Observable) => { + if (err && err.status === HttpStatusCode.NotFound) { this.loggerService.debug("Missing Resource: '", msgKey, "'"); } else { this.loggerService.debug( @@ -160,7 +149,7 @@ export class I18nService { ); } - return Observable.create((obs) => { + return new Observable((obs) => { obs.next(defaultValue); }); }) @@ -175,7 +164,7 @@ export class I18nService { } return observableDefer(() => { - return Observable.create((obs: Observer) => { + return new Observable((obs: Observer) => { if (cNode._loading == null) { this.loggerService.debug('I18n', 'Failed: ', msgKey, '=', cNode); obs.next('-I18nLoadFailed-'); diff --git a/core-web/libs/dotcms-js/src/lib/core/core-web-service-migration.md b/core-web/libs/dotcms-js/src/lib/core/core-web-service-migration.md new file mode 100644 index 000000000000..22e7842a0f06 --- /dev/null +++ b/core-web/libs/dotcms-js/src/lib/core/core-web-service-migration.md @@ -0,0 +1,610 @@ +# CoreWebService Migration Plan + +## Overview + +The `CoreWebService` is deprecated and should be replaced with Angular's `CoreWebService` directly. This document outlines a step-by-step migration plan for a Cursor agent to systematically migrate all usages across the monorepo. + +## Current State Analysis + +### CoreWebService Business Logic + +The `CoreWebService` contains the following business logic that must be analyzed during migration: + +1. **URL Normalization** (`getFixedUrl`) - **IMPORTANT: Must be handled during migration**: + - If URL starts with `api`, prepends `/` → Result: `/api/...` + - If URL starts with `v[1-9]`, prepends `/api/` → Result: `/api/v1/...` + - Example: `v1/users/current/` → `/api/v1/users/current/` + - Example: `api/content/...` → `/api/content/...` + +2. **Default Headers** (`getDefaultRequestHeaders`): + - `Accept: */*` + - `Content-Type: application/json` + - Special handling for `multipart/form-data` (removes Content-Type to let browser set boundary) + +3. **Error Handling**: + - Redirects to `/public/login` on 401 Unauthorized + - Emits HTTP errors via subjects for subscription (`subscribeToHttpError`) + - Custom error types: `CwError`, `NETWORK_CONNECTION_ERROR`, `SERVER_RESPONSE_ERROR` + +4. **Response Wrapper**: + - `requestView` returns `ResponseView` wrapper that provides: + - Access to `entity`, `contentlets`, `tempFiles` + - Header access via `header()` method + - `i18nMessagesMap` access + - `errorsMessages` getter + - `existError(errorCode)` method + +### Files Using CoreWebService + +There are **189 files** using `CoreWebService` in the monorepo: + +- **~15 services** in `libs/data-access/` +- **~10 services** in `libs/dotcms-js/` +- **~15 services** in `apps/dotcms-ui/` +- **~5 services** in `libs/dot-rules/` +- **~144 test files** (`.spec.ts`) + +### Existing Infrastructure + +The codebase already has: + +1. **`ServerErrorInterceptor`** (`apps/dotcms-ui/src/app/shared/interceptors/server-error.interceptor.ts`): + - Catches all HTTP errors and delegates to `DotHttpErrorManagerService` + +2. **`DotHttpErrorManagerService`** (`libs/data-access/src/lib/dot-http-error-manager/`): + - Handles 401 → redirects to login (if no user) + - Handles 403, 404, 500, 400, 204 with user-friendly messages + - Supports localized error messages + +3. **`CoreWebServiceMock`** (`libs/utils-testing/src/lib/core-web.service.mock.ts`): + - Used in all tests, simplifies mock behavior + +--- + +## URL Normalization Rules + +**CRITICAL**: During migration, you MUST analyze each URL and convert it to the correct format. Do NOT use any utility function - the URL should be explicit and correct from the start. + +### Conversion Rules + +| Current URL Pattern | Correct URL Format | Example | +|---------------------|-------------------|---------| +| `v1/...` | `/api/v1/...` | `v1/users/current/` → `/api/v1/users/current/` | +| `v2/...` | `/api/v2/...` | `v2/pages/render` → `/api/v2/pages/render` | +| `api/...` | `/api/...` | `api/content/query` → `/api/content/query` | +| `/api/v1/...` | `/api/v1/...` | Already correct, no change needed | + +### Examples of URL Corrections + +```typescript +// BEFORE (incorrect - missing prefix) +url: 'v1/users/current/' + +// AFTER (correct - explicit full path) +'/api/v1/users/current/' +``` + +```typescript +// BEFORE (incorrect - missing leading slash) +url: 'api/content/respectFrontendRoles/false/render/false/query/...' + +// AFTER (correct - with leading slash) +'/api/content/respectFrontendRoles/false/render/false/query/...' +``` + +```typescript +// BEFORE (already has /api/ prefix) +url: '/api/v1/toolgroups/gettingstarted/_addtouser' + +// AFTER (no change needed) +'/api/v1/toolgroups/gettingstarted/_addtouser' +``` + +--- + +## Migration Strategy + +### Phase 1: Service Migration Patterns + +For each service using `CoreWebService`, apply the appropriate migration pattern: + +#### Pattern A: Simple GET Request + +**Before:** +```typescript +@Injectable() +export class DotCurrentUserService { + private coreWebService = inject(CoreWebService); + private currentUsersUrl = 'v1/users/current/'; + + getCurrentUser(): Observable { + return this.coreWebService + .request({ + url: this.currentUsersUrl + }) + .pipe(map((res: DotCurrentUser) => res)); + } +} +``` + +**After:** +```typescript +import { HttpClient } from '@angular/common/http'; + +@Injectable() +export class DotCurrentUserService { + private http = inject(HttpClient); + private currentUsersUrl = '/api/v1/users/current/'; // ✅ Corrected URL + + getCurrentUser(): Observable { + return this.http.get(this.currentUsersUrl); + } +} +``` + +#### Pattern B: Request with Body (POST/PUT/PATCH/DELETE) + +**Before:** +```typescript +loginUser(params: DotLoginParams): Observable { + return this.coreWebService + .requestView({ + body: { userId: login, password, ... }, + method: 'POST', + url: 'v1/authentication' + }) + .pipe(pluck('entity')); +} +``` + +**After:** +```typescript +import { HttpClient } from '@angular/common/http'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; + +loginUser(params: DotLoginParams): Observable { + return this.http + .post>('/api/v1/authentication', { // ✅ Corrected URL + userId: login, + password, + ... + }) + .pipe(map((res) => res.entity)); +} +``` + +#### Pattern C: Request Needing Headers (Pagination) + +**Before:** +```typescript +get(url?: string): Observable { + return this.coreWebService + .requestView({ + params, + url: cleanURL || this.url + }) + .pipe( + map((response: ResponseView) => { + this.paginationPerPage = parseInt( + response.header(PaginatorService.PAGINATION_PER_PAGE_HEADER_NAME), + 10 + ); + return response.entity; + }) + ); +} +``` + +**After:** +```typescript +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { DotCMSResponse } from '@dotcms/dotcms-models'; + +get(url?: string): Observable { + // Note: Ensure url is already in correct format when passed + const requestUrl = cleanURL || this.url; + + return this.http + .get>(requestUrl, { + params, + observe: 'response' + }) + .pipe( + map((response: HttpResponse>) => { + this.paginationPerPage = parseInt( + response.headers.get(PaginatorService.PAGINATION_PER_PAGE_HEADER_NAME), + 10 + ); + return response.body.entity; + }) + ); +} +``` + +#### Pattern D: subscribeToHttpError (Only in LoginService) + +**Before:** +```typescript +this.coreWebService.subscribeToHttpError(HttpCode.UNAUTHORIZED).subscribe(() => { + this.logOutUser(); +}); +``` + +**After:** + +This pattern is only used in `LoginService`. Since `DotHttpErrorManagerService` already handles 401 errors and redirects to login, this subscription can be removed. The `ServerErrorInterceptor` already catches all HTTP errors. + +### Phase 2: Use Existing DotCMSResponse Interface + +Use the `DotCMSResponse` interface that already exists in `@dotcms/dotcms-models`: + +**Location:** `libs/dotcms-models/src/lib/dot-request-response.model.ts` + +```typescript +import { DotCMSResponse } from '@dotcms/dotcms-models'; +``` + +This interface is already defined as: + +```typescript +export interface DotCMSResponse { + entity: T; + errors: string[]; + i18nMessagesMap: Record; + messages: string[]; + pagination: unknown; + permissions: string[]; +} +``` + +**Note:** Some endpoints return `contentlets` or `tempFiles` instead of `entity`. For these cases, create a specific response type: + +```typescript +// For endpoints that return contentlets (like content search) +interface DotContentSearchResponse { + contentlets: T; +} + +// For temp file endpoints +interface DotTempFileResponse { + tempFiles: T; +} +``` + +--- + +## Migration Order (Priority) + +### Step 1: High Priority - Core Services +Migrate these first as they are foundational: + +1. `libs/dotcms-js/src/lib/core/login.service.ts` +2. `libs/dotcms-js/src/lib/core/site.service.ts` +3. `libs/dotcms-js/src/lib/core/dotcms-config.service.ts` + +### Step 2: Data Access Layer +Migrate all services in `libs/data-access/`: + +1. `dot-current-user.service.ts` +2. `dot-devices.service.ts` +3. `dot-personas.service.ts` +4. `dot-themes.service.ts` +5. `dot-page-render.service.ts` +6. `dot-page-layout.service.ts` +7. `dot-edit-page.service.ts` +8. `dot-contentlet-locker.service.ts` +9. `dot-crud.service.ts` +10. `add-to-bundle.service.ts` +11. `push-publish.service.ts` +12. `paginator.service.ts` (needs header access) +13. `dot-temp-file-upload.service.ts` +14. `dot-push-publish-filters.service.ts` +15. `dot-personalize.service.ts` + +### Step 3: dotcms-ui Services +Migrate services in `apps/dotcms-ui/src/app/api/services/`: + +1. `dot-account-service.ts` +2. `notifications-service.ts` +3. `dot-templates/dot-templates.service.ts` +4. `dot-containers/dot-containers.service.ts` +5. `dot-apps/dot-apps.service.ts` +6. `add-to-menu/add-to-menu.service.ts` + +### Step 4: Other Services +1. `apps/dotcms-ui/.../dot-contentlet-editor.service.ts` +2. `apps/dotcms-ui/.../dot-page-selector.service.ts` +3. `apps/dotcms-ui/.../field.service.ts` +4. `apps/dotcms-ui/.../dot-field-variables.service.ts` +5. `apps/dotcms-ui/.../dot-relationship.service.ts` +6. `apps/dotcms-ui/.../dot-container-contentlet.service.ts` + +### Step 5: Rules Module +1. `libs/dot-rules/src/lib/services/Rule.ts` +2. `libs/dot-rules/src/lib/components/restdropdown/RestDropdown.ts` + +### Step 6: dotcdn +1. `apps/dotcdn/src/app/dotcdn.service.ts` + +--- + +## Test Migration + +### Current Test Pattern (DEPRECATED) +Tests currently use `CoreWebServiceMock` and the deprecated `HttpClientTestingModule`: + +```typescript +// ❌ OLD - Deprecated pattern +TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], // ❌ Deprecated + providers: [ + { provide: CoreWebService, useClass: CoreWebServiceMock }, + MyService + ] +}); +``` + +### New Test Pattern with provideHttpClient + +Use `provideHttpClient()` and `provideHttpClientTesting()` instead: + +> ⚠️ **IMPORTANT**: `provideHttpClient()` must come **before** `provideHttpClientTesting()`, as `provideHttpClientTesting()` will overwrite parts of `provideHttpClient()`. + +```typescript +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +describe('MyService', () => { + let service: MyService; + let httpTesting: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + MyService + ] + }); + + service = TestBed.inject(MyService); + httpTesting = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTesting.verify(); + }); + + it('should get data', () => { + service.getData().subscribe((data) => { + expect(data).toEqual(mockData); + }); + + const req = httpTesting.expectOne('/api/v1/endpoint'); // ✅ Use correct URL + expect(req.request.method).toBe('GET'); + req.flush({ entity: mockData }); + }); +}); +``` + +### New Test Pattern with Spectator (Preferred) + +For services, use Spectator's `createHttpFactory` which simplifies HTTP testing: + +```typescript +import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator'; +import { MyService } from './my.service'; + +describe('MyService', () => { + let spectator: SpectatorHttp; + const createHttp = createHttpFactory(MyService); + + beforeEach(() => spectator = createHttp()); + + it('should get data', () => { + spectator.service.getData().subscribe(); + spectator.expectOne('/api/v1/endpoint', HttpMethod.GET); + }); + + it('should post data', () => { + spectator.service.postData({ name: 'test' }).subscribe(); + + const req = spectator.expectOne('/api/v1/endpoint', HttpMethod.POST); + expect(req.request.body).toEqual({ name: 'test' }); + }); + + it('should handle concurrent requests', () => { + spectator.service.loadMultiple().subscribe(); + + const reqs = spectator.expectConcurrent([ + { url: '/api/v1/resource1', method: HttpMethod.GET }, + { url: '/api/v1/resource2', method: HttpMethod.GET } + ]); + + spectator.flushAll(reqs, [{ entity: data1 }, { entity: data2 }]); + }); +}); +``` + +### Migration Checklist for Tests + +For each `.spec.ts` file: + +- [ ] Remove `HttpClientTestingModule` import +- [ ] Remove `CoreWebService` and `CoreWebServiceMock` from providers +- [ ] Choose testing approach: + - **Option A**: Use `provideHttpClient()` + `provideHttpClientTesting()` + - **Option B**: Use Spectator's `createHttpFactory` (preferred for service tests) +- [ ] Update URL expectations to use corrected URLs (e.g., `/api/v1/...`) + +--- + +## Checklist for Each Service Migration + +For each service, the Cursor agent should: + +- [ ] 1. Read the current service file +- [ ] 2. Identify all `coreWebService.request()` and `coreWebService.requestView()` calls +- [ ] 3. **Analyze each URL and correct it**: + - If URL starts with `v1/`, `v2/`, etc. → add `/api/` prefix + - If URL starts with `api/` → add `/` prefix + - If URL already starts with `/api/` → no change needed +- [ ] 4. Replace `CoreWebService` injection with `HttpClient` +- [ ] 5. Convert each method: + - Replace `request()` with appropriate `http.get/post/put/delete()` + - Replace `requestView().pipe(pluck('entity'))` with `http.method().pipe(map(res => res.entity))` + - Add `observe: 'response'` if headers are needed +- [ ] 6. Remove `CoreWebService` import +- [ ] 7. Update the corresponding `.spec.ts` file: + - Remove `HttpClientTestingModule` (deprecated) + - Remove `CoreWebService` and `CoreWebServiceMock` from providers + - Use `provideHttpClient()` + `provideHttpClientTesting()` OR Spectator's `createHttpFactory` + - Update URL expectations to use the corrected URL format (e.g., `/api/v1/...`) +- [ ] 8. Run `yarn nx run :lint` to check for errors +- [ ] 9. Run `yarn nx run :test` to verify tests pass + +--- + +## Deprecation Timeline + +1. **Phase 1** (Current): Mark as `@deprecated` +2. **Phase 2**: Migrate all services in `libs/data-access/` +3. **Phase 3**: Migrate remaining services +4. **Phase 4**: Remove `CoreWebService`, `ResponseView`, and `CoreWebServiceMock` + +--- + +## Commands for Agent + +### Find all usages +```bash +grep -r "CoreWebService" --include="*.ts" libs apps | grep -v ".spec.ts" | grep -v "node_modules" +``` + +### Run tests for affected projects +```bash +yarn nx affected -t test +``` + +### Lint affected projects +```bash +yarn nx affected -t lint +``` + +--- + +## Notes + +1. **Do NOT remove `CoreWebService` until all migrations are complete** +2. The `ServerErrorInterceptor` already handles global error management +3. `DotHttpErrorManagerService` handles 401 redirects, so `subscribeToHttpError` pattern is largely unnecessary +4. **Use `DotCMSResponse` from `@dotcms/dotcms-models`** - Do NOT use the one from `CoreWebService` +5. For endpoints returning `contentlets` or `tempFiles`, create specific response interfaces +6. Some services may need the full response (with headers) - use `observe: 'response'` in those cases +7. Watch for services that access `i18nMessagesMap` or `errorsMessages` from `ResponseView` +8. **URLs must be explicit and correct** - no utility functions for normalization + +--- + +## Example Full Migration + +### Before: `dot-devices.service.ts` + +```typescript +import { Observable } from 'rxjs'; +import { Injectable, inject } from '@angular/core'; +import { pluck } from 'rxjs/operators'; +import { CoreWebService } from '@dotcms/dotcms-js'; +import { DotDevice } from '@dotcms/dotcms-models'; + +@Injectable() +export class DotDevicesService { + private coreWebService = inject(CoreWebService); + + get(): Observable { + return this.coreWebService + .requestView({ + url: [ + 'api', // ❌ Missing leading slash + 'content', + 'respectFrontendRoles/false', + 'render/false', + 'query/+contentType:previewDevice +live:true +deleted:false +working:true', + 'limit/40/orderby/title' + ].join('/') + }) + .pipe(pluck('contentlets')); + } +} +``` + +### After: `dot-devices.service.ts` + +```typescript +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Injectable, inject } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { DotDevice } from '@dotcms/dotcms-models'; + +// Response type for content search endpoints that return contentlets +interface DotContentSearchResponse { + contentlets: T; +} + +@Injectable() +export class DotDevicesService { + private http = inject(HttpClient); + + get(): Observable { + const url = [ + '/api', // ✅ Corrected with leading slash + 'content', + 'respectFrontendRoles/false', + 'render/false', + 'query/+contentType:previewDevice +live:true +deleted:false +working:true', + 'limit/40/orderby/title' + ].join('/'); + + return this.http + .get>(url) + .pipe(map((response) => response.contentlets)); + } +} +``` + +### Before: `dot-current-user.service.ts` + +```typescript +@Injectable() +export class DotCurrentUserService { + private coreWebService = inject(CoreWebService); + private currentUsersUrl = 'v1/users/current/'; // ❌ Missing /api/ prefix + + getCurrentUser(): Observable { + return this.coreWebService + .request({ + url: this.currentUsersUrl + }) + .pipe(map((res: DotCurrentUser) => res)); + } +} +``` + +### After: `dot-current-user.service.ts` + +```typescript +import { HttpClient } from '@angular/common/http'; + +@Injectable() +export class DotCurrentUserService { + private http = inject(HttpClient); + private currentUsersUrl = '/api/v1/users/current/'; // ✅ Corrected URL + + getCurrentUser(): Observable { + return this.http.get(this.currentUsersUrl); + } +} +``` + diff --git a/core-web/libs/dotcms-js/src/lib/core/core-web.service.mock.ts b/core-web/libs/dotcms-js/src/lib/core/core-web.service.mock.ts deleted file mode 100644 index eaf70fe2866f..000000000000 --- a/core-web/libs/dotcms-js/src/lib/core/core-web.service.mock.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Observable, of } from 'rxjs'; - -import { - HttpClient, - HttpRequest, - HttpEvent, - HttpEventType, - HttpResponse, - HttpParams, - HttpHeaders -} from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; - -import { map, filter } from 'rxjs/operators'; - -import { DotCMSResponse, DotRequestOptionsArgs } from './core-web.service'; -import { ResponseView } from './util/response-view'; - -@Injectable() -export class CoreWebServiceMock { - private _http = inject(HttpClient); - - request(options: DotRequestOptionsArgs): Observable { - if (!options.method) { - options.method = 'GET'; - } - - const optionsArgs = { - params: new HttpParams() - }; - - if (options.params) { - Object.keys(options.params).forEach((key) => { - optionsArgs.params = optionsArgs.params.set(key, options.params[key]); - }); - } - - return this._http - .request( - new HttpRequest(options.method, options.url, options.body, { - params: optionsArgs.params - }) - ) - .pipe( - filter( - (event: HttpEvent> | any>) => - event.type === HttpEventType.Response - ), - map((resp: HttpResponse>) => { - try { - return resp.body; - } catch (error) { - return resp; - } - }) - ); - } - - requestView(options: DotRequestOptionsArgs): Observable> { - if (!options.method) { - options.method = 'GET'; - } - - const optionsArgs = { - headers: new HttpHeaders(), - params: new HttpParams() - }; - - if (options.params) { - Object.keys(options.params).forEach((key: string) => { - optionsArgs.params = optionsArgs.params.set(key, options.params[key]); - }); - } - - return this._http - .request( - new HttpRequest(options.method, options.url, options.body, { - params: optionsArgs.params - }) - ) - .pipe( - filter( - (event: HttpEvent> | any>) => - event.type === HttpEventType.Response - ), - map((resp: HttpResponse>) => { - return new ResponseView(resp); - }) - ); - } - - subscribeTo(httpErrorCode: number): Observable { - return of({ - error: httpErrorCode - }); - } -} diff --git a/core-web/libs/dotcms-js/src/lib/core/core-web.service.ts b/core-web/libs/dotcms-js/src/lib/core/core-web.service.ts deleted file mode 100644 index f50425f7842e..000000000000 --- a/core-web/libs/dotcms-js/src/lib/core/core-web.service.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { Observable, Subject, throwError } from 'rxjs'; - -import { - HttpClient, - HttpErrorResponse, - HttpEvent, - HttpEventType, - HttpHeaders, - HttpParams, - HttpRequest, - HttpResponse -} from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; -import { Router } from '@angular/router'; - -import { catchError, filter, map } from 'rxjs/operators'; - -import { LoggerService } from './logger.service'; -import { HttpCode } from './util/http-code'; -import { - CLIENTS_ONLY_MESSAGES, - CwError, - NETWORK_CONNECTION_ERROR, - SERVER_RESPONSE_ERROR, - UNKNOWN_RESPONSE_ERROR -} from './util/http-response-util'; -import { ResponseView } from './util/response-view'; - -export interface DotCMSResponse { - contentlets?: T; - entity?: T; - tempFiles?: T; // /api/v1/temp don't have entity - errors: string[]; - i18nMessagesMap: { [key: string]: string }; - messages: string[]; - permissions: string[]; -} - -export interface DotRequestOptionsArgs { - url: string; - body?: - | { - [key: string]: any; - } - | string; - method?: string; - params?: { - [key: string]: any; - }; - headers?: { - [key: string]: any; - }; -} - -/** - * Request data from dotCMS endpoints - * @export - * @class CoreWebService - * @deprecated Use Angular HttpClient instead - */ -@Injectable() -export class CoreWebService { - private loggerService = inject(LoggerService); - private router = inject(Router); - private http = inject(HttpClient); - - private httpErrosSubjects: Subject[] = []; - - /** - * - * Request data from dotCMS endpoints - * - * @template T - * @param {DotRequestOptionsArgs} options - * @return {*} {Observable>} - * @memberof CoreWebService - * @deprecated - */ - request(options: DotRequestOptionsArgs): Observable | T> { - if (!options.method) { - options.method = 'GET'; - } - - const request = this.getRequestOpts(options); - const source = options.body; - - return this.http.request(request).pipe( - filter( - (event: HttpEvent> | any>) => - event.type === HttpEventType.Response - ), - map((resp: HttpResponse>) => { - // some endpoints have empty body. - try { - return resp.body; - } catch (error) { - return resp; - } - }), - catchError( - (response: HttpErrorResponse, _original: Observable): Observable => { - if (response) { - this.emitHttpError(response.status); - - if ( - response.status === HttpCode.SERVER_ERROR || - response.status === HttpCode.FORBIDDEN - ) { - if ( - response.statusText && - response.statusText.indexOf('ECONNREFUSED') >= 0 - ) { - throw new CwError( - NETWORK_CONNECTION_ERROR, - CLIENTS_ONLY_MESSAGES[NETWORK_CONNECTION_ERROR], - request, - response, - source - ); - } else { - throw new CwError( - SERVER_RESPONSE_ERROR, - response.error.message, - request, - response, - source - ); - } - } else if (response.status === HttpCode.NOT_FOUND) { - this.loggerService.error( - 'Could not execute request: 404 path not valid.', - options.url - ); - throw new CwError( - UNKNOWN_RESPONSE_ERROR, - response.headers.get('error-message'), - request, - response, - source - ); - } - } - - return null; - } - ) - ); - } - - /** - * Return a response adapted to the follow json format: - * - * - * { - * "errors":[], - * "entity":{}, - * "messages":[], - * "i18nMessagesMap":{} - * } - * - * - * @RequestOptionsArgs options - * @returns Observable - */ - requestView(options: DotRequestOptionsArgs): Observable> { - if (!options.method) { - options.method = 'GET'; - } - - let request; - - if (options.body) { - if (typeof options.body === 'string') { - request = this.getRequestOpts(options); - } else { - request = this.getRequestOpts<{ [key: string]: any }>(options); - } - } else { - request = this.getRequestOpts(options); - } - - return this.http.request(request).pipe( - filter( - (event: HttpEvent> | any>) => - event.type === HttpEventType.Response - ), - map((resp: HttpResponse>) => { - if (resp.body && resp.body.errors && resp.body.errors.length > 0) { - return this.handleRequestViewErrors(resp); - } else { - return new ResponseView(resp); - } - }), - catchError((err: HttpErrorResponse) => { - this.emitHttpError(err.status); - - return throwError(this.handleResponseHttpErrors(err)); - }) - ); - } - - /** - * Emit to the subscriber when the request fail - * - * @param {number} httpErrorCode - * @returns {Observable} - * @memberof CoreWebService - */ - subscribeToHttpError(httpErrorCode: number): Observable { - if (!this.httpErrosSubjects[httpErrorCode]) { - this.httpErrosSubjects[httpErrorCode] = new Subject(); - } - - return this.httpErrosSubjects[httpErrorCode].asObservable(); - } - - private handleRequestViewErrors(resp: HttpResponse>): ResponseView { - if (resp.status === 401) { - this.router.navigate(['/public/login']); - } - - return new ResponseView(resp); - } - - private handleResponseHttpErrors(resp: HttpErrorResponse): HttpErrorResponse { - if (resp.status === 401) { - this.router.navigate(['/public/login']); - } - - return resp; - } - - private emitHttpError(status: number): void { - if (this.httpErrosSubjects[status]) { - this.httpErrosSubjects[status].next(); - } - } - - private getRequestOpts(options: DotRequestOptionsArgs): HttpRequest { - const headers = this.getHttpHeaders(options.headers); - const params = this.getHttpParams(options.params); - const url = this.getFixedUrl(options.url); - const body = options.body || null; - - if ( - options.method === 'POST' || - options.method === 'PUT' || - options.method === 'PATCH' || - options.method === 'DELETE' - ) { - return new HttpRequest(options.method, url, body, { - headers, - params - }); - } - - const method = <'GET' | 'HEAD' | 'JSONP' | 'OPTIONS'>options.method; - - // @ts-ignore - return new HttpRequest(method, url, { - headers, - params - }); - } - - private getHttpHeaders(headers: { [key: string]: string }): HttpHeaders { - let httpHeaders = this.getDefaultRequestHeaders(); - - if (headers && Object.keys(headers).length) { - Object.keys(headers).forEach((key) => { - httpHeaders = httpHeaders.set(key, headers[key]); - }); - - // If Content-Type == 'multipart/form-data' we need to remove Content-Type, - // otherwise "boundary" will not be added to Content-Type in the Header - if (headers['Content-Type'] === 'multipart/form-data') { - httpHeaders = httpHeaders.delete('Content-Type'); - } - } - - return httpHeaders; - } - - private getFixedUrl(url: string): string { - if (url?.startsWith('api')) { - return `/${url}`; - } - - const version = url ? url.split('/')[0] : ''; - if (version.match(/v[1-9]/g)) { - return `/api/${url}`; - } - - return url; - } - - private getHttpParams(params: { [key: string]: any }): HttpParams { - if (params && Object.keys(params).length) { - let httpParams = new HttpParams(); - - Object.keys(params).forEach((key: string) => { - httpParams = httpParams.set(key, params[key]); - }); - - return httpParams; - } - - return null; - } - - private getDefaultRequestHeaders(): HttpHeaders { - const headers = new HttpHeaders() - .set('Accept', '*/*') - .set('Content-Type', 'application/json'); - - return headers; - } -} diff --git a/core-web/libs/dotcms-js/src/lib/core/dotcms-config.service.ts b/core-web/libs/dotcms-js/src/lib/core/dotcms-config.service.ts index c148dda4e23b..454cebbda78d 100644 --- a/core-web/libs/dotcms-js/src/lib/core/dotcms-config.service.ts +++ b/core-web/libs/dotcms-js/src/lib/core/dotcms-config.service.ts @@ -1,13 +1,18 @@ import { BehaviorSubject, Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { filter, map, pluck, take } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; -import { CoreWebService } from './core-web.service'; import { LoggerService } from './logger.service'; import { Menu } from './routing.service'; +// Local interface to avoid circular dependency with dotcms-models +interface DotCMSEntityResponse { + entity: T; +} + /** * Created by josecastro on 7/29/16. * @@ -70,6 +75,36 @@ export interface DotTimeZone { offset: string; } +interface DotAppConfigResponse { + config: { + colors: DotUiColors; + [EMAIL_REGEX]: string; + license: { + displayServerId: string; + isCommunity: boolean; + level: number; + levelName: string; + }; + logos: { + loginScreen: string; + navBar: string; + }; + [DOTCMS_PAGINATOR_LINKS]: number; + [DOTCMS_PAGINATOR_ROWS]: number; + releaseInfo?: { + buildDate: string; + version: string; + }; + websocket: { + [DOTCMS_WEBSOCKET_RECONNECT_TIME]: number; + [DOTCMS_DISABLE_WEBSOCKET_PROTOCOL]: boolean; + }; + systemTimezone: SystemTimezone; + timezones: DotTimeZone[]; + }; + menu: Menu[]; +} + /** * @deprecated Use DotSystemConfigService from @dotcms/data-access instead. * This service uses the deprecated CoreWebService and will be removed in a future version. @@ -87,7 +122,7 @@ export interface DotTimeZone { providedIn: 'root' }) export class DotcmsConfigService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private loggerService = inject(LoggerService); private configParamsSubject: BehaviorSubject = new BehaviorSubject(null); @@ -99,7 +134,7 @@ export class DotcmsConfigService { * @param configParams - The configuration properties for the current instance. */ constructor() { - this.configUrl = 'v1/appconfiguration'; + this.configUrl = '/api/v1/appconfiguration'; this.loadConfig(); } @@ -112,12 +147,10 @@ export class DotcmsConfigService { loadConfig(): void { this.loggerService.debug('Loading configuration on: ', this.configUrl); - this.coreWebService - .requestView({ - url: this.configUrl - }) - .pipe(pluck('entity')) - .subscribe((res: any) => { + this.http + .get>(this.configUrl) + .pipe(map((response) => response.entity)) + .subscribe((res: DotAppConfigResponse) => { this.loggerService.debug('Configuration Loaded!', res); const configParams: ConfigParams = { @@ -154,27 +187,23 @@ export class DotcmsConfigService { * @memberof DotcmsConfigService */ getTimeZones(): Observable { - return this.coreWebService - .requestView({ - url: this.configUrl + return this.http.get>(this.configUrl).pipe( + map((response) => response.entity.config.timezones), + map((timezones: DotTimeZone[]) => { + return timezones.sort((a: DotTimeZone, b: DotTimeZone) => { + if (a.label > b.label) { + return 1; + } + + if (a.label < b.label) { + return -1; + } + + // a must be equal to b + return 0; + }); }) - .pipe( - pluck('entity', 'config', 'timezones'), - map((timezones: DotTimeZone[]) => { - return timezones.sort((a: DotTimeZone, b: DotTimeZone) => { - if (a.label > b.label) { - return 1; - } - - if (a.label < b.label) { - return -1; - } - - // a must be equal to b - return 0; - }); - }) - ); + ); } /** @@ -183,10 +212,9 @@ export class DotcmsConfigService { * @memberof DotcmsConfigService */ getSystemTimeZone(): Observable { - return this.coreWebService - .requestView({ - url: this.configUrl - }) - .pipe(pluck('entity', 'config', 'systemTimezone'), take(1)); + return this.http.get>(this.configUrl).pipe( + map((response) => response.entity.config.systemTimezone), + take(1) + ); } } diff --git a/core-web/libs/dotcms-js/src/lib/core/login.service.ts b/core-web/libs/dotcms-js/src/lib/core/login.service.ts index cdc3888f8ae9..fe99a924978e 100644 --- a/core-web/libs/dotcms-js/src/lib/core/login.service.ts +++ b/core-web/libs/dotcms-js/src/lib/core/login.service.ts @@ -1,15 +1,17 @@ import { Observable, of, Subject } from 'rxjs'; -import { HttpResponse } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { map, pluck, tap } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; -import { DotLoginInformation, SESSION_STORAGE_VARIATION_KEY } from '@dotcms/dotcms-models'; +import { + DotCMSResponse, + DotLoginInformation, + SESSION_STORAGE_VARIATION_KEY +} from '@dotcms/dotcms-models'; -import { CoreWebService } from './core-web.service'; import { DotcmsEventsService } from './dotcms-events.service'; -import { HttpCode } from './util/http-code'; export interface DotLoginParams { login: string; @@ -29,7 +31,7 @@ export const LOGOUT_URL = '/dotAdmin/logout'; providedIn: 'root' }) export class LoginService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private dotcmsEventsService = inject(DotcmsEventsService); currentUserLanguageId = ''; @@ -43,14 +45,14 @@ export class LoginService { this._loginAsUsersList$ = new Subject(); this.urls = { - changePassword: 'v1/changePassword', - getAuth: 'v1/authentication/logInUser', - loginAs: 'v1/users/loginas', - logout: 'v1/logout', - logoutAs: 'v1/users/logoutas', - recoverPassword: 'v1/forgotpassword', - serverInfo: 'v1/loginform', - userAuth: 'v1/authentication', + changePassword: '/api/v1/changePassword', + getAuth: '/api/v1/authentication/logInUser', + loginAs: '/api/v1/users/loginas', + logout: '/api/v1/logout', + logoutAs: '/api/v1/users/logoutas', + recoverPassword: '/api/v1/forgotpassword', + serverInfo: '/api/v1/loginform', + userAuth: '/api/v1/authentication', current: '/api/v1/users/current/' }; @@ -71,9 +73,9 @@ export class LoginService { return this._auth$.asObservable(); } - private _logout$: Subject = new Subject(); + private _logout$: Subject = new Subject(); - get logout$(): Observable { + get logout$(): Observable { return this._logout$.asObservable(); } @@ -104,13 +106,7 @@ export class LoginService { * @memberof LoginService */ getCurrentUser(): Observable { - return this.coreWebService - .request({ - url: this.urls.current - }) - .pipe( - map((res: HttpResponse) => res) - ) as unknown as Observable; + return this.http.get(this.urls.current); } /** @@ -120,19 +116,15 @@ export class LoginService { * @memberof LoginService */ loadAuth(): Observable { - return this.coreWebService - .requestView({ - url: this.urls.getAuth - }) - .pipe( - pluck('entity'), - tap((auth: Auth) => { - if (auth.user) { - this.setAuth(auth); - } - }), - map((auth: Auth) => this.getFullAuth(auth)) - ); + return this.http.get>(this.urls.getAuth).pipe( + map((response) => response.entity), + tap((auth: Auth) => { + if (auth.user) { + this.setAuth(auth); + } + }), + map((auth: Auth) => this.getFullAuth(auth)) + ); } /** @@ -146,13 +138,9 @@ export class LoginService { changePassword(password: string, token: string): Observable { const body = JSON.stringify({ password: password, token: token }); - return this.coreWebService - .requestView({ - body: body, - method: 'POST', - url: this.urls.changePassword - }) - .pipe(pluck('entity')); + return this.http + .post>(this.urls.changePassword, body) + .pipe(map((response) => response.entity)); } /** @@ -166,17 +154,13 @@ export class LoginService { getLoginFormInfo(language: string, i18nKeys: Array): Observable { this.setLanguage(language); - return this.coreWebService - .requestView({ - body: { - messagesKey: i18nKeys, - language: this.lang, - country: this.country - }, - method: 'POST', - url: this.urls.serverInfo + return this.http + .post<{ bodyJsonObject: DotLoginInformation }>(this.urls.serverInfo, { + messagesKey: i18nKeys, + language: this.lang, + country: this.country }) - .pipe(pluck('bodyJsonObject')); + .pipe(map((response) => response.bodyJsonObject)); } /** @@ -187,19 +171,15 @@ export class LoginService { * @memberof LoginService */ loginAs(userData: { user: User; password: string }): Observable { - return this.coreWebService - .requestView<{ loginAs: boolean }>({ - body: { - password: userData.password, - userId: userData.user.userId - }, - method: 'POST', - url: this.urls.loginAs + return this.http + .post>(this.urls.loginAs, { + password: userData.password, + userId: userData.user.userId }) .pipe( map((res) => { if (!res.entity.loginAs) { - throw res.errorsMessages; + throw res.errors; } this.setAuth({ @@ -207,9 +187,8 @@ export class LoginService { user: this._auth.user }); - return res; - }), - pluck('entity', 'loginAs') + return res.entity.loginAs; + }) ); } @@ -235,18 +214,14 @@ export class LoginService { }: DotLoginParams): Observable { this.setLanguage(language); - return this.coreWebService - .requestView({ - body: { - userId: login, - password: password, - rememberMe: rememberMe, - language: this.lang, - country: this.country, - backEndLogin: backEndLogin - }, - method: 'POST', - url: this.urls.userAuth + return this.http + .post>(this.urls.userAuth, { + userId: login, + password: password, + rememberMe: rememberMe, + language: this.lang, + country: this.country, + backEndLogin: backEndLogin }) .pipe( map((response) => { @@ -256,11 +231,6 @@ export class LoginService { }; this.setAuth(auth); - this.coreWebService - .subscribeToHttpError(HttpCode.UNAUTHORIZED) - .subscribe(() => { - this.logOutUser(); - }); return response.entity; }) @@ -274,21 +244,16 @@ export class LoginService { * @memberof LoginService */ logoutAs(): Observable { - return this.coreWebService - .requestView<{ logoutAs: boolean }>({ - method: 'PUT', - url: `${this.urls.logoutAs}` + return this.http.put>(this.urls.logoutAs, {}).pipe( + map((res) => { + this.setAuth({ + loginAsUser: null, + user: this._auth.user + }); + + return res.entity.logoutAs; }) - .pipe( - map((res) => { - this.setAuth({ - loginAsUser: null, - user: this._auth.user - }); - - return res.entity.logoutAs; - }) - ); + ); } /** @@ -299,13 +264,9 @@ export class LoginService { * @memberof LoginService */ recoverPassword(login: string): Observable { - return this.coreWebService - .requestView({ - body: { userId: login }, - method: 'POST', - url: this.urls.recoverPassword - }) - .pipe(pluck('entity')); + return this.http + .post>(this.urls.recoverPassword, { userId: login }) + .pipe(map((response) => response.entity)); } /** diff --git a/core-web/libs/dotcms-js/src/lib/core/routing.service.ts b/core-web/libs/dotcms-js/src/lib/core/routing.service.ts index eb224704e55a..bfc4fadd90ad 100644 --- a/core-web/libs/dotcms-js/src/lib/core/routing.service.ts +++ b/core-web/libs/dotcms-js/src/lib/core/routing.service.ts @@ -1,9 +1,12 @@ -import { Observable } from 'rxjs'; -import { Subject } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { CoreWebService } from './core-web.service'; +import { map } from 'rxjs/operators'; + +import { DotCMSResponse } from '@dotcms/dotcms-models'; + import { DotRouterService } from './dot-router.service'; import { DotcmsEventsService } from './dotcms-events.service'; import { LoginService } from './login.service'; @@ -11,11 +14,11 @@ import { LoginService } from './login.service'; @Injectable() export class RoutingService { private router = inject(DotRouterService); - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private _menusChange$: Subject = new Subject(); private menus: Menu[]; - private urlMenus: string; + private urlMenus = '/api/v1/CORE_WEB/menu'; private portlets: Map; private _currentPortletId: string; @@ -27,7 +30,6 @@ export class RoutingService { const loginService = inject(LoginService); const dotcmsEventsService = inject(DotcmsEventsService); - this.urlMenus = 'v1/CORE_WEB/menu'; this.portlets = new Map(); loginService.watchUser(this.loadMenus.bind(this)); @@ -130,16 +132,13 @@ export class RoutingService { } private loadMenus(): void { - this.coreWebService - .requestView({ - url: this.urlMenus - }) - .subscribe( - (response) => { - this.setMenus(response.entity); - }, - (error) => this._menusChange$.error(error) - ); + this.http + .get>(this.urlMenus) + .pipe(map((response) => response.entity)) + .subscribe({ + next: (menus) => this.setMenus(menus), + error: (error) => this._menusChange$.error(error) + }); } private getPortletId(url: string): string { diff --git a/core-web/libs/dotcms-js/src/lib/core/site.service.ts b/core-web/libs/dotcms-js/src/lib/core/site.service.ts index 45a92dd9efbe..a584c358c528 100644 --- a/core-web/libs/dotcms-js/src/lib/core/site.service.ts +++ b/core-web/libs/dotcms-js/src/lib/core/site.service.ts @@ -1,15 +1,22 @@ import { Observable, Subject, merge, of } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; -import { filter, map, pluck, skip, startWith, switchMap, take, tap } from 'rxjs/operators'; +import { filter, map, skip, startWith, switchMap, take, tap } from 'rxjs/operators'; + +import { DotCMSResponse } from '@dotcms/dotcms-models'; -import { CoreWebService } from './core-web.service'; import { DotcmsEventsService } from './dotcms-events.service'; import { LoggerService } from './logger.service'; import { LoginService } from './login.service'; import { DotEventTypeWrapper } from './models/dot-events/dot-event-type-wrapper'; +// Response type for content search endpoints that return contentlets +interface DotContentSearchResponse { + contentlets: T; +} + /** * @deprecated * This service is deprecated do not use it in new code. @@ -22,7 +29,7 @@ import { DotEventTypeWrapper } from './models/dot-events/dot-event-type-wrapper' providedIn: 'root' }) export class SiteService { - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private loggerService = inject(LoggerService); private selectedSite: Site; @@ -44,9 +51,9 @@ export class SiteService { const dotcmsEventsService = inject(DotcmsEventsService); this.urls = { - currentSiteUrl: 'v1/site/currentSite', - sitesUrl: 'v1/site', - switchSiteUrl: 'v1/site/switch' + currentSiteUrl: '/api/v1/site/currentSite', + sitesUrl: '/api/v1/site', + switchSiteUrl: '/api/v1/site/switch' }; dotcmsEventsService @@ -153,12 +160,9 @@ export class SiteService { * @memberof SiteService */ switchToDefaultSite(): Observable { - return this.coreWebService - .requestView({ - method: 'PUT', - url: 'v1/site/switch' - }) - .pipe(pluck('entity')); + return this.http + .put>(this.urls.switchSiteUrl, {}) + .pipe(map((response) => response.entity)); } /** @@ -169,14 +173,11 @@ export class SiteService { * @memberof SiteService */ getSiteById(id: string): Observable { - return this.coreWebService - .requestView({ - url: `/api/content/render/false/query/+contentType:host%20+identifier:${id}` - }) - .pipe( - pluck('contentlets'), - map((sites: Site[]) => sites[0]) - ); + return this.http + .get< + DotContentSearchResponse + >(`/api/content/render/false/query/+contentType:host%20+identifier:${id}`) + .pipe(map((response) => response.contentlets[0])); } /** @@ -209,11 +210,8 @@ export class SiteService { switchSite(site: Site): Observable { this.loggerService.debug('Applying a Site Switch', site.identifier); - return this.coreWebService - .requestView({ - method: 'PUT', - url: `${this.urls.switchSiteUrl}/${site.identifier}` - }) + return this.http + .put>(`${this.urls.switchSiteUrl}/${site.identifier}`, {}) .pipe( take(1), tap(() => this.setCurrentSite(site)), @@ -234,11 +232,9 @@ export class SiteService { } private requestCurrentSite(): Observable { - return this.coreWebService - .requestView({ - url: this.urls.currentSiteUrl - }) - .pipe(pluck('entity')); + return this.http + .get>(this.urls.currentSiteUrl) + .pipe(map((response) => response.entity)); } private setCurrentSite(site: Site): void { diff --git a/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.spec.ts b/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.spec.ts index aab213b77f3d..d1c906a77851 100644 --- a/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.spec.ts +++ b/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.spec.ts @@ -1,63 +1,79 @@ import { Server } from 'mock-socket'; -import { Observable, of, throwError } from 'rxjs'; +import { noop, of } from 'rxjs'; -import { ReflectiveInjector, Injectable } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; import { DotEventsSocket } from './dot-event-socket'; import { DotEventMessage } from './models/dot-event-message'; import { DotEventsSocketURL } from './models/dot-event-socket-url'; -import { CoreWebService } from '../core-web.service'; import { ConfigParams, DotcmsConfigService } from '../dotcms-config.service'; import { LoggerService } from '../logger.service'; -import { StringUtils } from '../string-utils.service'; -@Injectable() -class CoreWebServiceMock extends CoreWebService { - constructor() { - super(null, null, null, null, null); - } - - public requestView(): Observable { - return null; - } -} - -@Injectable() class DotcmsConfigMock { - getConfig(): Observable { + getConfig() { return of({ - colors: {}, + colors: { + primary: '#000', + secondary: '#fff', + background: '#fff' + }, emailRegex: '', - license: {}, + license: { + displayServerId: '', + isCommunity: false, + level: 0, + levelName: '' + }, + logos: { + loginScreen: '', + navBar: '' + }, menu: [], paginatorLinks: 1, paginatorRows: 2, + releaseInfo: { + buildDate: '', + version: '' + }, websocket: { websocketReconnectTime: 0, disabledWebsockets: false + }, + systemTimezone: { + id: '', + label: '', + offset: '' } - }); + } as ConfigParams); } } describe('DotEventsSocket', () => { - const coreWebServiceMock = new CoreWebServiceMock(); - const dotcmsConfig: DotcmsConfigMock = new DotcmsConfigMock(); let dotEventsSocket: DotEventsSocket; + let httpTesting: HttpTestingController; const url = new DotEventsSocketURL('localhost/testing', false); beforeEach(() => { - const injector = ReflectiveInjector.resolveAndCreate([ - { provide: CoreWebService, useValue: coreWebServiceMock }, - { provide: DotcmsConfigService, useValue: dotcmsConfig }, - { provide: DotEventsSocketURL, useValue: url }, - StringUtils, - LoggerService, - DotEventsSocket - ]); - - dotEventsSocket = injector.get(DotEventsSocket); + TestBed.configureTestingModule({ + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + { provide: DotcmsConfigService, useClass: DotcmsConfigMock }, + { provide: DotEventsSocketURL, useValue: url }, + LoggerService, + DotEventsSocket + ] + }); + + dotEventsSocket = TestBed.inject(DotEventsSocket); + httpTesting = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTesting.verify(); }); describe('WebSocket', () => { @@ -72,7 +88,7 @@ describe('DotEventsSocket', () => { done(); }); - dotEventsSocket.connect().subscribe(() => {}); + dotEventsSocket.connect().subscribe(noop); }); it('should catch a message', (done) => { @@ -89,7 +105,7 @@ describe('DotEventsSocket', () => { dotEventsSocket.messages().subscribe((message) => { expect(message).toEqual(expectedMessage); }); - dotEventsSocket.connect().subscribe(() => {}); + dotEventsSocket.connect().subscribe(noop); }); afterEach(() => { @@ -98,41 +114,23 @@ describe('DotEventsSocket', () => { }); describe('LongPolling', () => { - const requestOpts = { - url: 'http://localhost/testing', - params: {} - }; + const longPollingUrl = 'http://localhost/testing'; it('should connect', (done) => { - spyOn(coreWebServiceMock, 'requestView').and.callFake(() => { - dotEventsSocket.destroy(); - - return of({ - entity: { - message: 'message' - } - }); - }); - - dotEventsSocket.connect().subscribe(() => {}); + dotEventsSocket.connect().subscribe(noop); dotEventsSocket.open().subscribe(() => { - expect(coreWebServiceMock.requestView).toHaveBeenCalledWith(requestOpts); + dotEventsSocket.destroy(); done(); }); + + const req = httpTesting.expectOne(longPollingUrl); + expect(req.request.method).toBe('GET'); + req.flush({ entity: { message: 'message' } }); }); it('should catch a message', (done) => { - spyOn(coreWebServiceMock, 'requestView').and.callFake(() => { - return of({ - entity: { - event: 'event', - payload: 'message' - } - }); - }); - - dotEventsSocket.connect().subscribe(() => {}); + dotEventsSocket.connect().subscribe(noop); dotEventsSocket.messages().subscribe((message) => { dotEventsSocket.destroy(); @@ -142,46 +140,43 @@ describe('DotEventsSocket', () => { }); done(); }); - }); - it('should catch a message', (done) => { - let firstTime = true; - let countMessage = 0; - - spyOn(coreWebServiceMock, 'requestView').and.callFake(() => { - if (firstTime) { - firstTime = false; - - return throwError('ERROR'); - } else { - return of({ - entity: { - event: 'event', - payload: 'message' - } - }); + const req = httpTesting.expectOne(longPollingUrl); + expect(req.request.method).toBe('GET'); + req.flush({ + entity: { + event: 'event', + payload: 'message' } }); + }); - dotEventsSocket.connect().subscribe(() => {}); + it('should reconnect after error', (done) => { + dotEventsSocket.connect().subscribe(noop); dotEventsSocket.messages().subscribe((message) => { dotEventsSocket.destroy(); - - countMessage++; expect(message).toEqual({ event: 'event', payload: 'message' }); + done(); + }); + + // First request fails + const firstReq = httpTesting.expectOne(longPollingUrl); + firstReq.error(new ProgressEvent('error')); - setTimeout(() => { - if (countMessage === 1) { - done(); - } else if (countMessage === 2) { - expect(true).toBe(false, 'should be call just one'); + // After reconnect delay, the second request should succeed + setTimeout(() => { + const secondReq = httpTesting.expectOne(longPollingUrl); + secondReq.flush({ + entity: { + event: 'event', + payload: 'message' } - }, 10); - }); + }); + }, 1500); }); }); }); diff --git a/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.ts b/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.ts index 578b81eeb124..7bd04d7dbe7a 100644 --- a/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.ts +++ b/core-web/libs/dotcms-js/src/lib/core/util/dot-event-socket.ts @@ -1,5 +1,6 @@ import { Subject, Observable, timer } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { tap } from 'rxjs/operators'; @@ -10,7 +11,6 @@ import { DotEventsSocketURL } from './models/dot-event-socket-url'; import { Protocol } from './protocol'; import { WebSocketProtocol } from './websockets-protocol'; -import { CoreWebService } from '../core-web.service'; import { ConfigParams, DotcmsConfigService, WebSocketConfigParams } from '../dotcms-config.service'; import { LoggerService } from '../logger.service'; @@ -36,7 +36,7 @@ export class DotEventsSocket { private dotEventsSocketURL = inject(DotEventsSocketURL); private dotcmsConfigService = inject(DotcmsConfigService); private loggerService = inject(LoggerService); - private coreWebService = inject(CoreWebService); + private http = inject(HttpClient); private protocolImpl: Protocol; @@ -226,7 +226,7 @@ export class DotEventsSocket { return new LongPollingProtocol( this.dotEventsSocketURL.getLongPoolingURL(), this.loggerService, - this.coreWebService + this.http ); } diff --git a/core-web/libs/dotcms-js/src/lib/core/util/long-polling-protocol.ts b/core-web/libs/dotcms-js/src/lib/core/util/long-polling-protocol.ts index 459aa8ebfbb1..9393ea474825 100644 --- a/core-web/libs/dotcms-js/src/lib/core/util/long-polling-protocol.ts +++ b/core-web/libs/dotcms-js/src/lib/core/util/long-polling-protocol.ts @@ -1,10 +1,15 @@ -import { pluck, take } from 'rxjs/operators'; +import { HttpClient, HttpParams } from '@angular/common/http'; + +import { map, take } from 'rxjs/operators'; import { Protocol } from './protocol'; -import { CoreWebService } from '../core-web.service'; import { LoggerService } from '../logger.service'; +interface LongPollingResponse { + entity: T; +} + export class LongPollingProtocol extends Protocol { private isClosed = false; private isAlreadyOpen = false; @@ -13,7 +18,7 @@ export class LongPollingProtocol extends Protocol { constructor( private url: string, loggerService: LoggerService, - private coreWebService: CoreWebService + private http: HttpClient ) { super(loggerService); } @@ -46,12 +51,17 @@ export class LongPollingProtocol extends Protocol { this.isClosed = false; this.loggerService.info('Starting long polling connection'); - this.coreWebService - .requestView({ - url: this.url, - params: lastCallBack ? { lastCallBack: lastCallBack } : {} - }) - .pipe(pluck('entity'), take(1)) + let params = new HttpParams(); + if (lastCallBack) { + params = params.set('lastCallBack', lastCallBack.toString()); + } + + this.http + .get>(this.url, { params }) + .pipe( + map((response) => response.entity), + take(1) + ) .subscribe( (data) => { this.loggerService.debug('new Events', data); diff --git a/core-web/libs/dotcms-js/src/lib/core/util/response-view.ts b/core-web/libs/dotcms-js/src/lib/core/util/response-view.ts deleted file mode 100644 index 08e14812cbb8..000000000000 --- a/core-web/libs/dotcms-js/src/lib/core/util/response-view.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { HttpResponse, HttpHeaders } from '@angular/common/http'; - -import { DotCMSResponse } from '../core-web.service'; - -/** - * - * - * - * { - * "errors":[], - * "entity":{}, - * "messages":[], - * "i18nMessagesMap":{} - * } - * - */ -export class ResponseView { - private bodyJsonObject: DotCMSResponse; - private headers: HttpHeaders; - - public constructor(private resp: HttpResponse>) { - try { - this.bodyJsonObject = resp.body; - this.headers = resp.headers; - } catch (e) { - this.bodyJsonObject = null; - } - } - - public header(headerName: string): string { - return this.headers.get(headerName); - } - - get i18nMessagesMap(): { [key: string]: string } { - return this.bodyJsonObject.i18nMessagesMap; - } - - get contentlets(): T { - return this.bodyJsonObject.contentlets; - } - - get entity(): T { - return this.bodyJsonObject.entity; - } - - get tempFiles(): T { - return this.bodyJsonObject.tempFiles; - } - - get errorsMessages(): string { - let errorMessages = ''; - - if (this.bodyJsonObject.errors) { - this.bodyJsonObject.errors.forEach((e: any) => { - errorMessages += e.message; - }); - } else { - errorMessages = this.bodyJsonObject.messages.toString(); - } - - return errorMessages; - } - - get status(): number { - return this.resp.status; - } - - get response(): HttpResponse> { - return this.resp; - } - - public existError(errorCode: string): boolean { - return ( - this.bodyJsonObject.errors && - this.bodyJsonObject.errors.filter((e: any) => e.errorCode === errorCode).length > 0 - ); - } -} diff --git a/core-web/libs/dotcms-js/src/public_api.ts b/core-web/libs/dotcms-js/src/public_api.ts index 246a6f6d3560..abd6ece09351 100644 --- a/core-web/libs/dotcms-js/src/public_api.ts +++ b/core-web/libs/dotcms-js/src/public_api.ts @@ -1,7 +1,6 @@ // SERVICES export * from './lib/core/api-root.service'; export * from './lib/core/browser-util.service'; -export * from './lib/core/core-web.service'; export * from './lib/core/dot-push-publish-dialog.service'; export * from './lib/core/dot-router.service'; export * from './lib/core/dotcms-config.service'; @@ -20,7 +19,6 @@ export * from './lib/core/util/local-store.service'; export * from './lib/core/util/long-polling-protocol'; export * from './lib/core/util/notification.service'; export * from './lib/core/util/protocol'; -export * from './lib/core/util/response-view'; export * from './lib/core/util/websockets-protocol'; // MODELS @@ -28,4 +26,3 @@ export * from './lib/core/models'; export * from './lib/core/util/models/dot-event-socket-url'; export * from './lib/core/shared/user.model'; export * from './lib/core/site.service.mock'; -export * from './lib/core/core-web.service.mock'; diff --git a/core-web/libs/dotcms-models/src/lib/dot-request-response.model.ts b/core-web/libs/dotcms-models/src/lib/dot-request-response.model.ts index 9b4b4abd6f3c..a4ffe0a1e05c 100644 --- a/core-web/libs/dotcms-models/src/lib/dot-request-response.model.ts +++ b/core-web/libs/dotcms-models/src/lib/dot-request-response.model.ts @@ -10,3 +10,11 @@ export interface DotCMSResponse { pagination: unknown; permissions: string[]; } + +/** + * Generic response structure for dotCMS API endpoints that return bodyJsonObject + * @template T - The type of the bodyJsonObject data + */ +export interface DotCMSResponseJsonObject { + bodyJsonObject: T; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts index 2d1163f95342..69a46bf151ce 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts @@ -5,8 +5,7 @@ import { inject, Injectable } from '@angular/core'; import { pluck } from 'rxjs/operators'; -import { DotCMSResponse } from '@dotcms/dotcms-js'; -import { DotCategory } from '@dotcms/dotcms-models'; +import { DotCategory, DotCMSResponse } from '@dotcms/dotcms-models'; import { HierarchyParent } from '../models/dot-category-field.models'; diff --git a/core-web/libs/ui/src/lib/dot-site-selector/dot-site-selector.directive.spec.ts b/core-web/libs/ui/src/lib/dot-site-selector/dot-site-selector.directive.spec.ts index 7494bc07d8a2..53ca056cbd78 100644 --- a/core-web/libs/ui/src/lib/dot-site-selector/dot-site-selector.directive.spec.ts +++ b/core-web/libs/ui/src/lib/dot-site-selector/dot-site-selector.directive.spec.ts @@ -1,15 +1,13 @@ import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; import { fakeAsync } from '@angular/core/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Dropdown, DropdownFilterEvent, DropdownModule } from 'primeng/dropdown'; import { DotEventsService, DotSiteService } from '@dotcms/data-access'; -import { CoreWebService, mockSites, SiteService } from '@dotcms/dotcms-js'; -import { CoreWebServiceMock, SiteServiceMock } from '@dotcms/utils-testing'; +import { mockSites } from '@dotcms/utils-testing'; import { DotSiteSelectorDirective } from './dot-site-selector.directive'; @@ -21,13 +19,10 @@ describe('DotSiteSelectorDirective', () => { const createDirective = createDirectiveFactory({ directive: DotSiteSelectorDirective, - imports: [HttpClientTestingModule, DropdownModule, BrowserAnimationsModule], - providers: [ - { provide: SiteService, useClass: SiteServiceMock }, - { provide: CoreWebService, useClass: CoreWebServiceMock }, - DotEventsService, - DotSiteService - ] + imports: [DropdownModule, BrowserAnimationsModule], + mocks: [DotSiteService], + providers: [DotEventsService], + detectChanges: false }); beforeEach(() => { @@ -37,6 +32,8 @@ describe('DotSiteSelectorDirective', () => { dotSiteService = spectator.inject(DotSiteService); dotEventsService = spectator.inject(DotEventsService); dropdown = spectator.directive['control'] as Dropdown; + + dotSiteService.getSites = jest.fn().mockReturnValue(of(mockSites)); }); it('should create', () => { @@ -45,21 +42,15 @@ describe('DotSiteSelectorDirective', () => { }); describe('Get Sites', () => { - let getSitesSpy; - - beforeEach(() => { - getSitesSpy = jest.spyOn(dotSiteService, 'getSites').mockReturnValue(of(mockSites)); - }); - - it('should get sites list', () => { + it('should get sites list on init', () => { spectator.detectChanges(); - spectator.directive.ngOnInit(); - - expect(getSitesSpy).toHaveBeenCalled(); + expect(dotSiteService.getSites).toHaveBeenCalledWith('', 10); }); it('should get sites list with filter', fakeAsync(() => { + spectator.detectChanges(); + const event: DropdownFilterEvent = { filter: 'demo', originalEvent: new MouseEvent('click') @@ -72,16 +63,24 @@ describe('DotSiteSelectorDirective', () => { }); describe('Listen login-as/logout-as events', () => { - it('should send notification when login-as/logout-as', fakeAsync(() => { - const getSitesSpy = jest - .spyOn(dotSiteService, 'getSites') - .mockReturnValue(of(mockSites)); + it('should refresh sites on login-as event', fakeAsync(() => { spectator.detectChanges(); + jest.clearAllMocks(); + dotEventsService.notify('login-as'); spectator.tick(0); + + expect(dotSiteService.getSites).toHaveBeenCalledTimes(1); + })); + + it('should refresh sites on logout-as event', fakeAsync(() => { + spectator.detectChanges(); + jest.clearAllMocks(); + dotEventsService.notify('logout-as'); spectator.tick(0); - expect(getSitesSpy).toHaveBeenCalledTimes(2); + + expect(dotSiteService.getSites).toHaveBeenCalledTimes(1); })); }); }); diff --git a/core-web/libs/ui/src/lib/resolvers/dot-enterprise-license-resolver.service.spec.ts b/core-web/libs/ui/src/lib/resolvers/dot-enterprise-license-resolver.service.spec.ts index 7aa8b3f4bce5..b74ecadb5f99 100644 --- a/core-web/libs/ui/src/lib/resolvers/dot-enterprise-license-resolver.service.spec.ts +++ b/core-web/libs/ui/src/lib/resolvers/dot-enterprise-license-resolver.service.spec.ts @@ -1,10 +1,10 @@ import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { DotLicenseService } from '@dotcms/data-access'; -import { CoreWebService, CoreWebServiceMock } from '@dotcms/dotcms-js'; import { DotEnterpriseLicenseResolver } from './dot-enterprise-license-resolver.service'; @@ -14,11 +14,11 @@ describe('DotEnterpriseLicenseResolver', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], providers: [ + provideHttpClient(), + provideHttpClientTesting(), DotEnterpriseLicenseResolver, - DotLicenseService, - { provide: CoreWebService, useClass: CoreWebServiceMock } + DotLicenseService ] }); service = TestBed.inject(DotEnterpriseLicenseResolver); diff --git a/core-web/libs/utils-testing/src/index.ts b/core-web/libs/utils-testing/src/index.ts index 0ce901097da7..c0cca0108bbf 100644 --- a/core-web/libs/utils-testing/src/index.ts +++ b/core-web/libs/utils-testing/src/index.ts @@ -1,5 +1,4 @@ export * from './lib/clean-up-dialog'; -export * from './lib/core-web.service.mock'; export * from './lib/dot-auth-user.mock'; export * from './lib/dot-containers.mock'; export * from './lib/dot-containers.service.mock'; diff --git a/core-web/libs/utils-testing/src/lib/core-web.service.mock.ts b/core-web/libs/utils-testing/src/lib/core-web.service.mock.ts deleted file mode 100644 index 7b1868c9a726..000000000000 --- a/core-web/libs/utils-testing/src/lib/core-web.service.mock.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Observable, of } from 'rxjs'; - -import { - HttpClient, - HttpRequest, - HttpEvent, - HttpEventType, - HttpResponse, - HttpParams, - HttpHeaders -} from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; - -import { map, filter } from 'rxjs/operators'; - -import { ResponseView, DotCMSResponse, DotRequestOptionsArgs } from '@dotcms/dotcms-js'; - -@Injectable() -export class CoreWebServiceMock { - private _http = inject(HttpClient); - - request( - options: DotRequestOptionsArgs - ): Observable> | DotCMSResponse> { - if (!options.method) { - options.method = 'GET'; - } - - const optionsArgs = { - params: new HttpParams() - }; - - if (options.params) { - Object.keys(options.params).forEach((key) => { - optionsArgs.params = optionsArgs.params.set(key, options.params[key]); - }); - } - - return this._http - .request( - new HttpRequest(options.method, options.url, options.body, { - params: optionsArgs.params - }) - ) - .pipe( - filter( - (event: HttpEvent> | unknown>) => - event.type === HttpEventType.Response - ), - map((resp: HttpResponse>) => { - try { - return resp.body; - } catch (error) { - return resp; - } - }) - ); - } - - requestView(options: DotRequestOptionsArgs): Observable> { - if (!options.method) { - options.method = 'GET'; - } - - // Ensure url is defined to avoid HttpRequest constructor errors - if (!options.url) { - options.url = ''; - } - - const optionsArgs = { - headers: new HttpHeaders(), - params: new HttpParams() - }; - - if (options.params) { - Object.keys(options.params).forEach((key: string) => { - optionsArgs.params = optionsArgs.params.set(key, options.params[key]); - }); - } - - return this._http - .request( - new HttpRequest(options.method, options.url, options.body, { - params: optionsArgs.params - }) - ) - .pipe( - filter( - (event: HttpEvent> | unknown>) => - event.type === HttpEventType.Response - ), - map((resp: HttpResponse>) => { - return new ResponseView(resp); - }) - ); - } - - subscribeTo(httpErrorCode: number): Observable<{ error: number }> { - return of({ - error: httpErrorCode - }); - } -}