@intellegens/cornerstone-client 0.0.9999-alpha-9 → 0.0.9999-alpha-10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/index.ts +29 -0
- package/demo/public_html/favicon.ico +0 -0
- package/demo/public_html/index.html +106 -0
- package/demo/public_html/websettings.json +3 -0
- package/jest.config.js +29 -0
- package/package.json +1 -1
- package/src/adapters/CollectionViewAdapter/index.ts +390 -0
- package/src/adapters/index.ts +1 -0
- package/src/data/api/dto/PropertyPathDto.ts +4 -0
- package/src/data/api/dto/ReadOptionsDto.ts +8 -0
- package/src/data/api/dto/ReadResultDto.ts +13 -0
- package/src/data/api/dto/ReadResultMetadataDto.ts +8 -0
- package/src/data/api/dto/crud/CrudMetadataDto.ts +4 -0
- package/src/data/api/dto/crud/index.ts +1 -0
- package/src/data/api/dto/index.ts +4 -0
- package/src/data/api/dto/read/ReadMetadataDto.ts +8 -0
- package/src/data/api/dto/read/ReadSelectedDefinitionDto.ts +21 -0
- package/src/data/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.ts +25 -0
- package/src/data/api/dto/read/ReadSelectedNestedCriteriaDto.ts +20 -0
- package/src/data/api/dto/read/ReadSelectedOrderingDefinitionDto.ts +8 -0
- package/src/data/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.ts +16 -0
- package/src/data/api/dto/read/ReadSelectedPaginationDefinitionDto.ts +13 -0
- package/src/data/api/dto/read/ReadSelectedSearchDefinitionDto.ts +43 -0
- package/src/data/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.ts +186 -0
- package/src/data/api/dto/read/index.ts +9 -0
- package/src/data/api/dto/response/ApiErrorDto.ts +21 -0
- package/src/data/api/dto/response/ApiErrorResponseDto.ts +13 -0
- package/src/data/api/dto/response/ApiResponseDto.ts +7 -0
- package/src/data/api/dto/response/ApiSuccessResponseDto.ts +13 -0
- package/src/data/api/dto/response/MetadataDto.ts +24 -0
- package/src/data/api/dto/response/index.ts +5 -0
- package/src/data/api/enum/index.ts +2 -0
- package/src/data/api/enum/read/ReadSelectedCollectionOperator.ts +17 -0
- package/src/data/api/enum/read/ReadSelectedComparisonOperator.ts +96 -0
- package/src/data/api/enum/read/ReadSelectedLogicalOperator.ts +16 -0
- package/src/data/api/enum/read/ReadSelectedOrderingDirection.ts +13 -0
- package/src/data/api/enum/read/ReadSelectedPropertyType.ts +86 -0
- package/src/data/api/enum/read/index.ts +5 -0
- package/src/data/api/enum/response/ErrorCode.ts +13 -0
- package/src/data/api/enum/response/index.ts +1 -0
- package/src/data/api/index.ts +3 -0
- package/src/data/api/interface/IConcurrencySafe.ts +9 -0
- package/src/data/api/interface/IIdentifiable.ts +12 -0
- package/src/data/api/interface/IIdentifiableSecondary.ts +9 -0
- package/src/data/api/interface/index.ts +3 -0
- package/src/data/auth/dto/ClaimDto.ts +4 -0
- package/src/data/auth/dto/RegisterRequestDto.ts +4 -0
- package/src/data/auth/dto/RoleDto.ts +6 -0
- package/src/data/auth/dto/SignInRequestDto.ts +4 -0
- package/src/data/auth/dto/TokensDto.ts +4 -0
- package/src/data/auth/dto/UserDto.ts +18 -0
- package/src/data/auth/dto/UserInfoDto.ts +15 -0
- package/src/data/auth/dto/index.ts +4 -0
- package/src/data/auth/index.ts +2 -0
- package/src/data/auth/policy.ts +63 -0
- package/src/data/index.ts +2 -0
- package/src/index.ts +4 -0
- package/src/services/api/ApiCrudControllerClient/index.ts +129 -0
- package/src/services/api/ApiInitializationService/index.ts +254 -0
- package/src/services/api/ApiReadControllerClient/index.ts +137 -0
- package/src/services/api/HttpService/FetchHttpService.ts +34 -0
- package/src/services/api/HttpService/HttpRequestConfig.ts +10 -0
- package/src/services/api/HttpService/HttpResponse.ts +14 -0
- package/src/services/api/HttpService/IHttpService.ts +17 -0
- package/src/services/api/HttpService/README.md +106 -0
- package/src/services/api/HttpService/index.ts +12 -0
- package/src/services/api/UserManagementControllerClient/index.ts +160 -0
- package/src/services/api/index.ts +5 -0
- package/src/services/auth/client/AuthService/index.ts +187 -0
- package/src/services/auth/client/AuthorizationManagementControllerClient/index.ts +165 -0
- package/src/services/auth/client/index.ts +2 -0
- package/src/services/auth/index.ts +1 -0
- package/src/services/index.ts +2 -0
- package/src/utils/authorization/index.ts +47 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/result/index.ts +25 -0
- package/src/utils/search/index.ts +150 -0
- package/tests/ApiClients.test.ts +284 -0
- package/tests/CollectionViewAdapter.test.ts +392 -0
- package/tests/HttpService.test.ts +303 -0
- package/tests/setup.ts +76 -0
- package/tsconfig.json +19 -0
- package/LICENSE.md +0 -7
- /package/{adapters → dist/adapters}/CollectionViewAdapter/index.d.ts +0 -0
- /package/{adapters → dist/adapters}/CollectionViewAdapter/index.js +0 -0
- /package/{adapters → dist/adapters}/index.d.ts +0 -0
- /package/{adapters → dist/adapters}/index.js +0 -0
- /package/{data → dist/data}/api/dto/PropertyPathDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/PropertyPathDto.js +0 -0
- /package/{data → dist/data}/api/dto/ReadOptionsDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/ReadOptionsDto.js +0 -0
- /package/{data → dist/data}/api/dto/ReadResultDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/ReadResultDto.js +0 -0
- /package/{data → dist/data}/api/dto/ReadResultMetadataDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/ReadResultMetadataDto.js +0 -0
- /package/{data → dist/data}/api/dto/crud/CrudMetadataDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/crud/CrudMetadataDto.js +0 -0
- /package/{data → dist/data}/api/dto/crud/index.d.ts +0 -0
- /package/{data → dist/data}/api/dto/crud/index.js +0 -0
- /package/{data → dist/data}/api/dto/index.d.ts +0 -0
- /package/{data → dist/data}/api/dto/index.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadMetadataDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadMetadataDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedDefinitionDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedDefinitionDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCriteriaDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedNestedCriteriaDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedOrderingDefinitionDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedOrderingDefinitionDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedPaginationDefinitionDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedPaginationDefinitionDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedSearchDefinitionDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedSearchDefinitionDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.js +0 -0
- /package/{data → dist/data}/api/dto/read/index.d.ts +0 -0
- /package/{data → dist/data}/api/dto/read/index.js +0 -0
- /package/{data → dist/data}/api/dto/response/ApiErrorDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/ApiErrorDto.js +0 -0
- /package/{data → dist/data}/api/dto/response/ApiErrorResponseDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/ApiErrorResponseDto.js +0 -0
- /package/{data → dist/data}/api/dto/response/ApiResponseDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/ApiResponseDto.js +0 -0
- /package/{data → dist/data}/api/dto/response/ApiSuccessResponseDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/ApiSuccessResponseDto.js +0 -0
- /package/{data → dist/data}/api/dto/response/EmptyMetadataDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/EmptyMetadataDto.js +0 -0
- /package/{data → dist/data}/api/dto/response/MetadataDto.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/MetadataDto.js +0 -0
- /package/{data → dist/data}/api/dto/response/index.d.ts +0 -0
- /package/{data → dist/data}/api/dto/response/index.js +0 -0
- /package/{data → dist/data}/api/enum/index.d.ts +0 -0
- /package/{data → dist/data}/api/enum/index.js +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedCollectionOperator.d.ts +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedCollectionOperator.js +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedComparisonOperator.d.ts +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedComparisonOperator.js +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedLogicalOperator.d.ts +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedLogicalOperator.js +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedOrderingDirection.d.ts +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedOrderingDirection.js +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedPropertyType.d.ts +0 -0
- /package/{data → dist/data}/api/enum/read/ReadSelectedPropertyType.js +0 -0
- /package/{data → dist/data}/api/enum/read/index.d.ts +0 -0
- /package/{data → dist/data}/api/enum/read/index.js +0 -0
- /package/{data → dist/data}/api/enum/response/ApiErrorCodes.d.ts +0 -0
- /package/{data → dist/data}/api/enum/response/ApiErrorCodes.js +0 -0
- /package/{data → dist/data}/api/enum/response/ErrorCode.d.ts +0 -0
- /package/{data → dist/data}/api/enum/response/ErrorCode.js +0 -0
- /package/{data → dist/data}/api/enum/response/index.d.ts +0 -0
- /package/{data → dist/data}/api/enum/response/index.js +0 -0
- /package/{data → dist/data}/api/index.d.ts +0 -0
- /package/{data → dist/data}/api/index.js +0 -0
- /package/{data → dist/data}/api/interface/IConcurrencySafe.d.ts +0 -0
- /package/{data → dist/data}/api/interface/IConcurrencySafe.js +0 -0
- /package/{data → dist/data}/api/interface/IIdentifiable.d.ts +0 -0
- /package/{data → dist/data}/api/interface/IIdentifiable.js +0 -0
- /package/{data → dist/data}/api/interface/IIdentifiableSecondary.d.ts +0 -0
- /package/{data → dist/data}/api/interface/IIdentifiableSecondary.js +0 -0
- /package/{data → dist/data}/api/interface/index.d.ts +0 -0
- /package/{data → dist/data}/api/interface/index.js +0 -0
- /package/{data → dist/data}/auth/dto/ClaimDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/ClaimDto.js +0 -0
- /package/{data → dist/data}/auth/dto/RegisterRequestDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/RegisterRequestDto.js +0 -0
- /package/{data → dist/data}/auth/dto/RoleDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/RoleDto.js +0 -0
- /package/{data → dist/data}/auth/dto/SignInRequestDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/SignInRequestDto.js +0 -0
- /package/{data → dist/data}/auth/dto/TokensDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/TokensDto.js +0 -0
- /package/{data → dist/data}/auth/dto/UserDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/UserDto.js +0 -0
- /package/{data → dist/data}/auth/dto/UserInfoDto.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/UserInfoDto.js +0 -0
- /package/{data → dist/data}/auth/dto/index.d.ts +0 -0
- /package/{data → dist/data}/auth/dto/index.js +0 -0
- /package/{data → dist/data}/auth/index.d.ts +0 -0
- /package/{data → dist/data}/auth/index.js +0 -0
- /package/{data → dist/data}/auth/policy.d.ts +0 -0
- /package/{data → dist/data}/auth/policy.js +0 -0
- /package/{data → dist/data}/index.d.ts +0 -0
- /package/{data → dist/data}/index.js +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{services → dist/services}/api/ApiCrudControllerClient/index.d.ts +0 -0
- /package/{services → dist/services}/api/ApiCrudControllerClient/index.js +0 -0
- /package/{services → dist/services}/api/ApiInitializationService/index.d.ts +0 -0
- /package/{services → dist/services}/api/ApiInitializationService/index.js +0 -0
- /package/{services → dist/services}/api/ApiReadControllerClient/index.d.ts +0 -0
- /package/{services → dist/services}/api/ApiReadControllerClient/index.js +0 -0
- /package/{services → dist/services}/api/HttpService/FetchHttpService.d.ts +0 -0
- /package/{services → dist/services}/api/HttpService/FetchHttpService.js +0 -0
- /package/{services → dist/services}/api/HttpService/HttpRequestConfig.d.ts +0 -0
- /package/{services → dist/services}/api/HttpService/HttpRequestConfig.js +0 -0
- /package/{services → dist/services}/api/HttpService/HttpResponse.d.ts +0 -0
- /package/{services → dist/services}/api/HttpService/HttpResponse.js +0 -0
- /package/{services → dist/services}/api/HttpService/IHttpService.d.ts +0 -0
- /package/{services → dist/services}/api/HttpService/IHttpService.js +0 -0
- /package/{services → dist/services}/api/HttpService/index.d.ts +0 -0
- /package/{services → dist/services}/api/HttpService/index.js +0 -0
- /package/{services → dist/services}/api/UserManagementControllerClient/index.d.ts +0 -0
- /package/{services → dist/services}/api/UserManagementControllerClient/index.js +0 -0
- /package/{services → dist/services}/api/index.d.ts +0 -0
- /package/{services → dist/services}/api/index.js +0 -0
- /package/{services → dist/services}/auth/client/AuthService/index.d.ts +0 -0
- /package/{services → dist/services}/auth/client/AuthService/index.js +0 -0
- /package/{services → dist/services}/auth/client/AuthorizationManagementControllerClient/index.d.ts +0 -0
- /package/{services → dist/services}/auth/client/AuthorizationManagementControllerClient/index.js +0 -0
- /package/{services → dist/services}/auth/client/index.d.ts +0 -0
- /package/{services → dist/services}/auth/client/index.js +0 -0
- /package/{services → dist/services}/auth/index.d.ts +0 -0
- /package/{services → dist/services}/auth/index.js +0 -0
- /package/{services → dist/services}/index.d.ts +0 -0
- /package/{services → dist/services}/index.js +0 -0
- /package/{utils → dist/utils}/authorization/index.d.ts +0 -0
- /package/{utils → dist/utils}/authorization/index.js +0 -0
- /package/{utils → dist/utils}/index.d.ts +0 -0
- /package/{utils → dist/utils}/index.js +0 -0
- /package/{utils → dist/utils}/result/index.d.ts +0 -0
- /package/{utils → dist/utils}/result/index.js +0 -0
- /package/{utils → dist/utils}/search/index.d.ts +0 -0
- /package/{utils → dist/utils}/search/index.js +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
const mockReadSelectedAsync = jest.fn<(definition: unknown) => Promise<unknown>>();
|
|
2
|
+
|
|
3
|
+
jest.mock('@services', () => {
|
|
4
|
+
return {
|
|
5
|
+
ApiReadControllerClient: jest.fn().mockImplementation(() => ({
|
|
6
|
+
readSelectedAsync: mockReadSelectedAsync,
|
|
7
|
+
})),
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
jest.mock('@intellegens/cornerstone-client', () => {
|
|
12
|
+
return {
|
|
13
|
+
ReadSelectedOrderingDirection: {
|
|
14
|
+
Ascending: 'Ascending',
|
|
15
|
+
Descending: 'Descending',
|
|
16
|
+
},
|
|
17
|
+
ReadSelectedLogicalOperator: {
|
|
18
|
+
And: 'And',
|
|
19
|
+
Or: 'Or',
|
|
20
|
+
},
|
|
21
|
+
ReadSelectedComparisonOperator: {
|
|
22
|
+
Contains: 'Contains',
|
|
23
|
+
Equal: 'Equal',
|
|
24
|
+
GreaterOrEqual: 'GreaterOrEqual',
|
|
25
|
+
LessOrEqual: 'LessOrEqual',
|
|
26
|
+
},
|
|
27
|
+
ReadSelectedPropertyType: {
|
|
28
|
+
String: 'String',
|
|
29
|
+
Int: 'Int',
|
|
30
|
+
DateTimeOffset: 'DateTimeOffset',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import { jest, expect, describe, test, beforeEach, afterEach } from '@jest/globals';
|
|
36
|
+
import { ReadSelectedComparisonOperator, ReadSelectedLogicalOperator, ReadSelectedOrderingDirection, ReadSelectedPropertyType } from '../src/data';
|
|
37
|
+
import { CollectionViewAdapter, CollectionViewAdapterOptions } from '../src/adapters';
|
|
38
|
+
import { dateInterval, searchTerm } from '../src/utils';
|
|
39
|
+
|
|
40
|
+
type TestDto = { id: number; name: string; createdAt: string; rowVersion: string };
|
|
41
|
+
|
|
42
|
+
const makeItems = (n: number): TestDto[] =>
|
|
43
|
+
Array.from({ length: n }, (_, i) => ({
|
|
44
|
+
id: i + 1,
|
|
45
|
+
name: `Item ${i + 1}`,
|
|
46
|
+
createdAt: new Date(2024, 0, i + 1).toISOString(),
|
|
47
|
+
rowVersion: '1',
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
const fetchData = async () => {
|
|
51
|
+
// _fetchCurrentPageData() in CollectionViewAdapter has 50ms timeout set
|
|
52
|
+
jest.advanceTimersByTime(60);
|
|
53
|
+
// allow any pending microtasks to resolve
|
|
54
|
+
await Promise.resolve();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe('CollectionViewAdapter', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
jest.useFakeTimers();
|
|
60
|
+
mockReadSelectedAsync.mockReset();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
jest.useRealTimers();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// overrides?: Partial for passing options properties from test:
|
|
68
|
+
// const { adapter, callback } = setup({ useTotalItemCount: true });
|
|
69
|
+
const setup = (overrides?: Partial<CollectionViewAdapterOptions<TestDto>>) => {
|
|
70
|
+
const options: CollectionViewAdapterOptions<TestDto> = {
|
|
71
|
+
pagination: {
|
|
72
|
+
useTotalItemCount: true,
|
|
73
|
+
pageSize: 10,
|
|
74
|
+
pageNumber: 1,
|
|
75
|
+
...overrides?.pagination,
|
|
76
|
+
},
|
|
77
|
+
ordering: {
|
|
78
|
+
maxActiveOrderingColumns: 1,
|
|
79
|
+
orderByPaths: [],
|
|
80
|
+
...overrides?.ordering,
|
|
81
|
+
},
|
|
82
|
+
search: {
|
|
83
|
+
textSearchableProperties: ['name'],
|
|
84
|
+
numericSearchableProperties: ['id'],
|
|
85
|
+
...overrides?.search,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const callback = jest.fn();
|
|
90
|
+
const adapter = new CollectionViewAdapter<number, TestDto>('ControllerName', callback, options);
|
|
91
|
+
|
|
92
|
+
return { adapter, callback };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
test('callback returns correct loading status and collection data', async () => {
|
|
96
|
+
const data = makeItems(10);
|
|
97
|
+
mockReadSelectedAsync.mockResolvedValueOnce({ result: data, metadata: { totalCount: 123 } });
|
|
98
|
+
|
|
99
|
+
const { adapter, callback } = setup({ pagination: { useTotalItemCount: true } });
|
|
100
|
+
|
|
101
|
+
await fetchData();
|
|
102
|
+
|
|
103
|
+
expect(callback).toHaveBeenNthCalledWith(1, true, []);
|
|
104
|
+
expect(callback).toHaveBeenLastCalledWith(false, data);
|
|
105
|
+
expect(adapter.totalItemCount).toBe(123);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('adapter options set correct ordering', async () => {
|
|
109
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
|
|
110
|
+
|
|
111
|
+
const { adapter } = setup();
|
|
112
|
+
const promise = adapter.setOrdering(['name'], ReadSelectedOrderingDirection.Descending);
|
|
113
|
+
|
|
114
|
+
await fetchData();
|
|
115
|
+
await promise;
|
|
116
|
+
|
|
117
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
118
|
+
expect.objectContaining({
|
|
119
|
+
orderingDefinition: {
|
|
120
|
+
order: [
|
|
121
|
+
{
|
|
122
|
+
propertyPath: ['Name'], // PascalCased
|
|
123
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
undefined, // The abort signal parameter
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(adapter.getCurrentOrdering()).toEqual({
|
|
132
|
+
orderByPath: ['name'],
|
|
133
|
+
orderDirection: ReadSelectedOrderingDirection.Descending,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('text search is applied to adapter', async () => {
|
|
138
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(1), metadata: {} });
|
|
139
|
+
|
|
140
|
+
const { adapter } = setup({ search: { textSearchableProperties: ['name'] } });
|
|
141
|
+
|
|
142
|
+
const searchDefinition = searchTerm('Waldo', ['name'], ['id']);
|
|
143
|
+
if (!searchDefinition) throw new Error('searchDefinition is undefined');
|
|
144
|
+
const promise = adapter.setSearchDefinition(searchDefinition);
|
|
145
|
+
await fetchData();
|
|
146
|
+
await promise;
|
|
147
|
+
|
|
148
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
searchDefinition: expect.objectContaining({
|
|
151
|
+
logicalOperator: expect.any(Number),
|
|
152
|
+
propertyCriteria: expect.arrayContaining([
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
propertyPath: 'Name',
|
|
155
|
+
comparisonOperator: ReadSelectedComparisonOperator.Contains,
|
|
156
|
+
valueType: ReadSelectedPropertyType.String,
|
|
157
|
+
value: 'Waldo',
|
|
158
|
+
}),
|
|
159
|
+
]),
|
|
160
|
+
}),
|
|
161
|
+
}),
|
|
162
|
+
undefined, // The abort signal parameter
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('numeric search is applied to adapter', async () => {
|
|
167
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(1), metadata: {} });
|
|
168
|
+
|
|
169
|
+
const { adapter } = setup({ search: { textSearchableProperties: ['id'] } });
|
|
170
|
+
|
|
171
|
+
const searchDefinition = searchTerm('42', ['name'], ['id']);
|
|
172
|
+
if (!searchDefinition) throw new Error('searchDefinition is undefined');
|
|
173
|
+
const promise = adapter.setSearchDefinition(searchDefinition);
|
|
174
|
+
await fetchData();
|
|
175
|
+
await promise;
|
|
176
|
+
|
|
177
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
178
|
+
expect.objectContaining({
|
|
179
|
+
searchDefinition: expect.objectContaining({
|
|
180
|
+
logicalOperator: expect.any(Number),
|
|
181
|
+
propertyCriteria: expect.arrayContaining([
|
|
182
|
+
expect.objectContaining({
|
|
183
|
+
propertyPath: ['Id'],
|
|
184
|
+
comparisonOperator: ReadSelectedComparisonOperator.Equal,
|
|
185
|
+
valueType: ReadSelectedPropertyType.Int,
|
|
186
|
+
value: 42,
|
|
187
|
+
}),
|
|
188
|
+
]),
|
|
189
|
+
}),
|
|
190
|
+
}),
|
|
191
|
+
undefined,
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('applies date range filter from-to on the same date type property', async () => {
|
|
196
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(1), metadata: {} });
|
|
197
|
+
|
|
198
|
+
const { adapter } = setup();
|
|
199
|
+
const from = new Date('2024-01-05T00:00:00.000Z');
|
|
200
|
+
const to = new Date('2024-01-20T00:00:00.000Z');
|
|
201
|
+
|
|
202
|
+
const searchDefinition = dateInterval<TestDto>(
|
|
203
|
+
from,
|
|
204
|
+
to,
|
|
205
|
+
ReadSelectedComparisonOperator.GreaterOrEqual,
|
|
206
|
+
ReadSelectedComparisonOperator.LessOrEqual,
|
|
207
|
+
ReadSelectedPropertyType.DateTimeOffset,
|
|
208
|
+
'createdAt',
|
|
209
|
+
);
|
|
210
|
+
if (!searchDefinition) throw new Error('searchDefinition is undefined');
|
|
211
|
+
const promise = adapter.setSearchDefinition(searchDefinition);
|
|
212
|
+
await fetchData();
|
|
213
|
+
await promise;
|
|
214
|
+
|
|
215
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
216
|
+
expect.objectContaining({
|
|
217
|
+
searchDefinition: expect.objectContaining({
|
|
218
|
+
logicalOperator: ReadSelectedLogicalOperator.And,
|
|
219
|
+
propertyCriteria: expect.arrayContaining([
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
propertyPath: ['DateProperty'],
|
|
222
|
+
comparisonOperator: ReadSelectedComparisonOperator.GreaterOrEqual,
|
|
223
|
+
valueType: ReadSelectedPropertyType.DateTimeOffset,
|
|
224
|
+
value: expect.any(Object),
|
|
225
|
+
}),
|
|
226
|
+
expect.objectContaining({
|
|
227
|
+
propertyPath: ['DateProperty'],
|
|
228
|
+
comparisonOperator: ReadSelectedComparisonOperator.LessOrEqual,
|
|
229
|
+
valueType: ReadSelectedPropertyType.DateTimeOffset,
|
|
230
|
+
value: expect.any(Object),
|
|
231
|
+
}),
|
|
232
|
+
]),
|
|
233
|
+
}),
|
|
234
|
+
}),
|
|
235
|
+
undefined, // The abort signal parameter
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('applies pagination: page size and jump to page set skip/limit', async () => {
|
|
240
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(5), metadata: {} });
|
|
241
|
+
|
|
242
|
+
const { adapter } = setup({ pagination: { useTotalItemCount: true, pageSize: 5, pageNumber: 1 } });
|
|
243
|
+
|
|
244
|
+
const promise = adapter.jumpToPage(3);
|
|
245
|
+
await fetchData();
|
|
246
|
+
await promise;
|
|
247
|
+
|
|
248
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
249
|
+
// ReadSelectedDefinitionDto
|
|
250
|
+
expect.objectContaining({
|
|
251
|
+
paginationDefinition: { skip: 10, limit: 5 },
|
|
252
|
+
searchDefinition: undefined,
|
|
253
|
+
}),
|
|
254
|
+
undefined, // The abort signal parameter
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('calculates total when useTotalItemCount=false and last page is partial', async () => {
|
|
259
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
|
|
260
|
+
|
|
261
|
+
const { adapter } = setup({ pagination: { useTotalItemCount: false, pageSize: 5, pageNumber: 2 } });
|
|
262
|
+
|
|
263
|
+
await fetchData();
|
|
264
|
+
|
|
265
|
+
expect(adapter.totalItemCount).toBe(8); // 3 items on page 2 (5 skipped on first page) ... 5+3=8
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('setOrdering correctly handles PropertyPathDto array comparison and limits columns', async () => {
|
|
269
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
|
|
270
|
+
|
|
271
|
+
const { adapter } = setup({ ordering: { maxActiveOrderingColumns: 2, orderByPaths: [] } });
|
|
272
|
+
|
|
273
|
+
// Add first ordering
|
|
274
|
+
let promise = adapter.setOrdering(['name'], ReadSelectedOrderingDirection.Descending);
|
|
275
|
+
await fetchData();
|
|
276
|
+
await promise;
|
|
277
|
+
|
|
278
|
+
expect(adapter.getCurrentOrdering()).toEqual({
|
|
279
|
+
orderByPath: ['name'],
|
|
280
|
+
orderDirection: ReadSelectedOrderingDirection.Descending,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Add second ordering
|
|
284
|
+
promise = adapter.setOrdering(['id'], ReadSelectedOrderingDirection.Ascending);
|
|
285
|
+
await fetchData();
|
|
286
|
+
await promise;
|
|
287
|
+
|
|
288
|
+
// Should have both orderings, with 'id' first (most recent)
|
|
289
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
290
|
+
expect.objectContaining({
|
|
291
|
+
orderingDefinition: {
|
|
292
|
+
order: [
|
|
293
|
+
{
|
|
294
|
+
propertyPath: ['Id'], // PascalCased
|
|
295
|
+
direction: ReadSelectedOrderingDirection.Ascending,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
propertyPath: ['Name'], // PascalCased
|
|
299
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
undefined,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Add third ordering - should remove the oldest one (name)
|
|
308
|
+
promise = adapter.setOrdering(['createdAt'], ReadSelectedOrderingDirection.Descending);
|
|
309
|
+
await fetchData();
|
|
310
|
+
await promise;
|
|
311
|
+
|
|
312
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
313
|
+
expect.objectContaining({
|
|
314
|
+
orderingDefinition: {
|
|
315
|
+
order: [
|
|
316
|
+
{
|
|
317
|
+
propertyPath: ['CreatedAt'], // PascalCased
|
|
318
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
propertyPath: ['Id'], // PascalCased
|
|
322
|
+
direction: ReadSelectedOrderingDirection.Ascending,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
undefined,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Update existing ordering - should replace, not add
|
|
331
|
+
promise = adapter.setOrdering(['id'], ReadSelectedOrderingDirection.Descending);
|
|
332
|
+
await fetchData();
|
|
333
|
+
await promise;
|
|
334
|
+
|
|
335
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
336
|
+
expect.objectContaining({
|
|
337
|
+
orderingDefinition: {
|
|
338
|
+
order: [
|
|
339
|
+
{
|
|
340
|
+
propertyPath: ['Id'], // PascalCased - updated direction
|
|
341
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
propertyPath: ['CreatedAt'], // PascalCased
|
|
345
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
}),
|
|
350
|
+
undefined,
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('setOrdering correctly compares complex PropertyPathDto arrays', async () => {
|
|
355
|
+
mockReadSelectedAsync.mockResolvedValue({ result: makeItems(3), metadata: {} });
|
|
356
|
+
|
|
357
|
+
const { adapter } = setup({ ordering: { maxActiveOrderingColumns: 3, orderByPaths: [] } });
|
|
358
|
+
|
|
359
|
+
// Add ordering with nested property path
|
|
360
|
+
let promise = adapter.setOrdering(['user', 'profile', 'name'], ReadSelectedOrderingDirection.Ascending);
|
|
361
|
+
await fetchData();
|
|
362
|
+
await promise;
|
|
363
|
+
|
|
364
|
+
// Add different nested path
|
|
365
|
+
promise = adapter.setOrdering(['user', 'settings', 'theme'], ReadSelectedOrderingDirection.Descending);
|
|
366
|
+
await fetchData();
|
|
367
|
+
await promise;
|
|
368
|
+
|
|
369
|
+
// Update the first nested path - should replace it, not add new
|
|
370
|
+
promise = adapter.setOrdering(['user', 'profile', 'name'], ReadSelectedOrderingDirection.Descending);
|
|
371
|
+
await fetchData();
|
|
372
|
+
await promise;
|
|
373
|
+
|
|
374
|
+
expect(mockReadSelectedAsync).toHaveBeenLastCalledWith(
|
|
375
|
+
expect.objectContaining({
|
|
376
|
+
orderingDefinition: {
|
|
377
|
+
order: [
|
|
378
|
+
{
|
|
379
|
+
propertyPath: ['User', 'Profile', 'Name'], // PascalCased - updated direction
|
|
380
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
propertyPath: ['User', 'Settings', 'Theme'], // PascalCased
|
|
384
|
+
direction: ReadSelectedOrderingDirection.Descending,
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
}),
|
|
389
|
+
undefined,
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { AngularHttpService } from '../../angular/projects/intellegens/cornerstone-client-angular/src/lib/services/AngularHttpService';
|
|
2
|
+
import { FetchHttpService, IHttpService } from '../src';
|
|
3
|
+
import { jest } from '@jest/globals';
|
|
4
|
+
import { of, throwError } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
// Mock fetch for testing
|
|
7
|
+
const mockFetch = jest.fn() as jest.MockedFunction<typeof fetch>;
|
|
8
|
+
global.fetch = mockFetch;
|
|
9
|
+
|
|
10
|
+
// Mock Angular HttpClient
|
|
11
|
+
const mockAngularHttpClient = {
|
|
12
|
+
get: jest.fn(),
|
|
13
|
+
post: jest.fn(),
|
|
14
|
+
put: jest.fn(),
|
|
15
|
+
delete: jest.fn(),
|
|
16
|
+
patch: jest.fn(),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('HttpService Implementations', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('FetchHttpService', () => {
|
|
25
|
+
let fetchService: FetchHttpService;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
fetchService = new FetchHttpService();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should make a GET request successfully', async () => {
|
|
32
|
+
const mockResponse = {
|
|
33
|
+
ok: true,
|
|
34
|
+
status: 200,
|
|
35
|
+
statusText: 'OK',
|
|
36
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
37
|
+
json: jest.fn<() => Promise<any>>().mockResolvedValue({ data: 'test' }),
|
|
38
|
+
text: jest.fn<() => Promise<string>>().mockResolvedValue('{"data":"test"}'),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Mock Headers.forEach
|
|
42
|
+
mockResponse.headers.forEach = jest.fn((callback: (value: string, key: string) => void) => {
|
|
43
|
+
callback('application/json', 'content-type');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
mockFetch.mockResolvedValue(mockResponse as any);
|
|
47
|
+
|
|
48
|
+
const result = await fetchService.request('https://api.example.com/test');
|
|
49
|
+
|
|
50
|
+
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/test', {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
headers: undefined,
|
|
53
|
+
body: undefined,
|
|
54
|
+
credentials: 'include',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(result.ok).toBe(true);
|
|
58
|
+
expect(result.status).toBe(200);
|
|
59
|
+
expect(result.statusText).toBe('OK');
|
|
60
|
+
expect(result.headers).toEqual({ 'content-type': 'application/json' });
|
|
61
|
+
|
|
62
|
+
const jsonData = await result.json();
|
|
63
|
+
expect(jsonData).toEqual({ data: 'test' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should make a POST request with body', async () => {
|
|
67
|
+
const mockResponse = {
|
|
68
|
+
ok: true,
|
|
69
|
+
status: 201,
|
|
70
|
+
statusText: 'Created',
|
|
71
|
+
headers: new Map(),
|
|
72
|
+
json: jest.fn<() => Promise<any>>().mockResolvedValue({ id: 1 }),
|
|
73
|
+
text: jest.fn<() => Promise<string>>().mockResolvedValue('{"id":1}'),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
mockResponse.headers.forEach = jest.fn();
|
|
77
|
+
mockFetch.mockResolvedValue(mockResponse as any);
|
|
78
|
+
|
|
79
|
+
const testData = { name: 'test' };
|
|
80
|
+
const result = await fetchService.request('https://api.example.com/create', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
body: JSON.stringify(testData),
|
|
84
|
+
credentials: 'include',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/create', {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'Content-Type': 'application/json' },
|
|
90
|
+
body: JSON.stringify(testData),
|
|
91
|
+
credentials: 'include',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result.ok).toBe(true);
|
|
95
|
+
expect(result.status).toBe(201);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle fetch errors', async () => {
|
|
99
|
+
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await fetchService.request('https://api.example.com/error');
|
|
103
|
+
fail('Should have thrown an error');
|
|
104
|
+
} catch (error: any) {
|
|
105
|
+
expect(error.message).toBe('Network error');
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle non-ok responses', async () => {
|
|
110
|
+
const mockResponse = {
|
|
111
|
+
ok: false,
|
|
112
|
+
status: 404,
|
|
113
|
+
statusText: 'Not Found',
|
|
114
|
+
headers: new Map(),
|
|
115
|
+
json: jest.fn<() => Promise<any>>().mockResolvedValue({ error: 'Not found' }),
|
|
116
|
+
text: jest.fn<() => Promise<string>>().mockResolvedValue('{"error":"Not found"}'),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
mockResponse.headers.forEach = jest.fn();
|
|
120
|
+
mockFetch.mockResolvedValue(mockResponse as any);
|
|
121
|
+
|
|
122
|
+
const result = await fetchService.request('https://api.example.com/notfound');
|
|
123
|
+
|
|
124
|
+
expect(result.ok).toBe(false);
|
|
125
|
+
expect(result.status).toBe(404);
|
|
126
|
+
expect(result.statusText).toBe('Not Found');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('AngularHttpService', () => {
|
|
131
|
+
let angularService: AngularHttpService;
|
|
132
|
+
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
angularService = new AngularHttpService(mockAngularHttpClient as any);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should throw error if no HttpClient provided', () => {
|
|
138
|
+
expect(() => new AngularHttpService(null as any)).toThrow('Angular HttpClient instance is required');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should make a GET request successfully', async () => {
|
|
142
|
+
const mockResponse = {
|
|
143
|
+
status: 200,
|
|
144
|
+
statusText: 'OK',
|
|
145
|
+
body: { data: 'test' },
|
|
146
|
+
headers: {
|
|
147
|
+
keys: jest.fn().mockReturnValue(['content-type']),
|
|
148
|
+
get: jest.fn().mockReturnValue('application/json'),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
mockAngularHttpClient.get.mockReturnValue(of(mockResponse));
|
|
153
|
+
|
|
154
|
+
const result = await angularService.request('https://api.example.com/test');
|
|
155
|
+
|
|
156
|
+
expect(mockAngularHttpClient.get).toHaveBeenCalledWith('https://api.example.com/test', {
|
|
157
|
+
headers: {},
|
|
158
|
+
withCredentials: false,
|
|
159
|
+
observe: 'response',
|
|
160
|
+
responseType: 'json',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(result.ok).toBe(true);
|
|
164
|
+
expect(result.status).toBe(200);
|
|
165
|
+
expect(result.statusText).toBe('OK');
|
|
166
|
+
expect(result.headers).toEqual({ 'content-type': 'application/json' });
|
|
167
|
+
|
|
168
|
+
const jsonData = await result.json();
|
|
169
|
+
expect(jsonData).toEqual({ data: 'test' });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should make a POST request with body', async () => {
|
|
173
|
+
const mockResponse = {
|
|
174
|
+
status: 201,
|
|
175
|
+
statusText: 'Created',
|
|
176
|
+
body: { id: 1 },
|
|
177
|
+
headers: {
|
|
178
|
+
keys: jest.fn().mockReturnValue([]),
|
|
179
|
+
get: jest.fn(),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
mockAngularHttpClient.post.mockReturnValue(of(mockResponse));
|
|
184
|
+
|
|
185
|
+
const testData = { name: 'test' };
|
|
186
|
+
const result = await angularService.request('https://api.example.com/create', {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers: { 'Content-Type': 'application/json' },
|
|
189
|
+
body: JSON.stringify(testData),
|
|
190
|
+
credentials: 'include',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(mockAngularHttpClient.post).toHaveBeenCalledWith('https://api.example.com/create', testData, {
|
|
194
|
+
headers: { 'Content-Type': 'application/json' },
|
|
195
|
+
withCredentials: true,
|
|
196
|
+
observe: 'response',
|
|
197
|
+
responseType: 'json',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(result.ok).toBe(true);
|
|
201
|
+
expect(result.status).toBe(201);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should handle PUT requests', async () => {
|
|
205
|
+
const mockResponse = {
|
|
206
|
+
status: 200,
|
|
207
|
+
statusText: 'OK',
|
|
208
|
+
body: { updated: true },
|
|
209
|
+
headers: {
|
|
210
|
+
keys: jest.fn().mockReturnValue([]),
|
|
211
|
+
get: jest.fn(),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
mockAngularHttpClient.put.mockReturnValue(of(mockResponse));
|
|
216
|
+
|
|
217
|
+
const result = await angularService.request('https://api.example.com/update', {
|
|
218
|
+
method: 'PUT',
|
|
219
|
+
body: JSON.stringify({ id: 1, name: 'updated' }),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(mockAngularHttpClient.put).toHaveBeenCalled();
|
|
223
|
+
expect(result.ok).toBe(true);
|
|
224
|
+
expect(result.status).toBe(200);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should handle DELETE requests', async () => {
|
|
228
|
+
const mockResponse = {
|
|
229
|
+
status: 204,
|
|
230
|
+
statusText: 'No Content',
|
|
231
|
+
body: null,
|
|
232
|
+
headers: {
|
|
233
|
+
keys: jest.fn().mockReturnValue([]),
|
|
234
|
+
get: jest.fn(),
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
mockAngularHttpClient.delete.mockReturnValue(of(mockResponse));
|
|
239
|
+
|
|
240
|
+
const result = await angularService.request('https://api.example.com/delete/1', {
|
|
241
|
+
method: 'DELETE',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(mockAngularHttpClient.delete).toHaveBeenCalled();
|
|
245
|
+
expect(result.ok).toBe(true);
|
|
246
|
+
expect(result.status).toBe(204);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should handle Angular HttpErrorResponse', async () => {
|
|
250
|
+
const mockError = {
|
|
251
|
+
status: 400,
|
|
252
|
+
statusText: 'Bad Request',
|
|
253
|
+
error: { message: 'Invalid data' },
|
|
254
|
+
headers: {
|
|
255
|
+
keys: jest.fn().mockReturnValue(['content-type']),
|
|
256
|
+
get: jest.fn().mockReturnValue('application/json'),
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
mockAngularHttpClient.get.mockReturnValue(throwError(() => mockError));
|
|
261
|
+
|
|
262
|
+
const result = await angularService.request('https://api.example.com/error');
|
|
263
|
+
|
|
264
|
+
expect(result.ok).toBe(false);
|
|
265
|
+
expect(result.status).toBe(400);
|
|
266
|
+
expect(result.statusText).toBe('Bad Request');
|
|
267
|
+
expect(result.headers).toEqual({ 'content-type': 'application/json' });
|
|
268
|
+
|
|
269
|
+
const errorData = await result.json();
|
|
270
|
+
expect(errorData).toEqual({ message: 'Invalid data' });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should throw error for unsupported HTTP methods', async () => {
|
|
274
|
+
try {
|
|
275
|
+
await angularService.request('https://api.example.com/test', { method: 'OPTIONS' as any });
|
|
276
|
+
fail('Should have thrown an error');
|
|
277
|
+
} catch (error: any) {
|
|
278
|
+
expect(error.message).toContain('Unsupported HTTP method: OPTIONS');
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should handle non-Angular errors', async () => {
|
|
283
|
+
mockAngularHttpClient.get.mockReturnValue(throwError(() => new Error('Network error')));
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
await angularService.request('https://api.example.com/error');
|
|
287
|
+
fail('Should have thrown an error');
|
|
288
|
+
} catch (error: any) {
|
|
289
|
+
expect(error.message).toBe('Network error');
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('Interface Compliance', () => {
|
|
295
|
+
it('should implement IHttpService interface correctly', () => {
|
|
296
|
+
const fetchService: IHttpService = new FetchHttpService();
|
|
297
|
+
const angularService: IHttpService = new AngularHttpService(mockAngularHttpClient as any);
|
|
298
|
+
|
|
299
|
+
expect(typeof fetchService.request).toBe('function');
|
|
300
|
+
expect(typeof angularService.request).toBe('function');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|