Skip to content

Commit

Permalink
feat(cart): add DaffCartDriverResolveService (#2490)
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 committed Jun 29, 2023
1 parent 15cb6cd commit f691fd8
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 0 deletions.
1 change: 1 addition & 0 deletions libs/cart/driver/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './interfaces/public_api';
export * from './injection-tokens/public_api';
export * from './errors/public_api';
export * from './services/public_api';
export * from './operators/public_api';
1 change: 1 addition & 0 deletions libs/cart/driver/src/services/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DaffCartDriverResolveService } from './resolve.service';
180 changes: 180 additions & 0 deletions libs/cart/driver/src/services/resolve.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { TestBed } from '@angular/core/testing';
import {
catchError,
of,
throwError,
} from 'rxjs';

import {
DaffCart,
DaffCartStorageService,
} from '@daffodil/cart';
import {
DaffCartDriver,
DaffCartServiceInterface,
} from '@daffodil/cart/driver';
import { DaffTestingCartDriverModule } from '@daffodil/cart/driver/testing';
import { DaffCartFactory } from '@daffodil/cart/testing';
import { DaffDriverResponse } from '@daffodil/driver';

import { DaffCartDriverResolveService } from './resolve.service';

describe('@daffodil/cart/driver | DaffCartDriverResolveService', () => {
let service: DaffCartDriverResolveService;
let cartStorageSpy: jasmine.SpyObj<DaffCartStorageService>;
let cartDriverSpy: jasmine.SpyObj<DaffCartServiceInterface>;
let cartFactory: DaffCartFactory;

let cartId: DaffCart['id'];
let mockCartResponse: DaffDriverResponse<DaffCart>;

beforeEach(() => {
cartStorageSpy = jasmine.createSpyObj('DaffCartStorageService', ['setCartId', 'getCartId']);
cartDriverSpy = jasmine.createSpyObj('DaffCartDriver', ['get', 'create']);

TestBed.configureTestingModule({
imports: [
DaffTestingCartDriverModule,
],
providers: [
{
provide: DaffCartDriver,
useValue: cartDriverSpy,
},
{
provide: DaffCartStorageService,
useValue: cartStorageSpy,
},
],
});

service = TestBed.inject(DaffCartDriverResolveService);
cartFactory = TestBed.inject(DaffCartFactory);

mockCartResponse = {
response: cartFactory.create(),
errors: [],
};
cartId = 'cartId';
});

describe('getCartIdOrFail', () => {
describe('when the cart ID exists in storage', () => {
beforeEach(() => {
cartStorageSpy.getCartId.and.returnValue(cartId);
});

it('should return that cart ID', (done) => {
service.getCartIdOrFail().subscribe((res) => {
expect(res).toEqual(cartId);
done();
});
});
});

describe('when the cart ID does not exist in storage', () => {
beforeEach(() => {
cartStorageSpy.getCartId.and.returnValue(null);
cartDriverSpy.create.and.returnValue(of({ id: cartId }));
});

it('should call create', (done) => {
service.getCartIdOrFail().subscribe((res) => {
expect(cartDriverSpy.create).toHaveBeenCalledWith();
done();
});
});

describe('and when create is successful', () => {
beforeEach(() => {
cartDriverSpy.create.and.returnValue(of({ id: cartId }));
});

it('should return that cart ID', (done) => {
service.getCartIdOrFail().subscribe((res) => {
expect(res).toEqual(cartId);
done();
});
});

it('should set that cart ID in storage', (done) => {
service.getCartIdOrFail().subscribe((res) => {
expect(cartStorageSpy.setCartId).toHaveBeenCalledWith(cartId);
done();
});
});
});

describe('and when create fails', () => {
let error: Error;

beforeEach(() => {
error = new Error();
cartDriverSpy.create.and.returnValue(throwError(() => error));
});

it('should throw that error', (done) => {
service.getCartIdOrFail().pipe(
catchError((err) => {
expect(err).toEqual(error);
done();
return of();
}),
).subscribe((res) => {
fail('should not emit');
});
});
});
});
});

describe('getCartOrFail', () => {
describe('when getCartIdOrFail succeeds', () => {
beforeEach(() => {
spyOn(service, 'getCartIdOrFail').and.returnValue(of(cartId));
cartDriverSpy.get.and.returnValue(of(mockCartResponse));
});

it('should call get', (done) => {
service.getCartOrFail().subscribe((res) => {
expect(cartDriverSpy.get).toHaveBeenCalledWith(cartId);
done();
});
});

describe('and when get is successful', () => {
beforeEach(() => {
cartDriverSpy.get.and.returnValue(of(mockCartResponse));
});

it('should return that cart response', (done) => {
service.getCartOrFail().subscribe((res) => {
expect(res).toEqual(mockCartResponse);
done();
});
});
});

describe('and when get fails', () => {
let error: Error;

beforeEach(() => {
error = new Error();
cartDriverSpy.get.and.returnValue(throwError(() => error));
});

it('should throw that error', (done) => {
service.getCartOrFail().pipe(
catchError((err) => {
expect(err).toEqual(error);
done();
return of();
}),
).subscribe((res) => {
fail('should not emit');
});
});
});
});
});
});
65 changes: 65 additions & 0 deletions libs/cart/driver/src/services/resolve.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
Injectable,
Inject,
} from '@angular/core';
import {
of,
Observable,
throwError,
} from 'rxjs';
import {
catchError,
map,
switchMap,
tap,
} from 'rxjs/operators';

import {
DaffCart,
DaffCartStorageService,
} from '@daffodil/cart';
import { DaffDriverResponse } from '@daffodil/driver';

import {
DaffCartDriver,
DaffCartServiceInterface,
} from '../interfaces/public_api';
import { daffCartDriverHandleCartNotFound } from '../operators/public_api';

/**
* A service for resolving the cart in a way that is tolerant of errors and invalid or missing cart IDs in storage.
*/
@Injectable({
providedIn: 'root',
})
export class DaffCartDriverResolveService<T extends DaffCart = DaffCart> {
constructor(
@Inject(DaffCartDriver) private driver: DaffCartServiceInterface<T>,
private storage: DaffCartStorageService,
) {}

/**
* Resolves the cart, creating if no ID is in storage.
* If the cart cannot be found, removes the ID from storage.
* If the cart cannot be resolved, throws an error.
*/
getCartOrFail(): Observable<DaffDriverResponse<T>> {
return this.getCartIdOrFail().pipe(
switchMap((cartId) => this.driver.get(cartId)),
daffCartDriverHandleCartNotFound(this.storage),
);
}

/**
* Retrieves the cart ID, creating if no ID is in storage.
*/
getCartIdOrFail(): Observable<DaffCart['id']> {
const cartId = this.storage.getCartId();
return cartId
? of(cartId)
: this.driver.create().pipe(
map(({ id }) => id),
tap((id) => this.storage.setCartId(id)),
);
}
}

0 comments on commit f691fd8

Please sign in to comment.