mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Fix unit Test
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
NgModule,
|
||||
inject,
|
||||
provideAppInitializer,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
@@ -76,10 +77,17 @@ import {
|
||||
LogLevel,
|
||||
withSink,
|
||||
ConsoleLogSink,
|
||||
logger,
|
||||
} from '@isa/core/logging';
|
||||
import { IDBStorageProvider, UserStorageProvider } from '@isa/core/storage';
|
||||
import {
|
||||
IDBStorageProvider,
|
||||
provideUserSubFactory,
|
||||
UserStorageProvider,
|
||||
} from '@isa/core/storage';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TabNavigationService } from '@isa/core/tabs';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import z from 'zod';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -190,6 +198,31 @@ export function _notificationsHubOptionsFactory(
|
||||
return options;
|
||||
}
|
||||
|
||||
const USER_SUB_FACTORY = () => {
|
||||
const _logger = logger(() => ({
|
||||
context: 'USER_SUB',
|
||||
}));
|
||||
const auth = inject(OAuthService);
|
||||
|
||||
const claims = auth.getIdentityClaims();
|
||||
|
||||
if (!claims || typeof claims !== 'object' || !('sub' in claims)) {
|
||||
const err = new Error('No valid identity claims found. User is anonymous.');
|
||||
_logger.error(err.message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const validation = z.string().safeParse(claims['sub']);
|
||||
|
||||
if (!validation.success) {
|
||||
const err = new Error('Invalid "sub" claim in identity claims.');
|
||||
_logger.error(err.message, { claims });
|
||||
throw err;
|
||||
}
|
||||
|
||||
return signal(validation.data);
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, MainComponent],
|
||||
bootstrap: [AppComponent],
|
||||
@@ -263,6 +296,7 @@ export function _notificationsHubOptionsFactory(
|
||||
provide: DEFAULT_CURRENCY_CODE,
|
||||
useValue: 'EUR',
|
||||
},
|
||||
provideUserSubFactory(USER_SUB_FACTORY),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { of, throwError, NEVER } from 'rxjs';
|
||||
import { RemissionStockService } from './remission-stock.service';
|
||||
import { StockService } from '@generated/swagger/inventory-api';
|
||||
import { ResponseArgsError } from '@isa/common/data-access';
|
||||
import { Stock, StockInfo } from '../models';
|
||||
|
||||
jest.mock('@generated/swagger/inventory-api', () => ({
|
||||
StockService: jest.fn(),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Unit tests for RemissionStockService.
|
||||
* Tests the service's ability to fetch and cache stock information.
|
||||
* These tests are isolated and do not depend on each other.
|
||||
*
|
||||
* @group unit
|
||||
* @group services
|
||||
*/
|
||||
describe('RemissionStockService', () => {
|
||||
let service: RemissionStockService;
|
||||
let mockStockService: {
|
||||
StockCurrentStock: jest.Mock;
|
||||
StockInStock: jest.Mock;
|
||||
};
|
||||
let mockStockService: jest.Mocked<StockService>;
|
||||
|
||||
const mockStock: Stock = {
|
||||
id: 123,
|
||||
@@ -45,13 +39,11 @@ describe('RemissionStockService', () => {
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear all mocks before each test
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Create a fresh mock for each test to ensure isolation
|
||||
mockStockService = {
|
||||
StockCurrentStock: jest.fn(),
|
||||
StockInStock: jest.fn(),
|
||||
};
|
||||
} as unknown as jest.Mocked<StockService>;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
@@ -60,9 +52,16 @@ describe('RemissionStockService', () => {
|
||||
],
|
||||
});
|
||||
|
||||
// Create a fresh service instance for each test
|
||||
service = TestBed.inject(RemissionStockService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clear all mocks after each test to prevent interference
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests for fetchAssignedStock method.
|
||||
* Verifies caching behavior and API interaction.
|
||||
@@ -70,7 +69,7 @@ describe('RemissionStockService', () => {
|
||||
describe('fetchAssignedStock', () => {
|
||||
it('should fetch stock from API', async () => {
|
||||
mockStockService.StockCurrentStock.mockReturnValue(
|
||||
of({ result: mockStock, error: null }),
|
||||
of({ result: mockStock, error: false }),
|
||||
);
|
||||
|
||||
const result = await service.fetchAssignedStock();
|
||||
@@ -80,7 +79,7 @@ describe('RemissionStockService', () => {
|
||||
});
|
||||
|
||||
it('should throw ResponseArgsError when API returns error', async () => {
|
||||
const errorResponse = { error: 'API Error', result: null };
|
||||
const errorResponse = { error: true, result: undefined };
|
||||
mockStockService.StockCurrentStock.mockReturnValue(of(errorResponse));
|
||||
|
||||
await expect(service.fetchAssignedStock()).rejects.toThrow(
|
||||
@@ -90,7 +89,7 @@ describe('RemissionStockService', () => {
|
||||
|
||||
it('should throw ResponseArgsError when API returns no result', async () => {
|
||||
mockStockService.StockCurrentStock.mockReturnValue(
|
||||
of({ error: null, result: null }),
|
||||
of({ error: false, result: undefined }),
|
||||
);
|
||||
|
||||
await expect(service.fetchAssignedStock()).rejects.toThrow(
|
||||
@@ -98,20 +97,24 @@ describe('RemissionStockService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle abort signal', async () => {
|
||||
it('should handle abort signal by configuring the observable pipeline', async () => {
|
||||
// Arrange
|
||||
const abortController = new AbortController();
|
||||
const mockObservable = {
|
||||
pipe: jest.fn().mockReturnThis(),
|
||||
};
|
||||
mockStockService.StockCurrentStock.mockReturnValue(mockObservable);
|
||||
const pipeSpy = jest.fn().mockReturnValue(
|
||||
of({ result: mockStock, error: false }),
|
||||
);
|
||||
|
||||
mockStockService.StockCurrentStock.mockReturnValue({
|
||||
pipe: pipeSpy,
|
||||
} as any);
|
||||
|
||||
try {
|
||||
await service.fetchAssignedStock(abortController.signal);
|
||||
} catch {
|
||||
// Expected to fail since we're not properly mocking the observable
|
||||
}
|
||||
// Act
|
||||
const result = await service.fetchAssignedStock(abortController.signal);
|
||||
|
||||
expect(mockObservable.pipe).toHaveBeenCalled();
|
||||
// Assert
|
||||
expect(result).toEqual(mockStock);
|
||||
expect(pipeSpy).toHaveBeenCalled();
|
||||
expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle API observable errors', async () => {
|
||||
@@ -132,19 +135,23 @@ describe('RemissionStockService', () => {
|
||||
describe('fetchStock', () => {
|
||||
/**
|
||||
* Valid test parameters for stock fetching.
|
||||
* Note: The schema expects 'stockId', not 'assignedStockId'
|
||||
*/
|
||||
const validParams = {
|
||||
assignedStockId: 123,
|
||||
stockId: 123,
|
||||
itemIds: [100, 101],
|
||||
};
|
||||
|
||||
it('should fetch stock info successfully', async () => {
|
||||
it('should fetch stock info successfully with stockId provided', async () => {
|
||||
// Arrange
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
of({ result: mockStockInfo, error: null }),
|
||||
of({ result: mockStockInfo, error: false }),
|
||||
);
|
||||
|
||||
// Act
|
||||
const result = await service.fetchStock(validParams);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockStockInfo);
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalledWith({
|
||||
stockId: 123,
|
||||
@@ -152,69 +159,118 @@ describe('RemissionStockService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ResponseArgsError when API returns error', async () => {
|
||||
const errorResponse = { error: 'API Error', result: null };
|
||||
mockStockService.StockInStock.mockReturnValue(of(errorResponse));
|
||||
|
||||
await expect(service.fetchStock(validParams)).rejects.toThrow(
|
||||
ResponseArgsError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw ResponseArgsError when API returns no result', async () => {
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
of({ error: null, result: null }),
|
||||
);
|
||||
|
||||
await expect(service.fetchStock(validParams)).rejects.toThrow(
|
||||
ResponseArgsError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle abort signal', async () => {
|
||||
const abortController = new AbortController();
|
||||
const mockObservable = {
|
||||
pipe: jest.fn().mockReturnThis(),
|
||||
};
|
||||
mockStockService.StockInStock.mockReturnValue(mockObservable);
|
||||
|
||||
try {
|
||||
await service.fetchStock(validParams, abortController.signal);
|
||||
} catch {
|
||||
// Expected to fail since we're not properly mocking the observable
|
||||
}
|
||||
|
||||
expect(mockObservable.pipe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate params with schema', async () => {
|
||||
// This will be validated by the schema
|
||||
const invalidParams = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
assignedStockId: 'invalid' as any, // Should be number
|
||||
it('should fetch assigned stock when stockId not provided', async () => {
|
||||
// Arrange
|
||||
const paramsWithoutStockId = {
|
||||
itemIds: [100, 101],
|
||||
};
|
||||
|
||||
mockStockService.StockCurrentStock.mockReturnValue(
|
||||
of({ result: mockStock, error: false }),
|
||||
);
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
of({ result: mockStockInfo, error: null }),
|
||||
of({ result: mockStockInfo, error: false }),
|
||||
);
|
||||
|
||||
// The schema parsing should throw an error for invalid params
|
||||
// Act
|
||||
const result = await service.fetchStock(paramsWithoutStockId);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockStockInfo);
|
||||
expect(mockStockService.StockCurrentStock).toHaveBeenCalled();
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalledWith({
|
||||
stockId: 123, // Should use the fetched assigned stock ID
|
||||
articleIds: [100, 101],
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ResponseArgsError when API returns error', async () => {
|
||||
// Arrange
|
||||
const errorResponse = { error: true, result: undefined };
|
||||
mockStockService.StockInStock.mockReturnValue(of(errorResponse));
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.fetchStock(validParams)).rejects.toThrow(
|
||||
ResponseArgsError,
|
||||
);
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw ResponseArgsError when API returns no result', async () => {
|
||||
// Arrange
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
of({ error: false, result: undefined }),
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.fetchStock(validParams)).rejects.toThrow(
|
||||
ResponseArgsError,
|
||||
);
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle abort signal by configuring the observable pipeline', async () => {
|
||||
// Arrange
|
||||
const abortController = new AbortController();
|
||||
const pipeSpy = jest.fn().mockReturnValue(
|
||||
of({ result: mockStockInfo, error: false }),
|
||||
);
|
||||
|
||||
mockStockService.StockInStock.mockReturnValue({
|
||||
pipe: pipeSpy,
|
||||
} as any);
|
||||
|
||||
// Act
|
||||
const result = await service.fetchStock(validParams, abortController.signal);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockStockInfo);
|
||||
expect(pipeSpy).toHaveBeenCalled();
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate params with schema and reject invalid stockId type', async () => {
|
||||
// Arrange - This will be validated by the schema
|
||||
const invalidParams = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
stockId: 'invalid' as any, // Should be number
|
||||
itemIds: [100, 101],
|
||||
};
|
||||
|
||||
// Act & Assert - The schema parsing should throw an error for invalid params
|
||||
await expect(service.fetchStock(invalidParams)).rejects.toThrow();
|
||||
// Should not reach the API call
|
||||
expect(mockStockService.StockInStock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate params with schema and reject invalid itemIds type', async () => {
|
||||
// Arrange
|
||||
const invalidParams = {
|
||||
stockId: 123,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
itemIds: 'not-an-array' as any, // Should be array
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.fetchStock(invalidParams)).rejects.toThrow();
|
||||
expect(mockStockService.StockInStock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty itemIds array', async () => {
|
||||
// Arrange
|
||||
const paramsWithEmptyItems = {
|
||||
assignedStockId: 123,
|
||||
stockId: 123,
|
||||
itemIds: [],
|
||||
};
|
||||
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
of({ result: [], error: null }),
|
||||
of({ result: [], error: false }),
|
||||
);
|
||||
|
||||
// Act
|
||||
const result = await service.fetchStock(paramsWithEmptyItems);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalledWith({
|
||||
stockId: 123,
|
||||
@@ -223,23 +279,30 @@ describe('RemissionStockService', () => {
|
||||
});
|
||||
|
||||
it('should handle API observable errors', async () => {
|
||||
// Arrange
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
throwError(() => new Error('Network error')),
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.fetchStock(validParams)).rejects.toThrow(
|
||||
'Network error',
|
||||
);
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty array when API returns empty result', async () => {
|
||||
// Arrange
|
||||
mockStockService.StockInStock.mockReturnValue(
|
||||
of({ result: [], error: null }),
|
||||
of({ result: [], error: false }),
|
||||
);
|
||||
|
||||
// Act
|
||||
const result = await service.fetchStock(validParams);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
expect(mockStockService.StockInStock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user