mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 11:33:04 +00:00
RequestCacheService docs & tests
This commit is contained in:
@@ -72,9 +72,10 @@ describe("ObjectCacheService", () => {
|
|||||||
it("should not return a cached object that has exceeded its time to live", () => {
|
it("should not return a cached object that has exceeded its time to live", () => {
|
||||||
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
|
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
|
||||||
|
|
||||||
let testObj: any;
|
let getObsHasFired = false;
|
||||||
service.get(uuid, TestClass).take(1).subscribe(o => testObj = o);
|
const subscription = service.get(uuid, TestClass).subscribe(o => getObsHasFired = true);
|
||||||
expect(testObj).toBeUndefined();
|
expect(getObsHasFired).toBe(false);
|
||||||
|
subscription.unsubscribe();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
1
src/app/core/cache/object-cache.service.ts
vendored
1
src/app/core/cache/object-cache.service.ts
vendored
@@ -51,6 +51,7 @@ export class ObjectCacheService {
|
|||||||
* @param type
|
* @param type
|
||||||
* The type of the object to get
|
* The type of the object to get
|
||||||
* @return Observable<T>
|
* @return Observable<T>
|
||||||
|
* An observable of the requested object
|
||||||
*/
|
*/
|
||||||
get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
|
get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
|
||||||
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||||
|
147
src/app/core/cache/request-cache.service.spec.ts
vendored
Normal file
147
src/app/core/cache/request-cache.service.spec.ts
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { RequestCacheService } from "./request-cache.service";
|
||||||
|
import { Store } from "@ngrx/store";
|
||||||
|
import { RequestCacheState, RequestCacheEntry } from "./request-cache.reducer";
|
||||||
|
import { OpaqueToken } from "@angular/core";
|
||||||
|
import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "./request-cache.actions";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
describe("RequestCacheService", () => {
|
||||||
|
let service: RequestCacheService;
|
||||||
|
let store: Store<RequestCacheState>;
|
||||||
|
|
||||||
|
const keys = ["125c17f89046283c5f0640722aac9feb", "a06c3006a41caec5d635af099b0c780c"];
|
||||||
|
const serviceTokens = [new OpaqueToken('service1'), new OpaqueToken('service2')];
|
||||||
|
const resourceID = "9978";
|
||||||
|
const paginationOptions = { "resultsPerPage": 10, "currentPage": 1 };
|
||||||
|
const sortOptions = { "field": "id", "direction": 0 };
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
const validCacheEntry = (key) => {
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
timeAdded: timestamp,
|
||||||
|
msToLive: 24 * 60 * 60 * 1000 // a day
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const invalidCacheEntry = (key) => {
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
timeAdded: 0,
|
||||||
|
msToLive: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = new Store<RequestCacheState>(undefined, undefined, undefined);
|
||||||
|
spyOn(store, 'dispatch');
|
||||||
|
service = new RequestCacheService(store);
|
||||||
|
spyOn(window, 'Date').and.returnValue({ getTime: () => timestamp });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("findAll", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, "get").and.callFake((key) => Observable.of({key: key}));
|
||||||
|
});
|
||||||
|
describe("if the key isn't cached", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, "has").and.returnValue(false);
|
||||||
|
});
|
||||||
|
it("should dispatch a FIND_ALL action with the key, service, scopeID, paginationOptions and sortOptions", () => {
|
||||||
|
service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new RequestCacheFindAllAction(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions))
|
||||||
|
});
|
||||||
|
it("should return an observable of the newly cached request with the specified key", () => {
|
||||||
|
let result: RequestCacheEntry;
|
||||||
|
service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions).take(1).subscribe(entry => result = entry);
|
||||||
|
expect(result.key).toEqual(keys[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("if the key is already cached", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, "has").and.returnValue(true);
|
||||||
|
});
|
||||||
|
it("shouldn't dispatch anything", () => {
|
||||||
|
service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions);
|
||||||
|
expect(store.dispatch).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it("should return an observable of the existing cached request with the specified key", () => {
|
||||||
|
let result: RequestCacheEntry;
|
||||||
|
service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions).take(1).subscribe(entry => result = entry);
|
||||||
|
expect(result.key).toEqual(keys[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("findById", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, "get").and.callFake((key) => Observable.of({key: key}));
|
||||||
|
});
|
||||||
|
describe("if the key isn't cached", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, "has").and.returnValue(false);
|
||||||
|
});
|
||||||
|
it("should dispatch a FIND_BY_ID action with the key, service, and resourceID", () => {
|
||||||
|
service.findById(keys[0], serviceTokens[0], resourceID);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new RequestCacheFindByIDAction(keys[0], serviceTokens[0], resourceID))
|
||||||
|
});
|
||||||
|
it("should return an observable of the newly cached request with the specified key", () => {
|
||||||
|
let result: RequestCacheEntry;
|
||||||
|
service.findById(keys[0], serviceTokens[0], resourceID).take(1).subscribe(entry => result = entry);
|
||||||
|
expect(result.key).toEqual(keys[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("if the key is already cached", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, "has").and.returnValue(true);
|
||||||
|
});
|
||||||
|
it("shouldn't dispatch anything", () => {
|
||||||
|
service.findById(keys[0], serviceTokens[0], resourceID);
|
||||||
|
expect(store.dispatch).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it("should return an observable of the existing cached request with the specified key", () => {
|
||||||
|
let result: RequestCacheEntry;
|
||||||
|
service.findById(keys[0], serviceTokens[0], resourceID).take(1).subscribe(entry => result = entry);
|
||||||
|
expect(result.key).toEqual(keys[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get", () => {
|
||||||
|
it("should return an observable of the cached request with the specified key", () => {
|
||||||
|
spyOn(store, "select").and.callFake((...args:Array<any>) => {
|
||||||
|
return Observable.of(validCacheEntry(args[args.length - 1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
let testObj: RequestCacheEntry;
|
||||||
|
service.get(keys[1]).take(1).subscribe(entry => testObj = entry);
|
||||||
|
expect(testObj.key).toEqual(keys[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not return a cached request that has exceeded its time to live", () => {
|
||||||
|
spyOn(store, "select").and.callFake((...args:Array<any>) => {
|
||||||
|
return Observable.of(invalidCacheEntry(args[args.length - 1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
let getObsHasFired = false;
|
||||||
|
const subscription = service.get(keys[1]).subscribe(entry => getObsHasFired = true);
|
||||||
|
expect(getObsHasFired).toBe(false);
|
||||||
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("has", () => {
|
||||||
|
it("should return true if the request with the supplied key is cached and still valid", () => {
|
||||||
|
spyOn(store, 'select').and.returnValue(Observable.of(validCacheEntry(keys[1])));
|
||||||
|
expect(service.has(keys[1])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if the request with the supplied key isn't cached", () => {
|
||||||
|
spyOn(store, 'select').and.returnValue(Observable.of(undefined));
|
||||||
|
expect(service.has(keys[1])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if the request with the supplied key is cached but has exceeded its time to live", () => {
|
||||||
|
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry(keys[1])));
|
||||||
|
expect(service.has(keys[1])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
65
src/app/core/cache/request-cache.service.ts
vendored
65
src/app/core/cache/request-cache.service.ts
vendored
@@ -10,12 +10,35 @@ import {
|
|||||||
import { SortOptions } from "../shared/sort-options.model";
|
import { SortOptions } from "../shared/sort-options.model";
|
||||||
import { PaginationOptions } from "../shared/pagination-options.model";
|
import { PaginationOptions } from "../shared/pagination-options.model";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service to interact with the request cache
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestCacheService {
|
export class RequestCacheService {
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<RequestCacheState>
|
private store: Store<RequestCacheState>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new findAll request
|
||||||
|
*
|
||||||
|
* This will send a new findAll request to the backend,
|
||||||
|
* and store the request parameters and the fact that
|
||||||
|
* the request is pending
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the key should be a unique identifier for the request and its parameters
|
||||||
|
* @param service
|
||||||
|
* the service that initiated the request
|
||||||
|
* @param scopeID
|
||||||
|
* the id of an optional scope object
|
||||||
|
* @param paginationOptions
|
||||||
|
* the pagination options (optional)
|
||||||
|
* @param sortOptions
|
||||||
|
* the sort options (optional)
|
||||||
|
* @return Observable<RequestCacheEntry>
|
||||||
|
* an observable of the RequestCacheEntry for this request
|
||||||
|
*/
|
||||||
findAll(
|
findAll(
|
||||||
key: string,
|
key: string,
|
||||||
service: OpaqueToken,
|
service: OpaqueToken,
|
||||||
@@ -29,6 +52,22 @@ export class RequestCacheService {
|
|||||||
return this.get(key);
|
return this.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new findById request
|
||||||
|
*
|
||||||
|
* This will send a new findById request to the backend,
|
||||||
|
* and store the request parameters and the fact that
|
||||||
|
* the request is pending
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the key should be a unique identifier for the request and its parameters
|
||||||
|
* @param service
|
||||||
|
* the service that initiated the request
|
||||||
|
* @param resourceID
|
||||||
|
* the ID of the resource to find
|
||||||
|
* @return Observable<RequestCacheEntry>
|
||||||
|
* an observable of the RequestCacheEntry for this request
|
||||||
|
*/
|
||||||
findById(
|
findById(
|
||||||
key: string,
|
key: string,
|
||||||
service: OpaqueToken,
|
service: OpaqueToken,
|
||||||
@@ -40,12 +79,29 @@ export class RequestCacheService {
|
|||||||
return this.get(key);
|
return this.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an observable of the request with the specified key
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the key of the request to get
|
||||||
|
* @return Observable<RequestCacheEntry>
|
||||||
|
* an observable of the RequestCacheEntry with the specified key
|
||||||
|
*/
|
||||||
get(key: string): Observable<RequestCacheEntry> {
|
get(key: string): Observable<RequestCacheEntry> {
|
||||||
return this.store.select<RequestCacheEntry>('core', 'cache', 'request', key)
|
return this.store.select<RequestCacheEntry>('core', 'cache', 'request', key)
|
||||||
.filter(entry => this.isValid(entry))
|
.filter(entry => this.isValid(entry))
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the request with the specified key is cached
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the key of the request to check
|
||||||
|
* @return boolean
|
||||||
|
* true if the request with the specified key is cached,
|
||||||
|
* false otherwise
|
||||||
|
*/
|
||||||
has(key: string): boolean {
|
has(key: string): boolean {
|
||||||
let result: boolean;
|
let result: boolean;
|
||||||
|
|
||||||
@@ -56,6 +112,15 @@ export class RequestCacheService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a RequestCacheEntry should still be cached
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
* the entry to check
|
||||||
|
* @return boolean
|
||||||
|
* false if the entry is null, undefined, or its time to
|
||||||
|
* live has been exceeded, true otherwise
|
||||||
|
*/
|
||||||
private isValid(entry: RequestCacheEntry): boolean {
|
private isValid(entry: RequestCacheEntry): boolean {
|
||||||
if (hasNoValue(entry)) {
|
if (hasNoValue(entry)) {
|
||||||
return false;
|
return false;
|
||||||
|
Reference in New Issue
Block a user