RequestCacheService docs & tests

This commit is contained in:
Art Lowel
2017-03-14 14:36:03 +01:00
parent eaaf12d645
commit 34d9aece7e
4 changed files with 217 additions and 3 deletions

View File

@@ -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();
}); });
}); });

View File

@@ -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)

View 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);
});
});
});

View File

@@ -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;