Skip to content

Commit

Permalink
cache
Browse files Browse the repository at this point in the history
improve code in factories and in SlidingExpiration
add unit tests
  • Loading branch information
JohnDoePlusPlus committed Nov 15, 2019
1 parent 7c0c11d commit 20437a4
Show file tree
Hide file tree
Showing 15 changed files with 691 additions and 26 deletions.
12 changes: 2 additions & 10 deletions lib/cache/cacheProvider/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,14 @@ export class CacheProviderFactory implements Factory<CacheProvider> {
public create() {
switch (this.scope) {
case 'class':
return this.classCacheProvider();
return new ClassCacheProvider(this.cacheFactory);

case 'instance':
return this.instanceCacheProvider();
return new InstanceCacheProvider(this.cacheFactory);

default:
throw new Error(`@cache invalid scope option: ${this.scope}.`);
}
}

private classCacheProvider(): ClassCacheProvider {
return new ClassCacheProvider(this.cacheFactory);
}

private instanceCacheProvider(): InstanceCacheProvider {
return new InstanceCacheProvider(this.cacheFactory);
}

}
6 changes: 5 additions & 1 deletion lib/cache/expirations/SlidingExpiration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export class SlidingExpiration implements Expiration {
) { }

public add(key: string, clearCallback: (key: string) => unknown): void {
this.expirations.has(key) ? this.update(key, clearCallback) : this.addKey(key, clearCallback);
if (this.expirations.has(key)) {
this.update(key, clearCallback);
} else {
this.addKey(key, clearCallback);
}
}

private addKey(key: string, clearCallback: (key: string) => unknown): void {
Expand Down
12 changes: 2 additions & 10 deletions lib/cache/expirations/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,14 @@ export class ExpirationFactory implements Factory<Expiration> {
public create(): Expiration {
switch (this.expiration) {
case 'absolute':
return this.absoluteExpirtation();
return new AbsoluteExpiration(this.timeout);

case 'sliding':
return this.slidingExpiration();
return new SlidingExpiration(this.timeout);

default:
throw new Error(`@cache Expiration type is not supported: ${this.expiration}.`);
}
}

private absoluteExpirtation(): AbsoluteExpiration {
return new AbsoluteExpiration(this.timeout);
}

private slidingExpiration(): SlidingExpiration {
return new SlidingExpiration(this.timeout);
}

}
6 changes: 1 addition & 5 deletions lib/cache/storages/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@ export class StorageFactory implements Factory<Storage> {
public create(): Storage {
switch (this.storage) {
case 'memory':
return this.memoryStorage();
return new MemoryStorage(this.limit);

default:
throw new Error(`@cache Storage type is not supported: ${this.storage}.`);
}
}

private memoryStorage(): MemoryStorage {
return new MemoryStorage(this.limit);
}

}
46 changes: 46 additions & 0 deletions test/cache/cacheProvider/ClassCacheProvider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect } from 'chai';
import * as sinon from 'sinon';

import { ClassCacheProvider } from '../../../lib/cache/cacheProvider/ClassCacheProvider';
import { CacheFactory } from '../../../lib/cache/caches/factory';

describe('@cache ClassCacheProvider', () => {

let cacheFactoryStub: sinon.SinonStubbedInstance<CacheFactory>;
let service: ClassCacheProvider;

beforeEach(() => {
cacheFactoryStub = sinon.createStubInstance(CacheFactory);

service = new ClassCacheProvider(cacheFactoryStub as any);
});

describe('constructor', () => {

it('should create', () => expect(service).to.be.instanceOf(ClassCacheProvider));

it('should init cache property to null', () => expect(service['cache']).to.be.null);

});

describe('get', () => {

it('should call CacheFactory.create to create an instance at first call', () => {
const cacheInstance = {} as any;
cacheFactoryStub.create.returns(cacheInstance);

expect(service.get()).to.be.equals(cacheInstance);
expect(cacheFactoryStub.create.calledOnce).to.be.true;
expect(service['cache']).to.be.equals(cacheInstance);
});

it('should return existent instance of cache if is not first call', () => {
const response = service['cache'] = {} as any;

expect(cacheFactoryStub.create.called).to.be.false;
expect(service.get()).to.be.equals(response);
});

});

});
51 changes: 51 additions & 0 deletions test/cache/cacheProvider/InstanceCacheProvider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { expect } from 'chai';
import * as sinon from 'sinon';

import { InstanceCacheProvider } from '../../../lib/cache/cacheProvider/InstanceCacheProvider';
import { CacheFactory } from '../../../lib/cache/caches/factory';

describe('@cache InstanceCacheProvider', () => {

let cacheFactoryStub: sinon.SinonStubbedInstance<CacheFactory>;
let service: InstanceCacheProvider;

beforeEach(() => {
cacheFactoryStub = sinon.createStubInstance(CacheFactory);

service = new InstanceCacheProvider(cacheFactoryStub as any);
});

describe('constructor', () => {

it('should create', () => expect(service).to.be.instanceOf(InstanceCacheProvider));

it('should init instancesCaches property', () => {
expect(service['instanceCaches']).to.be.instanceOf(WeakMap);
});

});

describe('get', () => {

it('should create new cache instance if was called first time with this instance', () => {
const result = {} as any;
const instance = {} as any;
cacheFactoryStub.create.returns(result);

expect(service.get(instance)).to.be.equals(result);
expect(service['instanceCaches'].get(instance)).to.be.equals(result);
expect(cacheFactoryStub.create.calledOnce).to.be.true;
});

it('should return already created cache for current isntance', () => {
const result = {} as any;
const instance = {} as any;
service['instanceCaches'].set(instance, result);

expect(service.get(instance)).to.be.equals(result);
expect(cacheFactoryStub.create.called).to.be.false;
});

});

});
40 changes: 40 additions & 0 deletions test/cache/cacheProvider/factory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect } from 'chai';

import { ClassCacheProvider } from '../../../lib/cache/cacheProvider/ClassCacheProvider';
import { CacheProviderFactory } from '../../../lib/cache/cacheProvider/factory';
import { InstanceCacheProvider } from '../../../lib/cache/cacheProvider/InstanceCacheProvider';

describe('@cache CacheProviderFactory', () => {

describe('constructor', () => {

it('should create', () => {
expect(new CacheProviderFactory('class', undefined)).to.be.instanceOf(CacheProviderFactory);
});

});

describe('create', () => {

it('should create instanceof ClassCacheProvider if scope is "class"', () => {
const instance = new CacheProviderFactory('class', undefined);
expect(instance.create()).to.be.instanceOf(ClassCacheProvider);
});

it('should create instanceof InstanceCacheProvider if scope is "instance"', () => {
const instance = new CacheProviderFactory('instance', undefined);
expect(instance.create()).to.be.instanceOf(InstanceCacheProvider);
});

it('should throw error if scope options is not a valid one', () => {
const scope = '123' as any;
const message = `@cache invalid scope option: ${scope}.`;

const instance = new CacheProviderFactory(scope, undefined);

expect(() => instance.create()).to.throw(message);
});

});

});
148 changes: 148 additions & 0 deletions test/cache/caches/Cache.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { expect } from 'chai';
import * as sinon from 'sinon';

import { Cache } from '../../../lib/cache/caches/Cache';
import { AbsoluteExpiration } from '../../../lib/cache/expirations/AbsoluteExpiration';
import { Expiration } from '../../../lib/cache/expirations/Expiration';
import { MemoryStorage } from '../../../lib/cache/storages/MemoryStorage';
import { Storage } from '../../../lib/cache/storages/Storage';
import { HashService } from '../../../lib/utils/hash';

describe('@cache Cache', () => {

let hashStub: sinon.SinonStubbedInstance<HashService>;
let storageStub: sinon.SinonStubbedInstance<Storage>;
let expirationStub: sinon.SinonStubbedInstance<Expiration>;
let service: Cache;

beforeEach(() => {
hashStub = sinon.createStubInstance(HashService);
storageStub = sinon.createStubInstance(MemoryStorage);
expirationStub = sinon.createStubInstance(AbsoluteExpiration);

service = new Cache(storageStub, expirationStub, hashStub);
});

describe('constructor', () => {

it('should create', () => expect(service).to.be.instanceOf(Cache));

});

describe('set', () => {

it('should call hash.calculate to obtain arguments hash', async () => {
await service.set(['key'], 'value');

expect(hashStub.calculate.calledOnce).to.be.true;
});

it('should call storage.set to store given hashed key and data', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.set(['key'], 'value');

expect(storageStub.set.calledOnce).to.be.true;
expect(storageStub.set.calledWithExactly(key, 'value')).to.be.true;
});

it('should call expiration.add to make cache expirable', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.set(['key'], 'value');

expect(expirationStub.add.calledOnce).to.be.true;
expect(expirationStub.add.calledWith(key, sinon.match.func)).to.be.true;
});

describe('function passed to expiration', () => {

it('should call storage.delete', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.set(['key'], 'value');

const callback = expirationStub.add.firstCall.args[1];

await callback(key);

expect(storageStub.delete.calledOnce).to.be.true;
expect(storageStub.delete.calledWith(key)).to.be.true;
});

});

});

describe('has', () => {

it('should call hash.calculate to obtain arguments hash', async () => {
await service.has(['key']);

expect(hashStub.calculate.calledOnce).to.be.true;
});

it('should call storage has to check if key exists', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.has(['key']);

expect(storageStub.has.calledOnce).to.be.true;
expect(storageStub.has.calledWith(key)).to.be.true;
});

});

describe('get', () => {

it('should call hash.calculate to obtain arguments hash', async () => {
await service.get(['key']);

expect(hashStub.calculate.calledOnce).to.be.true;
});

it('should call expiration.add to update cache expiration', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.get(['key']);

expect(expirationStub.add.calledOnce).to.be.true;
expect(expirationStub.add.calledWith(key, sinon.match.func)).to.be.true;
});

it('should call storage.get to obtain cached value', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.get(['key']);

expect(storageStub.get.calledOnce).to.be.true;
expect(storageStub.get.calledWith(key)).to.be.true;
});

describe('function passed to expiration', () => {

it('should call storage.delete', async () => {
const key = 'key';
hashStub.calculate.returns(key);

await service.get(['key']);

const callback = expirationStub.add.firstCall.args[1];

await callback(key);

expect(storageStub.delete.calledOnce).to.be.true;
expect(storageStub.delete.calledWith(key)).to.be.true;
});

});

});

});
Loading

0 comments on commit 20437a4

Please sign in to comment.