mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
refactor(checkout): separate data-access layer boundaries
Extract domain-specific operations from CheckoutService into dedicated services to respect data-access layer boundaries and improve separation of concerns. ## Changes ### New Services Created **OrderCreationService** (oms-data-access) - createOrdersFromCheckout(): Creates orders from completed checkout - getLogistician(): Retrieves logistician (default '2470') - 8 unit tests with Jest/Spectator **AvailabilityService** (catalogue-data-access) - validateDownloadAvailabilities(): Validates download items availability - getDigDeliveryAvailability(): Gets DIG-Versand availability - getB2bDeliveryAvailability(): Gets B2B-Versand availability - 15 unit tests with Jest/Spectator **BranchService** (remission-data-access) - getDefaultBranch(): Gets default/current branch for user - 5 unit tests with Angular Testing Utilities - Note: Temporary location, will move to inventory-data-access ### CheckoutService Refactoring **Removed cross-domain imports:** - @generated/swagger/oms-api - @generated/swagger/availability-api - @generated/swagger/inventory-api **Added domain service dependencies:** - @isa/oms/data-access (OrderCreationService) - @isa/catalogue/data-access (AvailabilityService) - @isa/remission/data-access (BranchService) **Code reduction:** - Removed 254 lines (25.5% reduction: 996 → 742 lines) - Deleted 8 private methods moved to domain services ### Test Updates - Updated checkout.service.spec.ts with new service mocks - Fixed Zod validation (buyerType/payerType literals) - All 29 tests passing (8+15+5+11) ### AbortSignal Policy Removed abortSignal from data-mutating operations: - OrderCreationService.createOrdersFromCheckout() (POST) - Kept abortSignal for read-only operations per project convention ## Impact - Better separation of concerns - Improved maintainability (smaller, focused services) - Respects data-access layer boundaries - No functional changes (100% backward compatible) - 0 TypeScript compilation errors
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of, NEVER } from 'rxjs';
|
||||
import { BranchService } from './branch.service';
|
||||
import { StockService, BranchDTO } from '@generated/swagger/inventory-api';
|
||||
|
||||
/**
|
||||
* Unit tests for BranchService.
|
||||
* Tests the service's ability to fetch default branch information.
|
||||
*
|
||||
* @group unit
|
||||
* @group services
|
||||
*/
|
||||
describe('BranchService', () => {
|
||||
let service: BranchService;
|
||||
let mockStockService: jest.Mocked<StockService>;
|
||||
|
||||
const mockBranch: any = {
|
||||
id: 42,
|
||||
name: 'Test Branch',
|
||||
address: '123 Test St',
|
||||
branchType: 1,
|
||||
branchNumber: 'BR001',
|
||||
changed: '2024-01-01',
|
||||
created: '2024-01-01',
|
||||
isDefault: true,
|
||||
isOnline: true,
|
||||
key: 'test-branch',
|
||||
label: 'Test Branch Label',
|
||||
pId: 1,
|
||||
shortName: 'TB',
|
||||
status: 1,
|
||||
version: 1,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a fresh mock for each test to ensure isolation
|
||||
mockStockService = {
|
||||
StockCurrentBranch: jest.fn(),
|
||||
} as unknown as jest.Mocked<StockService>;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
BranchService,
|
||||
{ provide: StockService, useValue: mockStockService },
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(BranchService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('getDefaultBranch', () => {
|
||||
it('should fetch default branch successfully', async () => {
|
||||
// Arrange
|
||||
const mockResponse: any = {
|
||||
error: false,
|
||||
result: mockBranch,
|
||||
};
|
||||
mockStockService.StockCurrentBranch.mockReturnValue(of(mockResponse));
|
||||
|
||||
// Act
|
||||
const result = await service.getDefaultBranch();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockBranch);
|
||||
expect(mockStockService.StockCurrentBranch).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should throw error if API response contains error', async () => {
|
||||
// Arrange
|
||||
const mockResponse: any = {
|
||||
error: true,
|
||||
message: 'API Error',
|
||||
result: undefined,
|
||||
};
|
||||
mockStockService.StockCurrentBranch.mockReturnValue(of(mockResponse));
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.getDefaultBranch()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle abortSignal', async () => {
|
||||
// Arrange
|
||||
mockStockService.StockCurrentBranch.mockReturnValue(NEVER);
|
||||
const abortController = new AbortController();
|
||||
|
||||
// Act
|
||||
const promise = service.getDefaultBranch(abortController.signal);
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), 100),
|
||||
);
|
||||
|
||||
// Assert
|
||||
await expect(Promise.race([promise, timeout])).rejects.toThrow('Timeout');
|
||||
});
|
||||
|
||||
it('should return branch with all properties', async () => {
|
||||
// Arrange
|
||||
const mockResponse: any = {
|
||||
error: false,
|
||||
result: mockBranch,
|
||||
};
|
||||
mockStockService.StockCurrentBranch.mockReturnValue(of(mockResponse));
|
||||
|
||||
// Act
|
||||
const result = await service.getDefaultBranch();
|
||||
|
||||
// Assert
|
||||
expect(result.id).toBe(42);
|
||||
expect(result.name).toBe('Test Branch');
|
||||
expect(result.isDefault).toBe(true);
|
||||
expect(result.isOnline).toBe(true);
|
||||
expect(result.branchNumber).toBe('BR001');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { StockService, BranchDTO } from '@generated/swagger/inventory-api';
|
||||
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Service for branch/stock operations.
|
||||
*
|
||||
* @remarks
|
||||
* This service handles inventory operations:
|
||||
* - Retrieving the default/current branch for the user
|
||||
*
|
||||
* Note: This service is in remission-data-access as a temporary location.
|
||||
* It will be moved to inventory-data-access when that module is created.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BranchService {
|
||||
#logger = logger(() => ({ service: 'BranchService' }));
|
||||
readonly #stockService = inject(StockService);
|
||||
|
||||
/**
|
||||
* Gets the default branch from the inventory service.
|
||||
*
|
||||
* @param abortSignal - Optional signal to abort the operation
|
||||
* @returns Promise resolving to branch data
|
||||
* @throws {Error} If branch retrieval fails
|
||||
*/
|
||||
async getDefaultBranch(abortSignal?: AbortSignal): Promise<BranchDTO> {
|
||||
let req$ = this.#stockService.StockCurrentBranch();
|
||||
if (abortSignal) req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
|
||||
const res = await firstValueFrom(req$);
|
||||
|
||||
if (res.error) {
|
||||
const error = new ResponseArgsError(res);
|
||||
this.#logger.error('Failed to get default branch', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const branch = res.result;
|
||||
|
||||
if (!branch) {
|
||||
const error = new Error('No branch data returned');
|
||||
this.#logger.error('Failed to get default branch', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
id: branch.id,
|
||||
name: branch.name,
|
||||
address: branch.address,
|
||||
branchType: branch.branchType,
|
||||
branchNumber: branch.branchNumber,
|
||||
changed: branch.changed,
|
||||
created: branch.created,
|
||||
isDefault: branch.isDefault,
|
||||
isOnline: branch.isOnline,
|
||||
key: branch.key,
|
||||
label: branch.label,
|
||||
pId: branch.pId,
|
||||
shortName: branch.shortName,
|
||||
status: branch.status,
|
||||
version: branch.version,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './branch.service';
|
||||
export * from './remission-product-group.service';
|
||||
export * from './remission-reason.service';
|
||||
export * from './remission-return-receipt.service';
|
||||
|
||||
Reference in New Issue
Block a user