Skip to content

Commit

Permalink
120109: Fixed BaseDataService not emitting when the request is too fa…
Browse files Browse the repository at this point in the history
…st and the ResponsePending are not emitted

(cherry picked from commit 0f4d71e)
  • Loading branch information
alexandrevryghem committed Dec 5, 2024
1 parent 404ccd9 commit 987ea51
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 15 deletions.
69 changes: 56 additions & 13 deletions src/app/core/data/base/base-data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('BaseDataService', () => {
let selfLink;
let linksToFollow;
let testScheduler;
let remoteDataTimestamp: number;
let remoteDataMocks;

function initTestService(): TestService {
Expand Down Expand Up @@ -86,19 +87,21 @@ describe('BaseDataService', () => {
expect(actual).toEqual(expected);
});

const timeStamp = new Date().getTime();
// The response's lastUpdated equals the time of 60 seconds after the test started, ensuring they are not perceived
// as cached values.
remoteDataTimestamp = new Date().getTime() + 60 * 1000;
const msToLive = 15 * 60 * 1000;
const payload = { foo: 'bar' };
const statusCodeSuccess = 200;
const statusCodeError = 404;
const errorMessage = 'not found';
remoteDataMocks = {
RequestPending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
ResponsePending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
Success: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Success, undefined, payload, statusCodeSuccess),
SuccessStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess),
Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
RequestPending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
ResponsePending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
Success: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Success, undefined, payload, statusCodeSuccess),
SuccessStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess),
Error: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
ErrorStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
};

return new TestService(
Expand Down Expand Up @@ -330,11 +333,15 @@ describe('BaseDataService', () => {
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
});


it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
it('should not emit a cached completed RemoteData', () => {
// Old cached value from 1 minute before the test started
const oldCachedSucceededData: RemoteData<any> = Object.assign({}, remoteDataMocks.Success, {
timeCompleted: remoteDataTimestamp - 2 * 60 * 1000,
lastUpdated: remoteDataTimestamp - 2 * 60 * 1000,
} as RemoteData<any>);
testScheduler.run(({ cold, expectObservable }) => {
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
a: remoteDataMocks.Success,
a: oldCachedSucceededData,
b: remoteDataMocks.RequestPending,
c: remoteDataMocks.ResponsePending,
d: remoteDataMocks.Success,
Expand All @@ -352,6 +359,22 @@ describe('BaseDataService', () => {
});
});

it('should emit the first completed RemoteData since the request was made', () => {
testScheduler.run(({ cold, expectObservable }) => {
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b', {
a: remoteDataMocks.Success,
b: remoteDataMocks.SuccessStale,
}));
const expected = 'a-b';
const values = {
a: remoteDataMocks.Success,
b: remoteDataMocks.SuccessStale,
};

expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values);
});
});

it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
testScheduler.run(({ cold, expectObservable }) => {
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
Expand Down Expand Up @@ -514,11 +537,15 @@ describe('BaseDataService', () => {
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
});


it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
it('should not emit a cached completed RemoteData', () => {
testScheduler.run(({ cold, expectObservable }) => {
// Old cached value from 1 minute before the test started
const oldCachedSucceededData: RemoteData<any> = Object.assign({}, remoteDataMocks.Success, {
timeCompleted: remoteDataTimestamp - 2 * 60 * 1000,
lastUpdated: remoteDataTimestamp - 2 * 60 * 1000,
} as RemoteData<any>);
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
a: remoteDataMocks.Success,
a: oldCachedSucceededData,
b: remoteDataMocks.RequestPending,
c: remoteDataMocks.ResponsePending,
d: remoteDataMocks.Success,
Expand All @@ -536,6 +563,22 @@ describe('BaseDataService', () => {
});
});

it('should emit the first completed RemoteData since the request was made', () => {
testScheduler.run(({ cold, expectObservable }) => {
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b', {
a: remoteDataMocks.Success,
b: remoteDataMocks.SuccessStale,
}));
const expected = 'a-b';
const values = {
a: remoteDataMocks.Success,
b: remoteDataMocks.SuccessStale,
};

expectObservable(service.findListByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
});
});

it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
testScheduler.run(({ cold, expectObservable }) => {
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
Expand Down
6 changes: 4 additions & 2 deletions src/app/core/data/base/base-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,15 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
map((href: string) => this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow)),
);

const startTime: number = new Date().getTime();
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);

return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
// This skip ensures that if a stale object is present in the cache when you do a
// call it isn't immediately returned, but we wait until the remote data for the new request
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
// cached completed object
skipWhile((rd: RemoteData<T>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
skipWhile((rd: RemoteData<T>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
this.reRequestStaleRemoteData(reRequestOnStale, () =>
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)),
);
Expand All @@ -300,14 +301,15 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
map((href: string) => this.buildHrefFromFindOptions(href, options, [], ...linksToFollow)),
);

const startTime: number = new Date().getTime();
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);

return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
// This skip ensures that if a stale object is present in the cache when you do a
// call it isn't immediately returned, but we wait until the remote data for the new request
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
// cached completed object
skipWhile((rd: RemoteData<PaginatedList<T>>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
skipWhile((rd: RemoteData<PaginatedList<T>>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
this.reRequestStaleRemoteData(reRequestOnStale, () =>
this.findListByHref(href$, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)),
);
Expand Down

0 comments on commit 987ea51

Please sign in to comment.