Skip to content

Commit

Permalink
feat: support for new OCI Punchout REST API v2 including product vali…
Browse files Browse the repository at this point in the history
…dation and background search

- create a new basket for every punchout session to avoid basket conflicts for concurrent sessions
- removed no longer needed mock data

BREAKING CHANGES: required Intershop Commerce Management version: 7.10.28.0
  • Loading branch information
shauke committed Apr 1, 2021
1 parent 4a8f7e3 commit a5226f7
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { catchError, concatMap, mapTo, switchMap, take, tap } from 'rxjs/operato

import { AccountFacade } from 'ish-core/facades/account.facade';
import { AppFacade } from 'ish-core/facades/app.facade';
import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
import { CookiesService } from 'ish-core/utils/cookies/cookies.service';
import { whenTruthy } from 'ish-core/utils/operators';

Expand All @@ -15,8 +16,9 @@ import { PunchoutService } from '../../services/punchout/punchout.service';
export class PunchoutPageGuard implements CanActivate {
constructor(
private router: Router,
private accountFacade: AccountFacade,
private appFacade: AppFacade,
private accountFacade: AccountFacade,
private checkoutFacade: CheckoutFacade,
private cookiesService: CookiesService,
private punchoutService: PunchoutService,
@Inject(PLATFORM_ID) private platformId: string
Expand Down Expand Up @@ -59,14 +61,17 @@ export class PunchoutPageGuard implements CanActivate {
this.cookiesService.put('hookURL', route.queryParamMap.get('HOOK_URL'), { sameSite: 'Strict' });
}

// create a new basket for every punchout session to avoid basket conflicts for concurrent sessions
this.checkoutFacade.createBasket();

// Product Details
if (route.queryParamMap.get('FUNCTION') === 'DETAIL' && route.queryParamMap.get('PRODUCTID')) {
return of(this.router.parseUrl(`/product/${route.queryParamMap.get('PRODUCTID')}`));

// Validation of Products
} else if (route.queryParamMap.get('FUNCTION') === 'VALIDATE' && route.queryParamMap.get('PRODUCTID')) {
return this.punchoutService
.getProductPunchoutData(route.queryParamMap.get('PRODUCTID'), route.queryParamMap.get('QUANTITY'))
.getProductPunchoutData(route.queryParamMap.get('PRODUCTID'), route.queryParamMap.get('QUANTITY') || '1')
.pipe(
tap(data => this.punchoutService.submitPunchoutData(data)),
mapTo(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ describe('Punchout Service', () => {
apiServiceMock = mock(ApiService);
cookiesServiceMock = mock(CookiesService);

when(apiServiceMock.get(anything())).thenReturn(of({}));
when(apiServiceMock.resolveLinks()).thenReturn(() => of([]));
when(apiServiceMock.post(anything(), anything())).thenReturn(of({}));
when(apiServiceMock.resolveLink()).thenReturn(() => of({}));
when(apiServiceMock.put(anything(), anything())).thenReturn(of({}));
when(apiServiceMock.delete(anything())).thenReturn(of({}));
when(apiServiceMock.get(anything(), anything())).thenReturn(of({}));
when(apiServiceMock.resolveLinks(anything())).thenReturn(() => of([]));
when(apiServiceMock.post(anything(), anything(), anything())).thenReturn(of({}));
when(apiServiceMock.resolveLink(anything())).thenReturn(() => of({}));
when(apiServiceMock.put(anything(), anything(), anything())).thenReturn(of({}));
when(apiServiceMock.delete(anything(), anything())).thenReturn(of({}));

TestBed.configureTestingModule({
providers: [
Expand All @@ -46,12 +46,19 @@ describe('Punchout Service', () => {
expect(punchoutService).toBeTruthy();
});

it('should call the getPunchoutOciUsers when fetching punchout users', done => {
it('should call the getUsers when fetching punchout users', done => {
punchoutService.getUsers().subscribe(() => {
verify(apiServiceMock.get(anything())).once();
verify(apiServiceMock.get(anything(), anything())).once();
expect(capture(apiServiceMock.get).last()).toMatchInlineSnapshot(`
Array [
"customers/4711/punchouts/oci/users",
"customers/4711/punchouts/oci5/users",
Object {
"headers": HttpHeaders {
"lazyInit": [Function],
"lazyUpdate": null,
"normalizedNames": Map {},
},
},
]
`);
done();
Expand All @@ -60,27 +67,27 @@ describe('Punchout Service', () => {

it('should call the addUser for creating a new punchout user', done => {
punchoutService.createUser({ login: 'ociuser' } as PunchoutUser).subscribe(() => {
verify(apiServiceMock.post(anything(), anything())).once();
expect(capture(apiServiceMock.post).last()[0]).toMatchInlineSnapshot(`"customers/4711/punchouts/oci/users"`);
verify(apiServiceMock.post(anything(), anything(), anything())).once();
expect(capture(apiServiceMock.post).last()[0]).toMatchInlineSnapshot(`"customers/4711/punchouts/oci5/users"`);
done();
});
});

it('should call the updateUser for updating a punchout user', done => {
punchoutService.updateUser({ login: 'ociuser' } as PunchoutUser).subscribe(() => {
verify(apiServiceMock.put(anything(), anything())).once();
verify(apiServiceMock.put(anything(), anything(), anything())).once();
expect(capture(apiServiceMock.put).last()[0]).toMatchInlineSnapshot(
`"customers/4711/punchouts/oci/users/ociuser"`
`"customers/4711/punchouts/oci5/users/ociuser"`
);
done();
});
});

it('should call deleteUser for deleting a punchout user', done => {
punchoutService.deleteUser('ociuser').subscribe(() => {
verify(apiServiceMock.delete(anything())).once();
verify(apiServiceMock.delete(anything(), anything())).once();
expect(capture(apiServiceMock.delete).last()[0]).toMatchInlineSnapshot(
`"customers/4711/punchouts/oci/users/ociuser"`
`"customers/4711/punchouts/oci5/users/ociuser"`
);
done();
});
Expand Down
58 changes: 39 additions & 19 deletions src/app/extensions/punchout/services/punchout/punchout.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpParams } from '@angular/common/http';
import { HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable, throwError } from 'rxjs';
Expand All @@ -19,18 +19,27 @@ export class PunchoutService {

private currentCustomer$ = this.store.pipe(select(getLoggedInCustomer), whenTruthy(), take(1));

/**
* http header for Punchout API v2
*/
private punchoutHeaders = new HttpHeaders({
Accept: 'application/vnd.intershop.punchout.v2+json',
});

/**
* Gets the list of punchout users.
* @returns An array of punchout users.
*/
getUsers(): Observable<PunchoutUser[]> {
return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService.get(`customers/${customer.customerNo}/punchouts/oci/users`).pipe(
unpackEnvelope<Link>(),
this.apiService.resolveLinks<PunchoutUser>(),
map(users => users.map(user => ({ ...user, password: undefined })))
)
this.apiService
.get(`customers/${customer.customerNo}/punchouts/oci5/users`, { headers: this.punchoutHeaders })
.pipe(
unpackEnvelope<Link>(),
this.apiService.resolveLinks<PunchoutUser>({ headers: this.punchoutHeaders }),
map(users => users.map(user => ({ ...user, password: undefined })))
)
)
);
}
Expand All @@ -47,10 +56,14 @@ export class PunchoutService {

return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService.post(`customers/${customer.customerNo}/punchouts/oci/users`, user).pipe(
this.apiService.resolveLink<PunchoutUser>(),
map(updatedUser => ({ ...updatedUser, password: undefined }))
)
this.apiService
.post(`customers/${customer.customerNo}/punchouts/oci5/users`, user, {
headers: this.punchoutHeaders,
})
.pipe(
this.apiService.resolveLink<PunchoutUser>({ headers: this.punchoutHeaders }),
map(updatedUser => ({ ...updatedUser, password: undefined }))
)
)
);
}
Expand All @@ -68,7 +81,9 @@ export class PunchoutService {
return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService
.put<PunchoutUser>(`customers/${customer.customerNo}/punchouts/oci/users/${user.login}`, user)
.put<PunchoutUser>(`customers/${customer.customerNo}/punchouts/oci5/users/${user.login}`, user, {
headers: this.punchoutHeaders,
})
.pipe(map(updatedUser => ({ ...updatedUser, password: undefined })))
)
);
Expand All @@ -85,7 +100,9 @@ export class PunchoutService {

return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService.delete<void>(`customers/${customer.customerNo}/punchouts/oci/users/${login}`)
this.apiService.delete<void>(`customers/${customer.customerNo}/punchouts/oci5/users/${login}`, {
headers: this.punchoutHeaders,
})
)
);
}
Expand All @@ -102,7 +119,8 @@ export class PunchoutService {
return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService
.post<{ data: Attribute<string>[] }>(`customers/${customer.customerNo}/punchouts/oci/transfer`, undefined, {
.post<{ data: Attribute<string>[] }>(`customers/${customer.customerNo}/punchouts/oci5/transfer`, undefined, {
headers: this.punchoutHeaders,
params: new HttpParams().set('basketId', basketId),
})
.pipe(map(data => data.data))
Expand All @@ -113,18 +131,19 @@ export class PunchoutService {
/**
* Gets a JSON object with the necessary punchout data for the product validation.
* @param productSKU The product SKU of the product to validate.
* @param quantity The quantity for the validation.
* @param quantity The quantity for the validation (default: '1').
*/
getProductPunchoutData(productSKU: string, quantity: string): Observable<Attribute<string>[]> {
if (!productSKU) {
getProductPunchoutData(productId: string, quantity = '1'): Observable<Attribute<string>[]> {
if (!productId) {
return throwError('getProductPunchoutData() of the punchout service called without productSKU');
}

return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService
.get<{ data: Attribute<string>[] }>(`customers/${customer.customerNo}/punchouts/oci/validate`, {
params: new HttpParams().set('productSKU', productSKU).set('quantity', quantity),
.get<{ data: Attribute<string>[] }>(`customers/${customer.customerNo}/punchouts/oci5/validate`, {
headers: this.punchoutHeaders,
params: new HttpParams().set('productId', productId).set('quantity', quantity),
})
.pipe(map(data => data.data))
)
Expand All @@ -143,7 +162,8 @@ export class PunchoutService {
return this.currentCustomer$.pipe(
switchMap(customer =>
this.apiService
.get<{ data: Attribute<string>[] }>(`customers/${customer.customerNo}/punchouts/oci/search`, {
.get<{ data: Attribute<string>[] }>(`customers/${customer.customerNo}/punchouts/oci5/background-search`, {
headers: this.punchoutHeaders,
params: new HttpParams().set('searchString', searchString),
})
.pipe(map(data => data.data))
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

2 changes: 0 additions & 2 deletions src/environments/environment.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,4 @@ export const ENVIRONMENT_DEFAULTS: Environment = {
allowedCookies: ['cookieConsent', 'apiToken'],
},
cookieConsentVersion: 1,
// TODO: to no longer test punchout VALIDATE and SEARCH with mock data the next line needs to be removed once the ICM punchout REST API offers this functionality
apiMockPaths: ['^customers/OilCorp/punchouts/oci/(validate|search)'],
};

0 comments on commit a5226f7

Please sign in to comment.