[ACS-12332] Add checkout and cancel-checkout node support (#5334)

* [ACS-12332] Add checkout and cancel-checkout node support

* [ACS-12332] cr fix
This commit is contained in:
Mykyta Maliarchuk
2026-08-19 10:36:47 +02:00
committed by GitHub
parent ca364f23d4
commit df92e11d9a
3 changed files with 133 additions and 1 deletions
@@ -2410,4 +2410,91 @@ describe('ContentManagementService', () => {
expect(showErrorSpy).toHaveBeenCalledWith('APP.MESSAGES.ERRORS.GENERIC');
});
});
describe('Checkout operations', () => {
const nodeId = 'fake-node-id';
const makeApiError = (statusCode: number): Error => new Error(JSON.stringify({ error: { statusCode } }));
describe('checkout()', () => {
it('should return the working copy NodeEntry on success', () => {
const fakeEntry = { entry: { id: nodeId } } as NodeEntry;
spyOn(nodesApiService, 'checkoutNode').and.returnValue(of(fakeEntry));
contentManagementService.checkout(nodeId).subscribe((result) => {
expect(nodesApiService.checkoutNode).toHaveBeenCalledWith(nodeId);
expect(result).toEqual(fakeEntry);
});
});
it('should show 400 error notification', () => {
spyOn(nodesApiService, 'checkoutNode').and.returnValue(throwError(() => makeApiError(400)));
contentManagementService.checkout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.CHECKOUT.400');
});
it('should show 403 error notification', () => {
spyOn(nodesApiService, 'checkoutNode').and.returnValue(throwError(() => makeApiError(403)));
contentManagementService.checkout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.CHECKOUT.403');
});
it('should show 404 error notification', () => {
spyOn(nodesApiService, 'checkoutNode').and.returnValue(throwError(() => makeApiError(404)));
contentManagementService.checkout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.404');
});
it('should show 409 error notification', () => {
spyOn(nodesApiService, 'checkoutNode').and.returnValue(throwError(() => makeApiError(409)));
contentManagementService.checkout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.CHECKOUT.409');
});
it('should show UNKNOWN notification for unrecognised status code (500)', () => {
spyOn(nodesApiService, 'checkoutNode').and.returnValue(throwError(() => makeApiError(500)));
contentManagementService.checkout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.UNKNOWN');
});
});
describe('cancelCheckout()', () => {
it('should return the original NodeEntry on success', () => {
const fakeEntry = { entry: { id: nodeId } } as NodeEntry;
spyOn(nodesApiService, 'cancelCheckoutNode').and.returnValue(of(fakeEntry));
contentManagementService.cancelCheckout(nodeId).subscribe((result) => {
expect(result).toEqual(fakeEntry);
expect(nodesApiService.cancelCheckoutNode).toHaveBeenCalledWith(nodeId);
});
});
it('should show 400 error notification', () => {
spyOn(nodesApiService, 'cancelCheckoutNode').and.returnValue(throwError(() => makeApiError(400)));
contentManagementService.cancelCheckout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.CANCEL_CHECKOUT.400');
});
it('should show 403 error notification', () => {
spyOn(nodesApiService, 'cancelCheckoutNode').and.returnValue(throwError(() => makeApiError(403)));
contentManagementService.cancelCheckout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.CANCEL_CHECKOUT.403');
});
it('should show 404 error notification', () => {
spyOn(nodesApiService, 'cancelCheckoutNode').and.returnValue(throwError(() => makeApiError(404)));
contentManagementService.cancelCheckout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.404');
});
it('should show 409 error notification', () => {
spyOn(nodesApiService, 'cancelCheckoutNode').and.returnValue(throwError(() => makeApiError(409)));
contentManagementService.cancelCheckout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.CANCEL_CHECKOUT.409');
});
it('should show UNKNOWN notification for unrecognised status code', () => {
spyOn(nodesApiService, 'cancelCheckoutNode').and.returnValue(throwError(() => makeApiError(503)));
contentManagementService.cancelCheckout(nodeId).subscribe({ next: () => {} });
expect(showErrorSpy).toHaveBeenCalledWith('CHECKOUT.ERRORS.UNKNOWN');
});
});
});
});
@@ -53,7 +53,7 @@ import { DeletedNodesPaging, Node, NodeEntry, PathInfo, SiteBodyCreate, SiteEntr
import { inject, Injectable } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { forkJoin, Observable, of, zip } from 'rxjs';
import { forkJoin, Observable, of, zip, EMPTY } from 'rxjs';
import { catchError, map, mergeMap, take, tap } from 'rxjs/operators';
import { LinkOperationResult, NodeActionsService } from './node-actions.service';
import { ActivatedRoute, Router } from '@angular/router';
@@ -543,6 +543,35 @@ export class ContentManagementService {
});
}
checkout(nodeId: string): Observable<NodeEntry> {
return this.nodesApiService.checkoutNode(nodeId).pipe(
catchError((err) => {
this.notificationService.showError(this.resolveCheckoutErrorKey(err, 'CHECKOUT'));
return EMPTY;
})
);
}
cancelCheckout(nodeId: string): Observable<NodeEntry> {
return this.nodesApiService.cancelCheckoutNode(nodeId).pipe(
catchError((err) => {
this.notificationService.showError(this.resolveCheckoutErrorKey(err, 'CANCEL_CHECKOUT'));
return EMPTY;
})
);
}
private resolveCheckoutErrorKey(error: Error, operation: 'CHECKOUT' | 'CANCEL_CHECKOUT'): string {
let statusCode = -1;
try {
statusCode = JSON.parse(error.message).error.statusCode;
} catch {}
if (statusCode === 404) {
return 'CHECKOUT.ERRORS.404';
}
return [400, 403, 409].includes(statusCode) ? `CHECKOUT.ERRORS.${operation}.${statusCode}` : 'CHECKOUT.ERRORS.UNKNOWN';
}
private showCopyMessage(info: any, nodes: Array<NodeEntry>, newItems?: Array<NodeEntry>) {
const succeeded = newItems?.length ?? 0;
const failed = nodes.length - succeeded;